diff options
-rw-r--r-- | java/res/values/attrs.xml | 1 | ||||
-rw-r--r-- | java/res/values/config.xml | 1 | ||||
-rw-r--r-- | java/res/values/styles.xml | 4 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java | 47 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java | 27 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java | 54 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/CandidateView.java | 9 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java | 17 | ||||
-rw-r--r-- | java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java | 25 | ||||
-rw-r--r-- | native/src/correction.cpp | 198 | ||||
-rw-r--r-- | native/src/correction.h | 3 | ||||
-rw-r--r-- | native/src/defines.h | 3 |
12 files changed, 257 insertions, 132 deletions
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index c2200b5ad..172ca2f25 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -107,7 +107,6 @@ </declare-styleable> <declare-styleable name="CandidateView"> - <attr name="autoCorrectionVisualFlashEnabled" format="boolean" /> <attr name="autoCorrectHighlight" format="integer"> <flag name="autoCorrectBold" value="0x01" /> <flag name="autoCorrectUnderline" value="0x02" /> diff --git a/java/res/values/config.xml b/java/res/values/config.xml index 6327ede38..1c7c1a172 100644 --- a/java/res/values/config.xml +++ b/java/res/values/config.xml @@ -39,7 +39,6 @@ <bool name="config_default_compat_recorrection_enabled">true</bool> <bool name="config_default_sound_enabled">false</bool> <bool name="config_auto_correction_spacebar_led_enabled">true</bool> - <bool name="config_auto_correction_suggestion_strip_visual_flash_enabled">false</bool> <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false --> <bool name="config_show_mini_keyboard_at_touched_point">false</bool> <!-- The language is never displayed if == 0, always displayed if < 0 --> diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml index a47eeed12..8145d0510 100644 --- a/java/res/values/styles.xml +++ b/java/res/values/styles.xml @@ -85,7 +85,6 @@ <item name="android:background">@drawable/candidate_feedback_background</item> </style> <style name="CandidateViewStyle" parent="SuggestionsStripBackgroundStyle"> - <item name="autoCorrectionVisualFlashEnabled">@bool/config_auto_correction_suggestion_strip_visual_flash_enabled</item> <item name="autoCorrectHighlight">autoCorrectBold</item> <item name="colorTypedWord">#FFFFFFFF</item> <item name="colorAutoCorrect">#FFFCAE00</item> @@ -189,8 +188,7 @@ <item name="android:background">@drawable/keyboard_popup_panel_background_holo</item> </style> <style name="CandidateViewStyle.IceCreamSandwich" parent="SuggestionsStripBackgroundStyle.IceCreamSandwich"> - <item name="autoCorrectionVisualFlashEnabled">@bool/config_auto_correction_suggestion_strip_visual_flash_enabled</item> - <item name="autoCorrectHighlight">autoCorrectBold|autoCorrectInvert</item> + <item name="autoCorrectHighlight">autoCorrectBold</item> <item name="colorTypedWord">#FFFFFFFF</item> <item name="colorAutoCorrect">#FF3DC8FF</item> <item name="colorSuggested">#FFFFFFFF</item> diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index ae9809e56..3dca9aae6 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -20,12 +20,18 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.Paint; +import android.inputmethodservice.InputMethodService; +import android.media.AudioManager; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.AccessibilityEventCompatUtils; +import com.android.inputmethod.compat.AudioManagerCompatWrapper; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; @@ -39,17 +45,19 @@ public class AccessibleKeyboardViewProxy { // Delay in milliseconds between key press DOWN and UP events private static final long DELAY_KEY_PRESS = 10; - private int mScaledEdgeSlop; + private InputMethodService mInputMethod; + private FlickGestureDetector mGestureDetector; private LatinKeyboardBaseView mView; private AccessibleKeyboardActionListener mListener; - private FlickGestureDetector mGestureDetector; + private AudioManagerCompatWrapper mAudioManager; + private int mScaledEdgeSlop; private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY; private int mLastX = -1; private int mLastY = -1; - public static void init(Context context, SharedPreferences prefs) { - sInstance.initInternal(context, prefs); + public static void init(InputMethodService inputMethod, SharedPreferences prefs) { + sInstance.initInternal(inputMethod, prefs); sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance(); } @@ -65,15 +73,36 @@ public class AccessibleKeyboardViewProxy { // Not publicly instantiable. } - private void initInternal(Context context, SharedPreferences prefs) { + private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) { final Paint paint = new Paint(); paint.setTextAlign(Paint.Align.LEFT); paint.setTextSize(14.0f); paint.setAntiAlias(true); paint.setColor(Color.YELLOW); - mGestureDetector = new KeyboardFlickGestureDetector(context); - mScaledEdgeSlop = ViewConfiguration.get(context).getScaledEdgeSlop(); + mInputMethod = inputMethod; + mGestureDetector = new KeyboardFlickGestureDetector(inputMethod); + mScaledEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop(); + + final AudioManager audioManager = (AudioManager) inputMethod + .getSystemService(Context.AUDIO_SERVICE); + mAudioManager = new AudioManagerCompatWrapper(audioManager); + } + + /** + * @return {@code true} if the device should not speak text (eg. non-control) characters + */ + private boolean shouldObscureInput() { + // Always speak if the user is listening through headphones. + if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) + return false; + + final EditorInfo info = mInputMethod.getCurrentInputEditorInfo(); + if (info == null) + return false; + + // Don't speak if the IME is connected to a password field. + return InputTypeCompatUtils.isPasswordInputType(info.inputType); } public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event, @@ -90,8 +119,10 @@ public class AccessibleKeyboardViewProxy { if (key == null) break; + final boolean shouldObscure = shouldObscureInput(); final CharSequence description = KeyCodeDescriptionMapper.getInstance() - .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key); + .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key, + shouldObscure); if (description == null) return false; diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index ec4287dda..7302830d4 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -28,6 +28,9 @@ import com.android.inputmethod.latin.R; import java.util.HashMap; public class KeyCodeDescriptionMapper { + // The resource ID of the string spoken for obscured keys + private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; + private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); // Map of key labels to spoken description resource IDs @@ -118,10 +121,12 @@ public class KeyCodeDescriptionMapper { * @param context The package's context. * @param keyboard The keyboard on which the key resides. * @param key The key from which to obtain a description. + * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. * @return a character sequence describing the action performed by pressing * the key */ - public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key) { + public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key, + boolean shouldObscure) { if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard); if (description != null) @@ -136,12 +141,12 @@ public class KeyCodeDescriptionMapper { } else if (label.length() == 1 || (keyboard.isManualTemporaryUpperCase() && !TextUtils .isEmpty(key.mHintLabel))) { - return getDescriptionForKeyCode(context, keyboard, key); + return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } else { return label; } } else if (key.mCode != Keyboard.CODE_DUMMY) { - return getDescriptionForKeyCode(context, keyboard, key); + return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } return null; @@ -206,19 +211,29 @@ public class KeyCodeDescriptionMapper { * @param context The package's context. * @param keyboard The keyboard on which the key resides. * @param key The key from which to obtain a description. + * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. * @return a character sequence describing the action performed by pressing * the key */ - private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key) { + private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, + boolean shouldObscure) { final int code = getCorrectKeyCode(keyboard, key); if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) { return context.getString(mShiftLockedKeyCodeMap.get(code)); } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) { return context.getString(mShiftedKeyCodeMap.get(code)); - } else if (mKeyCodeMap.containsKey(code)) { + } + + // If the key description should be obscured, now is the time to do it. + final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); + if (shouldObscure && isDefinedNonCtrl) { + return context.getString(OBSCURED_KEY_RES_ID); + } + + if (mKeyCodeMap.containsKey(code)) { return context.getString(mKeyCodeMap.get(code)); - } else if (Character.isDefined(code) && !Character.isISOControl(code)) { + } else if (isDefinedNonCtrl) { return Character.toString((char) code); } else { return context.getString(R.string.spoken_description_unknown, code); diff --git a/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java new file mode 100644 index 000000000..b6c3e2a88 --- /dev/null +++ b/java/src/com/android/inputmethod/compat/AudioManagerCompatWrapper.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.compat; + +import android.media.AudioManager; + +import java.lang.reflect.Method; + +public class AudioManagerCompatWrapper { + private static final Method METHOD_isWiredHeadsetOn = CompatUtils.getMethod( + AudioManager.class, "isWiredHeadsetOn"); + private static final Method METHOD_isBluetoothA2dpOn = CompatUtils.getMethod( + AudioManager.class, "isBluetoothA2dpOn"); + + private final AudioManager mManager; + + public AudioManagerCompatWrapper(AudioManager manager) { + mManager = manager; + } + + /** + * Checks whether audio routing to the wired headset is on or off. + * + * @return true if audio is being routed to/from wired headset; + * false if otherwise + */ + public boolean isWiredHeadsetOn() { + return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isWiredHeadsetOn); + } + + /** + * Checks whether A2DP audio routing to the Bluetooth headset is on or off. + * + * @return true if A2DP audio is being routed to/from Bluetooth headset; + * false if otherwise + */ + public boolean isBluetoothA2dpOn() { + return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isBluetoothA2dpOn); + } +} diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index 0640fd0b1..f499bc0bb 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -278,7 +278,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(); - public final boolean mAutoCorrectionVisualFlashEnabled; public boolean mMoreSuggestionsAvailable; public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle, @@ -286,8 +285,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo super(words, dividers, infos); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle); - mAutoCorrectionVisualFlashEnabled = a.getBoolean( - R.styleable.CandidateView_autoCorrectionVisualFlashEnabled, false); mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0); mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0); mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0); @@ -584,7 +581,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo final int width = getWidth(); final int countInStrip = mStripParams.layout( mSuggestions, mCandidatesStrip, mCandidatesPane, width); - final int countInPane = mPaneParams.layout( + mPaneParams.layout( mSuggestions, mCandidatesPane, countInStrip, mStripParams.getTextColor(), width); } @@ -633,6 +630,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo private static CharSequence getEllipsizedText(CharSequence text, int maxWidth, TextPaint paint) { + if (text == null) return null; paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); if (width <= maxWidth) { @@ -703,9 +701,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { - if (!mStripParams.mAutoCorrectionVisualFlashEnabled) { - return; - } final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord); if (inverted == null) return; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index c71841042..649774d78 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.spellcheck; +import android.content.Intent; import android.content.res.Resources; import android.service.textservice.SpellCheckerService; import android.service.textservice.SpellCheckerService.Session; @@ -48,7 +49,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService { private static final int POOL_SIZE = 2; private final static String[] emptyArray = new String[0]; - private final Map<String, DictionaryPool> mDictionaryPools = + private Map<String, DictionaryPool> mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); @Override @@ -104,6 +105,16 @@ public class AndroidSpellCheckerService extends SpellCheckerService { } } + @Override + public boolean onUnbind(final Intent intent) { + final Map<String, DictionaryPool> oldPools = mDictionaryPools; + mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); + for (DictionaryPool pool : oldPools.values()) { + pool.close(); + } + return false; + } + private DictionaryPool getDictionaryPool(final String locale) { DictionaryPool pool = mDictionaryPools.get(locale); if (null == pool) { @@ -167,7 +178,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService { dictInfo.mDictionary.getWords(composer, suggestionsGatherer, dictInfo.mProximityInfo); isInDict = dictInfo.mDictionary.isValidWord(text); - mDictionaryPool.offer(dictInfo); + if (!mDictionaryPool.offer(dictInfo)) { + Log.e(TAG, "Can't re-insert a dictionary into its pool"); + } } catch (InterruptedException e) { // I don't think this can happen. return new SuggestionsInfo(0, new String[0]); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index dfbfcc7f6..ee294f6b0 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -28,7 +28,8 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { private final AndroidSpellCheckerService mService; private final int mMaxSize; private final Locale mLocale; - private int mSize = 0; + private int mSize; + private volatile boolean mClosed; public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service, final Locale locale) { @@ -36,6 +37,8 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { mMaxSize = maxSize; mService = service; mLocale = locale; + mSize = 0; + mClosed = false; } @Override @@ -52,4 +55,24 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { } } } + + public void close() { + synchronized(this) { + mClosed = true; + for (DictAndProximity dict : this) { + dict.mDictionary.close(); + } + clear(); + } + } + + @Override + public boolean offer(final DictAndProximity dict) { + if (mClosed) { + dict.mDictionary.close(); + return false; + } else { + return super.offer(dict); + } + } } diff --git a/native/src/correction.cpp b/native/src/correction.cpp index a4090a966..99412b211 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -95,10 +95,8 @@ int Correction::getFinalFreq(const int freq, unsigned short **word, int *wordLen } *word = mWord; - const bool sameLength = (mExcessivePos == mInputLength - 1) ? (mInputLength == inputIndex + 2) - : (mInputLength == inputIndex + 1); return Correction::RankingAlgorithm::calculateFinalFreq( - inputIndex, outputIndex, freq, sameLength, mEditDistanceTable, this); + inputIndex, outputIndex, freq, mEditDistanceTable, this); } bool Correction::initProcessState(const int outputIndex) { @@ -205,20 +203,6 @@ Correction::CorrectionType Correction::processCharAndCalcState( } if (mNeedsToTraverseAllNodes || isQuote(c)) { - const bool checkProximityChars = - !(mSkippedCount > 0 || mExcessivePos >= 0 || mTransposedPos >= 0); - // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of - // proximity chars of "s", but it should rather be handled as a skipped char. - if (checkProximityChars - && mInputIndex > 0 - && mCorrectionStates[mOutputIndex].mProximityMatching - && mCorrectionStates[mOutputIndex].mSkipping - && mProximityInfo->getMatchedProximityId( - mInputIndex - 1, c, false) - == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { - ++mSkippedCount; - --mProximityCount; - } return processSkipChar(c, isTerminal); } else { int inputIndexForProximity = mInputIndex; @@ -250,6 +234,8 @@ Correction::CorrectionType Correction::processCharAndCalcState( && mProximityInfo->getMatchedProximityId( inputIndexForProximity - 1, c, false) == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) { + // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of + // proximity chars of "s", but it should rather be handled as a skipped char. ++mSkippedCount; --mProximityCount; return processSkipChar(c, isTerminal); @@ -344,6 +330,16 @@ inline static void multiplyRate(const int rate, int *freq) { } } +inline static int getQuoteCount(const unsigned short* word, const int length) { + int quoteCount = 0; + for (int i = 0; i < length; ++i) { + if(word[i] == '\'') { + ++quoteCount; + } + } + return quoteCount; +} + /* static */ inline static int editDistance( int* editDistanceTable, const unsigned short* input, @@ -392,8 +388,7 @@ inline static int editDistance( /* static */ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int outputIndex, - const int freq, const bool sameLength, int* editDistanceTable, - const Correction* correction) { + const int freq, int* editDistanceTable, const Correction* correction) { const int excessivePos = correction->getExcessivePos(); const int transposedPos = correction->getTransposedPos(); const int inputLength = correction->mInputLength; @@ -402,6 +397,12 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const const ProximityInfo *proximityInfo = correction->mProximityInfo; const int skipCount = correction->mSkippedCount; const int proximityMatchedCount = correction->mProximityCount; + if (skipCount >= inputLength || inputLength == 0) { + return -1; + } + const bool sameLength = (excessivePos == inputLength - 1) ? (inputLength == inputIndex + 2) + : (inputLength == inputIndex + 1); + // TODO: use mExcessiveCount int matchCount = inputLength - correction->mProximityCount - (excessivePos >= 0 ? 1 : 0); @@ -409,67 +410,52 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const const unsigned short* word = correction->mWord; const bool skipped = skipCount > 0; - // ----- TODO: use edit distance here as follows? ---------------------- / - //if (!skipped && excessivePos < 0 && transposedPos < 0) { - // const int ed = editDistance(dp, proximityInfo->getInputWord(), - // inputLength, word, outputIndex + 1); - // matchCount = outputIndex + 1 - ed; - // if (ed == 1 && !sameLength) ++matchCount; - //} - // const int ed = editDistance(dp, proximityInfo->getInputWord(), - // inputLength, word, outputIndex + 1); - // if (ed == 1 && !sameLength) ++matchCount; ------------------------ / - int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); + const int quoteDiffCount = max(0, getQuoteCount(word, outputIndex + 1) + - getQuoteCount(proximityInfo->getPrimaryInputWord(), inputLength)); - // TODO: Demote by edit distance - int finalFreq = freq * matchWeight; - // +1 +11/-12 - /*if (inputLength == outputIndex && !skipped && excessivePos < 0 && transposedPos < 0) { - const int ed = editDistance(dp, proximityInfo->getInputWord(), + // TODO: Calculate edit distance for transposed and excessive + int matchWeight; + int ed = 0; + int adJustedProximityMatchedCount = proximityMatchedCount; + if (excessivePos < 0 && transposedPos < 0 && (proximityMatchedCount > 0 || skipped)) { + const unsigned short* primaryInputWord = proximityInfo->getPrimaryInputWord(); + ed = editDistance(editDistanceTable, primaryInputWord, inputLength, word, outputIndex + 1); - if (ed == 1) { - multiplyRate(160, &finalFreq); - } - }*/ - if (inputLength == outputIndex && excessivePos < 0 && transposedPos < 0 - && (proximityMatchedCount > 0 || skipped)) { - const int ed = editDistance(editDistanceTable, proximityInfo->getPrimaryInputWord(), - inputLength, word, outputIndex + 1); - if (ed == 1) { - multiplyRate(160, &finalFreq); + matchWeight = powerIntCapped(typedLetterMultiplier, outputIndex + 1 - ed); + if (ed == 1 && inputLength == outputIndex) { + // Promote a word with just one skipped char + multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE, &matchWeight); } + ed = max(0, ed - quoteDiffCount); + adJustedProximityMatchedCount = min(max(0, ed - (outputIndex + 1 - inputLength)), + proximityMatchedCount); + } else { + matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); } - // TODO: Promote properly? - //if (skipCount == 1 && excessivePos < 0 && transposedPos < 0 && inputLength == outputIndex - // && !sameLength) { - // multiplyRate(150, &finalFreq); - //} - //if (skipCount == 0 && excessivePos < 0 && transposedPos < 0 && inputLength == outputIndex - // && !sameLength) { - // multiplyRate(150, &finalFreq); - //} - //if (skipCount == 0 && excessivePos < 0 && transposedPos < 0 - // && inputLength == outputIndex + 1) { - // multiplyRate(150, &finalFreq); - //} + // TODO: Demote by edit distance + int finalFreq = freq * matchWeight; + + /////////////////////////////////////////////// + // Promotion and Demotion for each correction + // Demotion for a word with missing character if (skipped) { - if (inputLength >= 2) { - const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE - * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) - / (10 * inputLength - - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); - if (DEBUG_DICT_FULL) { - LOGI("Demotion rate for missing character is %d.", demotionRate); - } - multiplyRate(demotionRate, &finalFreq); - } else { - finalFreq = 0; + const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE + * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) + / (10 * inputLength + - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); + if (DEBUG_DICT_FULL) { + LOGI("Demotion rate for missing character is %d.", demotionRate); } + multiplyRate(demotionRate, &finalFreq); } + + // Demotion for a word with transposed character if (transposedPos >= 0) multiplyRate( WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); + + // Demotion for a word with excessive character if (excessivePos >= 0) { multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); if (!proximityInfo->existsAdjacentProximityChars(inputIndex)) { @@ -478,52 +464,62 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq); } } - int lengthFreq = typedLetterMultiplier; - multiplyIntCapped(powerIntCapped(typedLetterMultiplier, outputIndex), &lengthFreq); - if ((outputIndex + 1) == matchCount) { - // Full exact match - if (outputIndex > 1) { - if (DEBUG_DICT) { - LOGI("Found full matched word."); - } - multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); - } - if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0) { - finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); - } - } else if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0 - && outputIndex > 0) { + + // Promotion for a word with proximity characters + for (int i = 0; i < adJustedProximityMatchedCount; ++i) { // A word with proximity corrections - if (DEBUG_DICT) { - LOGI("Found one proximity correction."); + if (DEBUG_DICT_FULL) { + LOGI("Found a proximity correction."); } multiplyIntCapped(typedLetterMultiplier, &finalFreq); multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); } - if (DEBUG_DICT_FULL) { - LOGI("calc: %d, %d", outputIndex, sameLength); + + const int errorCount = proximityMatchedCount + skipCount; + multiplyRate( + 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq); + + // Promotion for an exactly matched word + if (matchCount == outputIndex + 1) { + // Full exact match + if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0) { + finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); + } } - if (sameLength) multiplyIntCapped(fullWordMultiplier, &finalFreq); - // TODO: check excessive count and transposed count + // Promote a word with no correction + if (proximityMatchedCount == 0 && transposedPos < 0 && !skipped && excessivePos < 0) { + multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); + } + + // TODO: Check excessive count and transposed count + // TODO: Remove this if possible /* - If the last character of the user input word is the same as the next character - of the output word, and also all of characters of the user input are matched - to the output word, we'll promote that word a bit because - that word can be considered the combination of skipped and matched characters. - This means that the 'sm' pattern wins over the 'ma' pattern. - e.g.) - shel -> shell [mmmma] or [mmmsm] - hel -> hello [mmmaa] or [mmsma] - m ... matching - s ... skipping - a ... traversing all + If the last character of the user input word is the same as the next character + of the output word, and also all of characters of the user input are matched + to the output word, we'll promote that word a bit because + that word can be considered the combination of skipped and matched characters. + This means that the 'sm' pattern wins over the 'ma' pattern. + e.g.) + shel -> shell [mmmma] or [mmmsm] + hel -> hello [mmmaa] or [mmsma] + m ... matching + s ... skipping + a ... traversing all */ if (matchCount == inputLength && matchCount >= 2 && !skipped && word[matchCount] == word[matchCount - 1]) { multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq); } + if (sameLength) { + multiplyIntCapped(fullWordMultiplier, &finalFreq); + } + + if (DEBUG_DICT_FULL) { + LOGI("calc: %d, %d", outputIndex, sameLength); + } + return finalFreq; } diff --git a/native/src/correction.h b/native/src/correction.h index 9d385a44e..871a04251 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -139,8 +139,7 @@ private: class RankingAlgorithm { public: static int calculateFinalFreq(const int inputIndex, const int depth, - const int freq, const bool sameLength, int *editDistanceTable, - const Correction* correction); + const int freq, int *editDistanceTable, const Correction* correction); static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq, const Correction* correction); }; diff --git a/native/src/defines.h b/native/src/defines.h index c1d08e695..a29fb7e5b 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -177,6 +177,8 @@ static void dumpWord(const unsigned short* word, const int length) { #define FULL_MATCHED_WORDS_PROMOTION_RATE 120 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105 +#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 160 +#define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 42 // This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java // This is only used for the size of array. Not to be used in c functions. @@ -194,5 +196,6 @@ static void dumpWord(const unsigned short* word, const int length) { #define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3 #define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) #endif // LATINIME_DEFINES_H |