diff options
Diffstat (limited to 'java/src')
12 files changed, 260 insertions, 180 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java index 94a1ee6eb..e80982fc7 100644 --- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java +++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java @@ -269,13 +269,9 @@ public final class MainKeyboardAccessibilityDelegate eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */); // Inject a fake down event to {@link PointerTracker} to handle a long press correctly. tracker.processMotionEvent(downEvent, mKeyDetector); - // The above fake down event triggers an unnecessary long press timer that should be - // canceled. - tracker.cancelLongPressTimer(); downEvent.recycle(); - // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout - // has passed. - mKeyboardView.onLongPress(tracker); + // Invoke {@link PointerTracker#onLongPressed()} as if a long press timeout has passed. + tracker.onLongPressed(); // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout) // or a key invokes IME switcher dialog, we should just ignore the next // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 06b87bd9a..ad15fa223 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -461,12 +461,17 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy windowContentView.addView(mDrawingPreviewPlacerView); } + // Implements {@link DrawingProxy#onKeyPressed(Key,boolean)}. @Override - public void showKeyPreview(@Nonnull final Key key) { - // If the key is invalid or has no key preview, we must not show key preview. - if (key.noKeyPreview()) { - return; + public void onKeyPressed(@Nonnull final Key key, final boolean withPreview) { + key.onPressed(); + invalidateKey(key); + if (withPreview && !key.noKeyPreview()) { + showKeyPreview(key); } + } + + private void showKeyPreview(@Nonnull final Key key) { final Keyboard keyboard = getKeyboard(); if (keyboard == null) { return; @@ -483,15 +488,26 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy getWidth(), mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated()); } - // Implements {@link DrawingProxy#dismissKeyPreviewWithoutDelay(Key)}. - @Override - public void dismissKeyPreviewWithoutDelay(@Nonnull final Key key) { + private void dismissKeyPreviewWithoutDelay(@Nonnull final Key key) { mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */); invalidateKey(key); } + // Implements {@link DrawingProxy#onKeyReleased(Key,boolean)}. @Override - public void dismissKeyPreview(@Nonnull final Key key) { + public void onKeyReleased(@Nonnull final Key key, final boolean withAnimation) { + key.onReleased(); + invalidateKey(key); + if (!key.noKeyPreview()) { + if (withAnimation) { + dismissKeyPreview(key); + } else { + dismissKeyPreviewWithoutDelay(key); + } + } + } + + private void dismissKeyPreview(@Nonnull final Key key) { if (isHardwareAccelerated()) { mKeyPreviewChoreographer.dismissKeyPreview(key, true /* withAnimation */); return; @@ -574,7 +590,11 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy mDrawingPreviewPlacerView.removeAllViews(); } - private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { + // Implements {@link DrawingProxy@showMoreKeysKeyboard(Key,PointerTracker)}. + @Override + @Nullable + public MoreKeysPanel showMoreKeysKeyboard(@Nonnull final Key key, + @Nonnull final PointerTracker tracker) { final MoreKeySpec[] moreKeys = key.getMoreKeys(); if (moreKeys == null) { return null; @@ -590,7 +610,7 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy && !key.noKeyPreview() && moreKeys.length == 1 && mKeyPreviewDrawParams.getVisibleWidth() > 0; final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder( - context, key, getKeyboard(), isSingleMoreKeyWithPreview, + getContext(), key, getKeyboard(), isSingleMoreKeyWithPreview, mKeyPreviewDrawParams.getVisibleWidth(), mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key)); moreKeysKeyboard = builder.build(); @@ -603,50 +623,6 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); moreKeysKeyboardView.setKeyboard(moreKeysKeyboard); container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - return moreKeysKeyboardView; - } - - // Implements {@link DrawingProxy@onLongPress(PointerTracker)}. - /** - * Called when a key is long pressed. - * @param tracker the pointer tracker which pressed the parent key - */ - @Override - public void onLongPress(@Nonnull final PointerTracker tracker) { - if (isShowingMoreKeysPanel()) { - return; - } - final Key key = tracker.getKey(); - if (key == null) { - return; - } - final KeyboardActionListener listener = mKeyboardActionListener; - if (key.hasNoPanelAutoMoreKey()) { - final int moreKeyCode = key.getMoreKeys()[0].mCode; - tracker.onLongPressed(); - listener.onPressKey(moreKeyCode, 0 /* repeatCount */, true /* isSinglePointer */); - listener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, - Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); - listener.onReleaseKey(moreKeyCode, false /* withSliding */); - return; - } - final int code = key.getCode(); - if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { - // Long pressing the space key invokes IME switcher dialog. - if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { - tracker.onLongPressed(); - listener.onReleaseKey(code, false /* withSliding */); - return; - } - } - openMoreKeysPanel(key, tracker); - } - - private void openMoreKeysPanel(final Key key, final PointerTracker tracker) { - final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext()); - if (moreKeysPanel == null) { - return; - } final int[] lastCoords = CoordinateUtils.newInstance(); tracker.getLastCoordinates(lastCoords); @@ -664,10 +640,8 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy // {@code mPreviewVisibleOffset} has been set appropriately in // {@link KeyboardView#showKeyPreview(PointerTracker)}. final int pointY = key.getY() + mKeyPreviewDrawParams.getVisibleOffset(); - moreKeysPanel.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); - tracker.onShowMoreKeysPanel(moreKeysPanel); - // TODO: Implement zoom in animation of more keys panel. - mKeyPreviewChoreographer.dismissKeyPreview(key, false /* withAnimation */); + moreKeysKeyboardView.showMoreKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); + return moreKeysKeyboardView; } public boolean isInDraggingFinger() { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 7902ce852..9764cb389 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -222,7 +222,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int trackersSize = sTrackers.size(); for (int i = 0; i < trackersSize; ++i) { final PointerTracker tracker = sTrackers.get(i); - tracker.setReleasedKeyGraphics(tracker.getKey()); + tracker.setReleasedKeyGraphics(tracker.getKey(), true /* withAnimation */); } } @@ -382,19 +382,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element, return mKeyDetector.detectHitKey(x, y); } - private void setReleasedKeyGraphics(@Nullable final Key key) { + private void setReleasedKeyGraphics(@Nullable final Key key, final boolean withAnimation) { if (key == null) { return; } - sDrawingProxy.dismissKeyPreview(key); - // Even if the key is disabled, update the key release graphics just in case. - updateReleaseKeyGraphics(key); + sDrawingProxy.onKeyReleased(key, withAnimation); if (key.isShift()) { for (final Key shiftKey : mKeyboard.mShiftKeys) { if (shiftKey != key) { - updateReleaseKeyGraphics(shiftKey); + sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */); } } } @@ -403,11 +401,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { - updateReleaseKeyGraphics(altKey); + sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { if (k != key && k.getAltCode() == altCode) { - updateReleaseKeyGraphics(k); + sDrawingProxy.onKeyReleased(k, false /* withAnimation */); } } } @@ -418,7 +416,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime); } - private void setPressedKeyGraphics(final Key key, final long eventTime) { + private void setPressedKeyGraphics(@Nullable final Key key, final long eventTime) { if (key == null) { return; } @@ -430,15 +428,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element, return; } - if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) { - sDrawingProxy.showKeyPreview(key); - } - updatePressKeyGraphics(key); + final boolean noKeyPreview = sInGesture || needsToSuppressKeyPreviewPopup(eventTime); + sDrawingProxy.onKeyPressed(key, !noKeyPreview); if (key.isShift()) { for (final Key shiftKey : mKeyboard.mShiftKeys) { if (shiftKey != key) { - updatePressKeyGraphics(shiftKey); + sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */); } } } @@ -447,26 +443,16 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { - updatePressKeyGraphics(altKey); + sDrawingProxy.onKeyPressed(altKey, false /* withPreview */); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { if (k != key && k.getAltCode() == altCode) { - updatePressKeyGraphics(k); + sDrawingProxy.onKeyPressed(k, false /* withPreview */); } } } } - private static void updateReleaseKeyGraphics(final Key key) { - key.onReleased(); - sDrawingProxy.invalidateKey(key); - } - - private static void updatePressKeyGraphics(final Key key) { - key.onPressed(); - sDrawingProxy.invalidateKey(key); - } - public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() { return mGestureStrokeDrawingPoints; } @@ -837,7 +823,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, } private void processDraggingFingerOutFromOldKey(final Key oldKey) { - setReleasedKeyGraphics(oldKey); + setReleasedKeyGraphics(oldKey, true /* withAnimation */); callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */); startKeySelectionByDraggingFinger(oldKey); sTimerProxy.cancelKeyTimersOf(this); @@ -880,12 +866,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element, } onUpEvent(x, y, eventTime); cancelTrackingForAction(); - setReleasedKeyGraphics(oldKey); + setReleasedKeyGraphics(oldKey, true /* withAnimation */); } else { if (!mIsDetectingGesture) { cancelTrackingForAction(); } - setReleasedKeyGraphics(oldKey); + setReleasedKeyGraphics(oldKey, true /* withAnimation */); } } @@ -913,7 +899,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); if (sInGesture) { mCurrentKey = null; - setReleasedKeyGraphics(oldKey); + setReleasedKeyGraphics(oldKey, true /* withAnimation */); return; } } @@ -978,7 +964,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode; mCurrentRepeatingKeyCode = Constants.NOT_A_CODE; // Release the last pressed key. - setReleasedKeyGraphics(currentKey); + setReleasedKeyGraphics(currentKey, true /* withAnimation */); if (isShowingMoreKeysPanel()) { if (!mIsTrackingForActionDisabled) { @@ -1015,14 +1001,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, } } - public void onShowMoreKeysPanel(final MoreKeysPanel panel) { - setReleasedKeyGraphics(mCurrentKey); - final int translatedX = panel.translateX(mLastX); - final int translatedY = panel.translateY(mLastY); - panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); - mMoreKeysPanel = panel; - } - @Override public void cancelTrackingForAction() { if (isShowingMoreKeysPanel()) { @@ -1035,14 +1013,49 @@ public final class PointerTracker implements PointerTrackerQueue.Element, return !mIsTrackingForActionDisabled; } - public void cancelLongPressTimer() { + public void onLongPressed() { sTimerProxy.cancelLongPressTimersOf(this); + if (isShowingMoreKeysPanel()) { + return; + } + final Key key = getKey(); + if (key == null) { + return; + } + if (key.hasNoPanelAutoMoreKey()) { + cancelKeyTracking(); + final int moreKeyCode = key.getMoreKeys()[0].mCode; + sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */); + sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE, + Constants.NOT_A_COORDINATE, false /* isKeyRepeat */); + sListener.onReleaseKey(moreKeyCode, false /* withSliding */); + return; + } + final int code = key.getCode(); + if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) { + // Long pressing the space key invokes IME switcher dialog. + if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) { + cancelKeyTracking(); + sListener.onReleaseKey(code, false /* withSliding */); + return; + } + } + + setReleasedKeyGraphics(key, false /* withAnimation */); + final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this); + if (moreKeysPanel == null) { + return; + } + final int translatedX = moreKeysPanel.translateX(mLastX); + final int translatedY = moreKeysPanel.translateY(mLastY); + moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis()); + mMoreKeysPanel = moreKeysPanel; } - public void onLongPressed() { + private void cancelKeyTracking() { resetKeySelectionByDraggingFinger(); cancelTrackingForAction(); - setReleasedKeyGraphics(mCurrentKey); + setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); sPointerTrackerQueue.remove(this); } @@ -1059,7 +1072,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, private void onCancelEventInternal() { sTimerProxy.cancelKeyTimersOf(this); - setReleasedKeyGraphics(mCurrentKey); + setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */); resetKeySelectionByDraggingFinger(); dismissMoreKeysPanel(); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java index 7fc586a0f..06bdfc41b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingProxy.java @@ -17,29 +17,36 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.MoreKeysPanel; import com.android.inputmethod.keyboard.PointerTracker; import javax.annotation.Nonnull; import javax.annotation.Nullable; public interface DrawingProxy { - // TODO: Remove this method. - public void invalidateKey(@Nullable Key key); - - // TODO: Rename this method to onKeyPressed. - public void showKeyPreview(@Nonnull Key key); - - // TODO: Rename this method to onKeyReleased. - public void dismissKeyPreview(@Nonnull Key key); + /** + * Called when a key is being pressed. + * @param key the {@link Key} that is being pressed. + * @param withPreview true if key popup preview should be displayed. + */ + public void onKeyPressed(@Nonnull Key key, boolean withPreview); /** - * Dismiss a key preview visual without delay. - * @param key the key whose preview visual should be dismissed. + * Called when a key is being released. + * @param key the {@link Key} that is being released. + * @param withAnimation when true, key popup preview should be dismissed with animation. */ - public void dismissKeyPreviewWithoutDelay(@Nonnull Key key); + public void onKeyReleased(@Nonnull Key key, boolean withAnimation); - // TODO: Rename this method to onKeyLongPressed. - public void onLongPress(@Nonnull PointerTracker tracker); + /** + * Start showing more keys keyboard of a key that is being long pressed. + * @param key the {@link Key} that is being long pressed and showing more keys keyboard. + * @param tracker the {@link PointerTracker} that detects this long pressing. + * @return {@link MoreKeysPanel} that is being shown. null if there is no need to show more keys + * keyboard. + */ + @Nullable + public MoreKeysPanel showMoreKeysKeyboard(@Nonnull Key key, @Nonnull PointerTracker tracker); /** * Start a while-typing-animation. diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java index 8068427bc..91f3558eb 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java @@ -66,7 +66,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> case MSG_LONGPRESS_SHIFT_KEY: cancelLongPressTimers(); final PointerTracker tracker2 = (PointerTracker) msg.obj; - drawingProxy.onLongPress(tracker2); + tracker2.onLongPressed(); break; case MSG_UPDATE_BATCH_INPUT: final PointerTracker tracker3 = (PointerTracker) msg.obj; @@ -74,8 +74,7 @@ public final class TimerHandler extends LeakGuardHandlerWrapper<DrawingProxy> startUpdateBatchInputTimer(tracker3); break; case MSG_DISMISS_KEY_PREVIEW: - final Key key = (Key) msg.obj; - drawingProxy.dismissKeyPreviewWithoutDelay(key); + drawingProxy.onKeyReleased((Key) msg.obj, false /* withAnimation */); break; case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT: drawingProxy.dismissGestureFloatingPreviewTextWithoutDelay(); diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index b24fdea55..f2d7a8c3c 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -63,6 +63,9 @@ public class DictionaryFacilitator { // HACK: This threshold is being used when adding a capitalized entry in the User History // dictionary. private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140; + // How many words we need to type in a row ({@see mConfidenceInMostProbableLanguage}) to + // declare we are confident the user is typing in the most probable language. + private static final int CONFIDENCE_THRESHOLD = 3; private DictionaryGroup[] mDictionaryGroups = new DictionaryGroup[] { new DictionaryGroup() }; private DictionaryGroup mMostProbableDictionaryGroup = mDictionaryGroups[0]; @@ -138,6 +141,10 @@ public class DictionaryFacilitator { public final Locale mLocale; private Dictionary mMainDict; + // Confidence that the most probable language is actually the language the user is + // typing in. For now, this is simply the number of times a word from this language + // has been committed in a row. + private int mConfidence = 0; public float mWeightForTypingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; public float mWeightForGesturingInLocale = WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap = @@ -260,8 +267,9 @@ public class DictionaryFacilitator { public void switchMostProbableLanguage(final Locale locale) { if (null == locale) { // In many cases, there is no locale to a committed word. For example, a typed word - // that does not auto-correct has no locale. In this case we simply do not change - // the most probable language. + // that is in none of the currently active dictionaries but still does not + // auto-correct to anything has no locale. In this case we simply do not change + // the most probable language and do not touch confidence. return; } final DictionaryGroup newMostProbableDictionaryGroup = @@ -272,15 +280,28 @@ public class DictionaryFacilitator { // facilitator any more. In this case, just not changing things is fine. return; } - mMostProbableDictionaryGroup.mWeightForTypingInLocale = - DictionaryGroup.WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE; - mMostProbableDictionaryGroup.mWeightForGesturingInLocale = - DictionaryGroup.WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE; - newMostProbableDictionaryGroup.mWeightForTypingInLocale = - DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; - newMostProbableDictionaryGroup.mWeightForGesturingInLocale = - DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; - mMostProbableDictionaryGroup = newMostProbableDictionaryGroup; + if (newMostProbableDictionaryGroup == mMostProbableDictionaryGroup) { + ++newMostProbableDictionaryGroup.mConfidence; + } else { + mMostProbableDictionaryGroup.mWeightForTypingInLocale = + DictionaryGroup.WEIGHT_FOR_TYPING_IN_NOT_MOST_PROBABLE_LANGUAGE; + mMostProbableDictionaryGroup.mWeightForGesturingInLocale = + DictionaryGroup.WEIGHT_FOR_GESTURING_IN_NOT_MOST_PROBABLE_LANGUAGE; + mMostProbableDictionaryGroup.mConfidence = 0; + newMostProbableDictionaryGroup.mWeightForTypingInLocale = + DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; + newMostProbableDictionaryGroup.mWeightForGesturingInLocale = + DictionaryGroup.WEIGHT_FOR_MOST_PROBABLE_LANGUAGE; + mMostProbableDictionaryGroup = newMostProbableDictionaryGroup; + } + } + + public boolean isConfidentAboutCurrentLanguageBeing(final Locale mLocale) { + final DictionaryGroup mostProbableDictionaryGroup = mMostProbableDictionaryGroup; + if (!mostProbableDictionaryGroup.mLocale.equals(mLocale)) { + return false; + } + return mostProbableDictionaryGroup.mConfidence >= CONFIDENCE_THRESHOLD; } @Nullable @@ -624,7 +645,8 @@ public class DictionaryFacilitator { final int timeStampInSeconds, final boolean blockPotentiallyOffensive) { final ExpandableBinaryDictionary userHistoryDictionary = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY); - if (userHistoryDictionary == null) { + if (userHistoryDictionary == null + || !isConfidentAboutCurrentLanguageBeing(userHistoryDictionary.mLocale)) { return; } final int maxFreq = getFrequency(word); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 7b7b6d35e..66746cb6a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -708,6 +708,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.mSuggest.setAutoCorrectionThreshold( settingsValues.mAutoCorrectionThreshold); } + mInputLogic.mSuggest.setPlausibilityThreshold(settingsValues.mPlausibilityThreshold); } /** @@ -1007,6 +1008,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen suggest.setAutoCorrectionThreshold( currentSettingsValues.mAutoCorrectionThreshold); } + suggest.setPlausibilityThreshold(currentSettingsValues.mPlausibilityThreshold); switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 2d0ec42a6..0bf0f687a 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; +import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -64,15 +65,30 @@ public final class Suggest { } private float mAutoCorrectionThreshold; + private float mPlausibilityThreshold; public Suggest(final DictionaryFacilitator dictionaryFacilitator) { mDictionaryFacilitator = dictionaryFacilitator; } + /** + * Set the normalized-score threshold for a suggestion to be considered strong enough that we + * will auto-correct to this. + * @param threshold the threshold + */ public void setAutoCorrectionThreshold(final float threshold) { mAutoCorrectionThreshold = threshold; } + /** + * Set the normalized-score threshold for what we consider a "plausible" suggestion, in + * the same dimension as the auto-correction threshold. + * @param threshold the threshold + */ + public void setPlausibilityThreshold(final float threshold) { + mPlausibilityThreshold = threshold; + } + public interface OnGetSuggestedWordsCallback { public void onGetSuggestedWords(final SuggestedWords suggestedWords); } @@ -117,7 +133,8 @@ public final class Suggest { return suggestionsContainer; } - private static String getWhitelistedWordOrNull(final ArrayList<SuggestedWordInfo> suggestions) { + private static SuggestedWordInfo getWhitelistedWordInfoOrNull( + @Nonnull final ArrayList<SuggestedWordInfo> suggestions) { if (suggestions.isEmpty()) { return null; } @@ -125,9 +142,21 @@ public final class Suggest { if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { return null; } - return firstSuggestedWordInfo.mWord; + return firstSuggestedWordInfo; } + // Quality constants for dictionary match + // In increasing order of quality + // This source dictionary does not match the typed word. + private static final int QUALITY_NO_MATCH = 0; + // This source dictionary has a null locale, and the preferred locale is also null. + private static final int QUALITY_MATCH_NULL = 1; + // This source dictionary has a non-null locale different from the preferred locale. The + // preferred locale may be null : this is still better than MATCH_NULL. + private static final int QUALITY_MATCH_OTHER_LOCALE = 2; + // This source dictionary matches the preferred locale. + private static final int QUALITY_MATCH_PREFERRED_LOCALE = 3; + // Retrieves suggestions for non-batch input (typing, recorrection, predictions...) // and calls the callback function with the suggestions. private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer, @@ -152,11 +181,54 @@ public final class Suggest { // For transforming suggestions that don't come for any dictionary, we // use the currently most probable locale as it's our best bet. mostProbableLocale); - @Nullable final Dictionary sourceDictionaryOfRemovedWord = - SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(wordComposer.getTypedWord(), - mostProbableLocale /* preferredLocale */, suggestionsContainer); - final String whitelistedWord = getWhitelistedWordOrNull(suggestionsContainer); + boolean typedWordExistsInAnotherLanguage = false; + int qualityOfFoundSourceDictionary = QUALITY_NO_MATCH; + @Nullable Dictionary sourceDictionaryOfRemovedWord = null; + for (final SuggestedWordInfo info : suggestionsContainer) { + // Search for the best dictionary, defined as the first one with the highest match + // quality we can find. + if (typedWordString.equals(info.mWord)) { + if (mostProbableLocale.equals(info.mSourceDict.mLocale)) { + if (qualityOfFoundSourceDictionary < QUALITY_MATCH_PREFERRED_LOCALE) { + // Use this source if the old match had lower quality than this match + sourceDictionaryOfRemovedWord = info.mSourceDict; + qualityOfFoundSourceDictionary = QUALITY_MATCH_PREFERRED_LOCALE; + } + } else { + final int matchQuality = (null == info.mSourceDict.mLocale) + ? QUALITY_MATCH_NULL : QUALITY_MATCH_OTHER_LOCALE; + if (qualityOfFoundSourceDictionary < matchQuality) { + // Use this source if the old match had lower quality than this match + sourceDictionaryOfRemovedWord = info.mSourceDict; + qualityOfFoundSourceDictionary = matchQuality; + } + typedWordExistsInAnotherLanguage = true; + } + } + } + + SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer); + + final SuggestedWordInfo whitelistedWordInfo = + getWhitelistedWordInfoOrNull(suggestionsContainer); + final String whitelistedWord; + if (null != whitelistedWordInfo && + (mDictionaryFacilitator.isConfidentAboutCurrentLanguageBeing( + whitelistedWordInfo.mSourceDict.mLocale) + || (!typedWordExistsInAnotherLanguage + && !hasPlausibleCandidateInAnyOtherLanguage(suggestionsContainer, + consideredWord, whitelistedWordInfo)))) { + // We'll use the whitelist candidate if we are confident the user is typing in the + // language of the dictionary it's coming from, or if there is no plausible candidate + // coming from another language. + whitelistedWord = whitelistedWordInfo.mWord; + } else { + // If on the contrary we are not confident in the current language and we have + // at least a plausible candidate in any other language, then we don't use this + // whitelist candidate. + whitelistedWord = null; + } final boolean resultsArePredictions = !wordComposer.isComposingWord(); // We allow auto-correction if we have a whitelisted word, or if the word had more than @@ -198,7 +270,7 @@ public final class Suggest { hasAutoCorrection = false; } else { final SuggestedWordInfo firstSuggestion = suggestionResults.first(); - if (!AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold( + if (!AutoCorrectionUtils.suggestionExceedsThreshold( firstSuggestion, consideredWord, mAutoCorrectionThreshold)) { // Score is too low for autocorrect hasAutoCorrection = false; @@ -247,6 +319,20 @@ public final class Suggest { false /* isObsoleteSuggestions */, inputStyle, sequenceNumber)); } + private boolean hasPlausibleCandidateInAnyOtherLanguage( + final ArrayList<SuggestedWordInfo> suggestionsContainer, final String consideredWord, + final SuggestedWordInfo whitelistedWordInfo) { + for (final SuggestedWordInfo info : suggestionsContainer) { + if (whitelistedWordInfo.mSourceDict.mLocale.equals(info.mSourceDict.mLocale)) { + continue; + } + return AutoCorrectionUtils.suggestionExceedsThreshold(info, consideredWord, + mPlausibilityThreshold); + } + // No candidate in another language + return false; + } + // Retrieves suggestions for the batch input // and calls the callback function with the suggestions. private void getSuggestedWordsForBatchInput(final WordComposer wordComposer, @@ -280,8 +366,7 @@ public final class Suggest { final SuggestedWordInfo rejected = suggestionsContainer.remove(0); suggestionsContainer.add(1, rejected); } - SuggestedWordInfo.removeDupsAndReturnSourceOfTypedWord(null /* typedWord */, - null /* preferredLocale */, suggestionsContainer); + SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer); // For some reason some suggestions with MIN_VALUE are making their way here. // TODO: Find a more robust way to detect distracters. diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index cbf48f0c0..390b311e2 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -356,53 +356,30 @@ public class SuggestedWords { } // This will always remove the higher index if a duplicate is found. - // Returns null if the typed word is not found. Always return the dictionary for the - // highest suggestion matching the locale if found, otherwise return the dictionary for - // the highest suggestion. - @Nullable - public static Dictionary removeDupsAndReturnSourceOfTypedWord( - @Nullable final String typedWord, - @Nullable final Locale preferredLocale, + public static void removeDups(@Nullable final String typedWord, @Nonnull ArrayList<SuggestedWordInfo> candidates) { if (candidates.isEmpty()) { - return null; + return; } - final Dictionary sourceDictionaryOfTypedWord; if (!TextUtils.isEmpty(typedWord)) { - sourceDictionaryOfTypedWord = - removeSuggestedWordInfoFromListAndReturnSourceDictionary(typedWord, - preferredLocale, candidates, -1 /* startIndexExclusive */); - } else { - sourceDictionaryOfTypedWord = null; + removeSuggestedWordInfoFromList(typedWord, candidates, -1 /* startIndexExclusive */); } for (int i = 0; i < candidates.size(); ++i) { - removeSuggestedWordInfoFromListAndReturnSourceDictionary(candidates.get(i).mWord, - null /* preferredLocale */, candidates, i /* startIndexExclusive */); + removeSuggestedWordInfoFromList(candidates.get(i).mWord, candidates, + i /* startIndexExclusive */); } - return sourceDictionaryOfTypedWord; } - @Nullable - private static Dictionary removeSuggestedWordInfoFromListAndReturnSourceDictionary( - @Nonnull final String word, @Nullable final Locale preferredLocale, - @Nonnull final ArrayList<SuggestedWordInfo> candidates, + private static void removeSuggestedWordInfoFromList( + @Nonnull final String word, @Nonnull final ArrayList<SuggestedWordInfo> candidates, final int startIndexExclusive) { - Dictionary sourceDictionaryOfTypedWord = null; for (int i = startIndexExclusive + 1; i < candidates.size(); ++i) { final SuggestedWordInfo previous = candidates.get(i); if (word.equals(previous.mWord)) { - if (null == sourceDictionaryOfTypedWord - || (null != preferredLocale - && preferredLocale.equals(previous.mSourceDict.mLocale))) { - if (Dictionary.TYPE_USER_HISTORY != previous.mSourceDict.mDictType) { - sourceDictionaryOfTypedWord = previous.mSourceDict; - } - } candidates.remove(i); --i; } } - return sourceDictionaryOfTypedWord; } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 16c053474..490fa827c 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -238,6 +238,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return !currentAutoCorrectionSetting.equals(autoCorrectionOff); } + public static float readPlausibilityThreshold(final Resources res) { + return Float.parseFloat(res.getString(R.string.plausibility_threshold)); + } + public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE, diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 26415e7d4..c3755792c 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -96,6 +96,7 @@ public class SettingsValues { public final int mKeyPreviewPopupDismissDelay; private final boolean mAutoCorrectEnabled; public final float mAutoCorrectionThreshold; + public final float mPlausibilityThreshold; public final boolean mAutoCorrectionEnabledPerUserSettings; private final boolean mSuggestionsEnabledPerUserSettings; private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds; @@ -172,6 +173,7 @@ public class SettingsValues { Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true); mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, autoCorrectionThresholdRawValue); + mPlausibilityThreshold = Settings.readPlausibilityThreshold(res); mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java index 120cffbde..2fd257922 100644 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java @@ -29,9 +29,8 @@ public final class AutoCorrectionUtils { // Purely static class: can't instantiate. } - public static boolean suggestionExceedsAutoCorrectionThreshold( - final SuggestedWordInfo suggestion, final String consideredWord, - final float autoCorrectionThreshold) { + public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion, + final String consideredWord, final float threshold) { if (null != suggestion) { // Shortlist a whitelisted word if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { @@ -45,11 +44,11 @@ public final class AutoCorrectionUtils { if (DBG) { Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + "," + autoCorrectionSuggestionScore + ", " + normalizedScore - + "(" + autoCorrectionThreshold + ")"); + + "(" + threshold + ")"); } - if (normalizedScore >= autoCorrectionThreshold) { + if (normalizedScore >= threshold) { if (DBG) { - Log.d(TAG, "Auto corrected by S-threshold."); + Log.d(TAG, "Exceeds threshold."); } return true; } |