diff options
Diffstat (limited to 'java/src')
41 files changed, 792 insertions, 705 deletions
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index e195a5406..55282c583 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -32,8 +32,6 @@ import java.lang.reflect.Field; import java.util.ArrayList; public final class SuggestionSpanUtils { - private static final String TAG = SuggestionSpanUtils.class.getSimpleName(); - // Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1). public static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField( @@ -60,7 +58,7 @@ public final class SuggestionSpanUtils { } final Spannable spannable = new SpannableString(text); final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */, - new String[] {} /* suggestions */, (int)OBJ_FLAG_AUTO_CORRECTION, + new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION, SuggestionSpanPickedNotificationReceiver.class); spannable.setSpan(suggestionSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java index 62b905dc5..1d9b9991e 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java @@ -54,7 +54,6 @@ public final class DictionaryProvider extends ContentProvider { private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt"; private static final String QUERY_PARAMETER_TRUE = "true"; private static final String QUERY_PARAMETER_DELETE_RESULT = "result"; - private static final String QUERY_PARAMETER_SUCCESS = "success"; private static final String QUERY_PARAMETER_FAILURE = "failure"; public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol"; private static final int NO_MATCH = 0; diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java index 939c25f10..41916b614 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -50,8 +50,6 @@ import java.util.concurrent.TimeUnit; * to access, and mark the current state as such. */ public final class DictionaryService extends Service { - private static final String TAG = DictionaryService.class.getName(); - /** * The package name, to use in the intent actions. */ diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java index d8aa33bb8..859f1b35b 100644 --- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java @@ -21,8 +21,6 @@ import android.content.Context; import android.content.Intent; public final class EventHandler extends BroadcastReceiver { - private static final String TAG = EventHandler.class.getName(); - /** * Receives a intent broadcast. * diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index bb1aa4776..8880af48c 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -273,7 +273,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public void startDoubleTapShiftKeyTimer() { final MainKeyboardView keyboardView = getMainKeyboardView(); if (keyboardView != null) { - keyboardView.getTimerProxy().startDoubleTapShiftKeyTimer(); + keyboardView.startDoubleTapShiftKeyTimer(); } } @@ -282,7 +282,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public void cancelDoubleTapShiftKeyTimer() { final MainKeyboardView keyboardView = getMainKeyboardView(); if (keyboardView != null) { - keyboardView.getTimerProxy().cancelDoubleTapShiftKeyTimer(); + keyboardView.cancelDoubleTapShiftKeyTimer(); } } @@ -290,7 +290,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { @Override public boolean isInDoubleTapShiftKeyTimeout() { final MainKeyboardView keyboardView = getMainKeyboardView(); - return keyboardView != null && keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout(); + return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout(); } /** @@ -314,10 +314,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { R.layout.input_view, null); mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); - if (isHardwareAcceleratedDrawingEnabled) { - mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? - } + mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled); mKeyboardView.setKeyboardActionListener(mLatinIME); // This always needs to be set since the accessibility state can diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 254b20b87..054c503d8 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -154,6 +154,12 @@ public class KeyboardView extends View { Color.red(color), Color.green(color), Color.blue(color)); } + public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { + if (!enabled) return; + // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? + setLayerType(LAYER_TYPE_HARDWARE, null); + } + /** * Attaches a keyboard to this view. The keyboard can be switched at any time and the * view will re-layout itself to accommodate the keyboard. @@ -605,4 +611,8 @@ public class KeyboardView extends View { super.onDetachedFromWindow(); freeOffscreenBuffer(); } + + public void deallocateMemory() { + freeOffscreenBuffer(); + } } diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 6aa43b994..6782317d0 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -53,16 +53,15 @@ import com.android.inputmethod.keyboard.internal.GestureFloatingPreviewText; import com.android.inputmethod.keyboard.internal.GestureTrailsPreview; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams; +import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; -import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.DebugSettings; -import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; @@ -116,13 +115,9 @@ import java.util.WeakHashMap; * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration */ public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, - PointerTracker.DrawingProxy, MoreKeysPanel.Controller, - TouchScreenRegulator.ProcessMotionEvent { + PointerTracker.DrawingProxy, MoreKeysPanel.Controller { private static final String TAG = MainKeyboardView.class.getSimpleName(); - // TODO: Kill process when the usability study mode was changed. - private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; - /** Listener for {@link KeyboardActionListener}. */ private KeyboardActionListener mKeyboardActionListener; @@ -184,12 +179,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // TODO: Make this parameter customizable by user via settings. private int mGestureFloatingPreviewTextLingerTimeout; - private final TouchScreenRegulator mTouchScreenRegulator; - private KeyDetector mKeyDetector; - private final boolean mHasDistinctMultitouch; - private int mOldPointerCount = 1; - private Key mOldKey; + private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper; private final KeyTimerHandler mKeyTimerHandler; @@ -201,9 +192,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3; private static final int MSG_UPDATE_BATCH_INPUT = 4; - private final int mKeyRepeatStartTimeout; - private final int mKeyRepeatInterval; - private final int mLongPressShiftLockTimeout; private final int mIgnoreAltCodeKeyTimeout; private final int mGestureRecognitionUpdateTime; @@ -211,12 +199,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final TypedArray mainKeyboardViewAttr) { super(outerInstance); - mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); - mKeyRepeatInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_keyRepeatInterval, 0); - mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt( @@ -235,17 +217,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack startWhileTypingFadeinAnimation(keyboardView); break; case MSG_REPEAT_KEY: - final Key currentKey = tracker.getKey(); - final int code = msg.arg1; - if (currentKey != null && currentKey.mCode == code) { - startKeyRepeatTimer(tracker, mKeyRepeatInterval); - startTypingStateTimer(currentKey); - final KeyboardActionListener listener = - keyboardView.getKeyboardActionListener(); - listener.onPressKey(code, true /* isRepeatKey */, true /* isSinglePointer */); - listener.onCodeInput(code, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } + tracker.onKeyRepeat(msg.arg1); break; case MSG_LONGPRESS_KEY: keyboardView.onLongPress(tracker); @@ -257,19 +229,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } } - private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { + @Override + public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) { final Key key = tracker.getKey(); - if (key == null) { + if (key == null || delay == 0) { return; } sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); } - @Override - public void startKeyRepeatTimer(final PointerTracker tracker) { - startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout); - } - public void cancelKeyRepeatTimer() { removeMessages(MSG_REPEAT_KEY); } @@ -280,31 +248,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } @Override - public void startLongPressTimer(final PointerTracker tracker) { + public void startLongPressTimer(final PointerTracker tracker, final int delay) { cancelLongPressTimer(); - if (tracker == null) { - return; - } - final Key key = tracker.getKey(); - final int delay; - switch (key.mCode) { - case Constants.CODE_SHIFT: - delay = mLongPressShiftLockTimeout; - break; - default: - final int longpressTimeout = - Settings.getInstance().getCurrent().mKeyLongpressTimeout; - if (tracker.isInSlidingKeyInputFromModifier()) { - // We use longer timeout for sliding finger input started from the modifier key. - delay = longpressTimeout * 3; - } else { - delay = longpressTimeout; - } - break; - } - if (delay > 0) { - sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); - } + if (delay <= 0) return; + sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay); } @Override @@ -475,15 +422,16 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - mTouchScreenRegulator = new TouchScreenRegulator(context, this); - + PointerTracker.init(getResources()); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final boolean forceNonDistinctMultitouch = prefs.getBoolean( DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false); final boolean hasDistinctMultitouch = context.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); - mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; - PointerTracker.init(getResources()); + .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT) + && !forceNonDistinctMultitouch; + mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null + : new NonDistinctMultitouchHelper(); + mPreviewPlacerView = new PreviewPlacerView(context, attrs); final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes( @@ -564,6 +512,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; } + @Override + public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { + super.setHardwareAcceleratedDrawingEnabled(enabled); + mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled); + } + private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { if (resId == 0) { // TODO: Stop returning null. @@ -653,7 +607,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); PointerTracker.setKeyDetector(mKeyDetector); - mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth); mMoreKeysKeyboardCache.clear(); mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); @@ -901,9 +854,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } @Override - public void showGestureTrail(final PointerTracker tracker) { + public void showGestureTrail(final PointerTracker tracker, + final boolean showsFloatingPreviewText) { locatePreviewPlacerView(); - mGestureFloatingPreviewText.setPreviewPosition(tracker); + if (showsFloatingPreviewText) { + mGestureFloatingPreviewText.setPreviewPosition(tracker); + } mGestureTrailsPreview.setPreviewPosition(tracker); } @@ -1059,6 +1015,18 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } } + public void startDoubleTapShiftKeyTimer() { + mKeyTimerHandler.startDoubleTapShiftKeyTimer(); + } + + public void cancelDoubleTapShiftKeyTimer() { + mKeyTimerHandler.cancelDoubleTapShiftKeyTimer(); + } + + public boolean isInDoubleTapShiftKeyTimeout() { + return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout(); + } + @Override public boolean dispatchTouchEvent(MotionEvent event) { if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { @@ -1072,140 +1040,34 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack if (getKeyboard() == null) { return false; } - return mTouchScreenRegulator.onTouchEvent(me); - } - - @Override - public boolean processMotionEvent(final MotionEvent me) { - final boolean nonDistinctMultitouch = !mHasDistinctMultitouch; - final int action = me.getActionMasked(); - final int pointerCount = me.getPointerCount(); - final int oldPointerCount = mOldPointerCount; - mOldPointerCount = pointerCount; - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // If the device does not have distinct multi-touch support panel, ignore all multi-touch - // events except a transition from/to single-touch. - if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) { + if (mNonDistinctMultitouchHelper != null) { + if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) { + // Key repeating timer will be canceled if 2 or more keys are in action. + mKeyTimerHandler.cancelKeyRepeatTimer(); + } + // Non distinct multitouch screen support + mNonDistinctMultitouchHelper.processMotionEvent(me, this); return true; } + return processMotionEvent(me); + } - final long eventTime = me.getEventTime(); - final int index = me.getActionIndex(); - final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - - // TODO: This might be moved to the tracker.processMotionEvent() call below. - if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { - writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); + public boolean processMotionEvent(final MotionEvent me) { + if (LatinImeLogger.sUsabilityStudy) { + UsabilityStudyLogUtils.writeMotionEvent(me); } - // TODO: This should be moved to the tracker.processMotionEvent() call below. // Currently the same "move" event is being logged twice. if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.mainKeyboardView_processMotionEvent( - me, action, eventTime, index, id, x, y); - } - - if (mKeyTimerHandler.isInKeyRepeat()) { - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - // Key repeating timer will be canceled if 2 or more keys are in action, and current - // event (UP or DOWN) is non-modifier key. - if (pointerCount > 1 && !tracker.isModifier()) { - mKeyTimerHandler.cancelKeyRepeatTimer(); - } - // Up event will pass through. - } - - // TODO: cleanup this code into a multi-touch to single-touch event converter class? - // Translate mutli-touch event to single-touch events on the device that has no distinct - // multi-touch panel. - if (nonDistinctMultitouch) { - // Use only main (id=0) pointer tracker. - final PointerTracker tracker = PointerTracker.getPointerTracker(0, this); - if (pointerCount == 1 && oldPointerCount == 2) { - // Multi-touch to single touch transition. - // Send a down event for the latest pointer if the key is different from the - // previous key. - final Key newKey = tracker.getKeyOn(x, y); - if (mOldKey != newKey) { - tracker.onDownEvent(x, y, eventTime, this); - if (action == MotionEvent.ACTION_UP) { - tracker.onUpEvent(x, y, eventTime); - } - } - } else if (pointerCount == 2 && oldPointerCount == 1) { - // Single-touch to multi-touch transition. - // Send an up event for the last pointer. - final int[] lastCoords = CoordinateUtils.newInstance(); - mOldKey = tracker.getKeyOn( - CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords)); - tracker.onUpEvent( - CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime); - } else if (pointerCount == 1 && oldPointerCount == 1) { - tracker.processMotionEvent(action, x, y, eventTime, this); - } else { - Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount - + " (old " + oldPointerCount + ")"); - } - return true; - } - - if (action == MotionEvent.ACTION_MOVE) { - for (int i = 0; i < pointerCount; i++) { - final int pointerId = me.getPointerId(i); - final PointerTracker tracker = PointerTracker.getPointerTracker( - pointerId, this); - final int px = (int)me.getX(i); - final int py = (int)me.getY(i); - tracker.onMoveEvent(px, py, eventTime, me); - if (ENABLE_USABILITY_STUDY_LOG) { - writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); - } - // TODO: This seems to be no longer necessary, and confusing because it leads to - // duplicate MotionEvents being recorded. - // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - // ResearchLogger.mainKeyboardView_processMotionEvent( - // me, action, eventTime, i, pointerId, px, py); - // } - } - } else { - final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); - tracker.processMotionEvent(action, x, y, eventTime, this); + ResearchLogger.mainKeyboardView_processMotionEvent(me); } + final int index = me.getActionIndex(); + final int id = me.getPointerId(index); + final PointerTracker tracker = PointerTracker.getPointerTracker(id, this); + tracker.processMotionEvent(me, this); return true; } - private static void writeUsabilityStudyLog(final MotionEvent me, final int action, - final long eventTime, final int index, final int id, final int x, final int y) { - final String eventTag; - switch (action) { - case MotionEvent.ACTION_UP: - eventTag = "[Up]"; - break; - case MotionEvent.ACTION_DOWN: - eventTag = "[Down]"; - break; - case MotionEvent.ACTION_POINTER_UP: - eventTag = "[PointerUp]"; - break; - case MotionEvent.ACTION_POINTER_DOWN: - eventTag = "[PointerDown]"; - break; - case MotionEvent.ACTION_MOVE: - eventTag = "[Move]"; - break; - default: - eventTag = "[Action" + action + "]"; - break; - } - final float size = me.getSize(index); - final float pressure = me.getPressure(index); - UsabilityStudyLogUtils.getInstance().write( - eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); - } - public void cancelAllOngoingEvents() { mKeyTimerHandler.cancelAllMessages(); mDrawingHandler.cancelAllMessages(); @@ -1398,4 +1260,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight); } } + + @Override + public void deallocateMemory() { + super.deallocateMemory(); + mGestureTrailsPreview.deallocateMemory(); + } } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index c7b096430..ab5fee99d 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -34,6 +34,7 @@ import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.ResourceUtils; @@ -87,14 +88,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public void dismissKeyPreview(PointerTracker tracker); public void showSlidingKeyInputPreview(PointerTracker tracker); public void dismissSlidingKeyInputPreview(); - public void showGestureTrail(PointerTracker tracker); + public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText); } public interface TimerProxy { public void startTypingStateTimer(Key typedKey); public boolean isTypingState(); - public void startKeyRepeatTimer(PointerTracker tracker); - public void startLongPressTimer(PointerTracker tracker); + public void startKeyRepeatTimer(PointerTracker tracker, int delay); + public void startLongPressTimer(PointerTracker tracker, int delay); public void cancelLongPressTimer(); public void startDoubleTapShiftKeyTimer(); public void cancelDoubleTapShiftKeyTimer(); @@ -110,9 +111,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { @Override public boolean isTypingState() { return false; } @Override - public void startKeyRepeatTimer(PointerTracker tracker) {} + public void startKeyRepeatTimer(PointerTracker tracker, int delay) {} @Override - public void startLongPressTimer(PointerTracker tracker) {} + public void startLongPressTimer(PointerTracker tracker, int delay) {} @Override public void cancelLongPressTimer() {} @Override @@ -137,6 +138,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public final int mTouchNoiseThresholdTime; public final int mTouchNoiseThresholdDistance; public final int mSuppressKeyPreviewAfterBatchInputDuration; + public final int mKeyRepeatStartTimeout; + public final int mKeyRepeatInterval; + public final int mLongPressShiftLockTimeout; public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); @@ -145,6 +149,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mTouchNoiseThresholdTime = 0; mTouchNoiseThresholdDistance = 0; mSuppressKeyPreviewAfterBatchInputDuration = 0; + mKeyRepeatStartTimeout = 0; + mKeyRepeatInterval = 0; + mLongPressShiftLockTimeout = 0; } public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) { @@ -156,6 +163,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element { R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0); + mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); + mKeyRepeatInterval = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatInterval, 0); + mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0); } } @@ -327,6 +340,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // the more keys panel currently being shown. equals null if no panel is active. private MoreKeysPanel mMoreKeysPanel; + private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3; // true if this pointer is in a sliding key input. boolean mIsInSlidingKeyInput; // true if this pointer is in a sliding key input from a modifier key, @@ -464,6 +478,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mPointerId = id; mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( id, sGestureStrokeParams, sGesturePreviewParams); + setKeyEventHandler(handler); + } + + private void setKeyEventHandler(final KeyEventHandler handler) { setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -471,7 +489,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) { + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, + final boolean isRepeatKey) { // While gesture input is going on, this method should be a no-operation. But when gesture // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code> // are set to false. To keep this method is a no-operation, @@ -481,17 +500,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier(); if (DEBUG_LISTENER) { - Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId, + Log.d(TAG, String.format("[%d] onPress : %s%s%s%s", mPointerId, KeyDetector.printableCode(key), ignoreModifierKey ? " ignoreModifier" : "", - key.isEnabled() ? "" : " disabled")); + key.isEnabled() ? "" : " disabled", + isRepeatKey ? " repeat" : "")); } if (ignoreModifierKey) { return false; } if (key.isEnabled()) { - mListener.onPressKey(key.mCode, false /* isRepeatKey */, - getActivePointerTrackerCount() == 1); + mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; mTimerProxy.startTypingStateTimer(key); @@ -602,10 +621,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return mIsInSlidingKeyInput; } - public boolean isInSlidingKeyInputFromModifier() { - return mIsInSlidingKeyInputFromModifier; - } - public Key getKey() { return mCurrentKey; } @@ -753,7 +768,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return sPointerTrackerQueue.size(); } - public boolean isOldestTrackerInQueue() { + private boolean isOldestTrackerInQueue() { return sPointerTrackerQueue.getOldestElement() == this; } @@ -776,7 +791,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { dismissAllMoreKeysPanels(); } mTimerProxy.cancelLongPressTimer(); - mDrawingProxy.showGestureTrail(this); + // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. + mDrawingProxy.showGestureTrail( + this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); } public void updateBatchInputByTimer(final long eventTime) { @@ -792,7 +809,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - mDrawingProxy.showGestureTrail(this); + // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. + mDrawingProxy.showGestureTrail( + this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); } private void updateBatchInput(final long eventTime) { @@ -833,7 +852,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - mDrawingProxy.showGestureTrail(this); + // A gesture floating preview text will be shown at the oldest pointer/finger on the screen. + mDrawingProxy.showGestureTrail( + this, isOldestTrackerInQueue() /* showsFloatingPreviewText */); } private void cancelBatchInput() { @@ -849,8 +870,23 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mListener.onCancelBatchInput(); } - public void processMotionEvent(final int action, final int x, final int y, final long eventTime, - final KeyEventHandler handler) { + public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) { + final int action = me.getActionMasked(); + final long eventTime = me.getEventTime(); + if (action == MotionEvent.ACTION_MOVE) { + final int pointerCount = me.getPointerCount(); + for (int index = 0; index < pointerCount; index++) { + final int id = me.getPointerId(index); + final PointerTracker tracker = getPointerTracker(id, handler); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + tracker.onMoveEvent(x, y, eventTime, me); + } + return; + } + final int index = me.getActionIndex(); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: @@ -860,24 +896,18 @@ public final class PointerTracker implements PointerTrackerQueue.Element { case MotionEvent.ACTION_POINTER_UP: onUpEvent(x, y, eventTime); break; - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime, null); - break; case MotionEvent.ACTION_CANCEL: onCancelEvent(x, y, eventTime); break; } } - public void onDownEvent(final int x, final int y, final long eventTime, + private void onDownEvent(final int x, final int y, final long eventTime, final KeyEventHandler handler) { if (DEBUG_EVENT) { printTouchEvent("onDownEvent:", x, y, eventTime); } - mDrawingProxy = handler.getDrawingProxy(); - mTimerProxy = handler.getTimerProxy(); - setKeyboardActionListener(handler.getKeyboardActionListener()); - setKeyDetectorInner(handler.getKeyDetector()); + setKeyEventHandler(handler); // Naive up-to-down noise filter. final long deltaT = eventTime - mUpTime; if (deltaT < sParams.mTouchNoiseThresholdTime) { @@ -909,7 +939,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } // A gesture should start only from a non-modifier key. mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() - && key != null && !key.isModifier(); + && key != null && !key.isModifier() && !key.isRepeatable(); if (mIsDetectingGesture) { if (getActivePointerTrackerCount() == 1) { sGestureFirstDownTime = eventTime; @@ -937,7 +967,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update key according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { key = onDownKey(x, y, eventTime); } @@ -987,7 +1017,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } - public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { + private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { if (DEBUG_MOVE_EVENT) { printTouchEvent("onMoveEvent:", x, y, eventTime); } @@ -1013,7 +1043,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final int translatedY = mMoreKeysPanel.translateY(y); mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime); onMoveKey(x, y); - mDrawingProxy.showSlidingKeyInputPreview(this); + if (mIsInSlidingKeyInputFromModifier) { + mDrawingProxy.showSlidingKeyInputPreview(this); + } return; } onMoveEventInternal(x, y, eventTime); @@ -1025,7 +1057,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // at {@link #setKeyboard}. In those cases, we should update key according // to the new keyboard layout. Key key = newKey; - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) { key = onMoveKey(x, y); } onMoveToNewKey(key, x, y); @@ -1168,10 +1200,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element { slideOutFromOldKey(oldKey, x, y); } } - mDrawingProxy.showSlidingKeyInputPreview(this); + if (mIsInSlidingKeyInputFromModifier) { + mDrawingProxy.showSlidingKeyInputPreview(this); + } } - public void onUpEvent(final int x, final int y, final long eventTime) { + private void onUpEvent(final int x, final int y, final long eventTime) { if (DEBUG_EVENT) { printTouchEvent("onUpEvent :", x, y, eventTime); } @@ -1271,7 +1305,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sPointerTrackerQueue.remove(this); } - public void onCancelEvent(final int x, final int y, final long eventTime) { + private void onCancelEvent(final int x, final int y, final long eventTime) { if (DEBUG_EVENT) { printTouchEvent("onCancelEvt:", x, y, eventTime); } @@ -1292,16 +1326,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } - private void startRepeatKey(final Key key) { - if (sInGesture) return; - if (key == null) return; - if (!key.isRepeatable()) return; - // Don't start key repeat when we are in sliding input mode. - if (mIsInSlidingKeyInput) return; - detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); - mTimerProxy.startKeyRepeatTimer(this); - } - private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, final Key newKey) { if (mKeyDetector == null) { @@ -1353,7 +1377,22 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // We always need to start the long press timer if the key has its more keys regardless of // whether or not we are in the sliding input mode. if (mIsInSlidingKeyInput && key.mMoreKeys == null) return; - mTimerProxy.startLongPressTimer(this); + final int delay; + switch (key.mCode) { + case Constants.CODE_SHIFT: + delay = sParams.mLongPressShiftLockTimeout; + break; + default: + final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout; + if (mIsInSlidingKeyInputFromModifier) { + // We use longer timeout for sliding finger input started from the modifier key. + delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT; + } else { + delay = longpressTimeout; + } + break; + } + mTimerProxy.startLongPressTimer(this, delay); } private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { @@ -1367,6 +1406,26 @@ public final class PointerTracker implements PointerTrackerQueue.Element { callListenerOnRelease(key, code, false /* withSliding */); } + private void startRepeatKey(final Key key) { + if (sInGesture) return; + if (key == null) return; + if (!key.isRepeatable()) return; + // Don't start key repeat when we are in sliding input mode. + if (mIsInSlidingKeyInput) return; + detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis()); + mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout); + } + + public void onKeyRepeat(final int code) { + final Key key = getKey(); + if (key == null || key.mCode != code) { + return; + } + mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval); + callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */); + callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis()); + } + private void printTouchEvent(final String title, final int x, final int y, final long eventTime) { final Key key = mKeyDetector.detectHitKey(x, y); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java index 9bfddba42..c6dd9e100 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java @@ -115,9 +115,7 @@ public class GestureFloatingPreviewText extends AbstractDrawingPreview { @Override public void setPreviewPosition(final PointerTracker tracker) { - final boolean needsToUpdateLastPointer = - tracker.isOldestTrackerInQueue() && isPreviewEnabled(); - if (!needsToUpdateLastPointer) { + if (!isPreviewEnabled()) { return; } tracker.getLastCoordinates(mLastPointerCoords); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java index dff5177ce..19e995548 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java @@ -104,7 +104,13 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { freeOffscreenBuffer(); } + public void deallocateMemory() { + freeOffscreenBuffer(); + } + private void freeOffscreenBuffer() { + mOffscreenCanvas.setBitmap(null); + mOffscreenCanvas.setMatrix(null); if (mOffscreenBuffer != null) { mOffscreenBuffer.recycle(); mOffscreenBuffer = null; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 8ead44c31..164910dd4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -29,8 +29,8 @@ import com.android.inputmethod.latin.utils.RecapitalizeStatus; * * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()}, * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)}, - * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()}, - * {@link #onUpdateShiftState(int,int)}. + * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, + * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}. * * The actions are {@link SwitchActions}'s methods. */ diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 1594df75b..7bb7442f3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -374,8 +374,7 @@ public final class KeyboardTextsSet { /* 115 */ "w", /* 116 */ "y", /* 117 */ "x", - // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* 118 */ "\u00F1", + /* 118 */ EMPTY, /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm", /* 120 */ "!icon/settings_key|!code/key_settings", /* 121 */ "!icon/shortcut_key|!code/key_shortcut", @@ -625,7 +624,8 @@ public final class KeyboardTextsSet { /* Language az: Azerbaijani */ private static final String[] LANGUAGE_az = { - /* 0 */ null, + // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX + /* 0 */ "\u00E2", // U+0259: "ə" LATIN SMALL LETTER SCHWA /* 1 */ "\u0259", // U+0131: "ı" LATIN SMALL LETTER DOTLESS I @@ -776,9 +776,28 @@ public final class KeyboardTextsSet { /* 8~ */ null, null, null, null, null, null, /* ~13 */ - // U+0140: "ŀ" LATIN SMALL LETTER L WITH MIDDLE DOT + // U+00B7: "·" MIDDLE DOT // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE - /* 14 */ "\u0140,\u0142", + /* 14 */ "l\u00B7l,\u0142", + /* 15~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + /* ~52 */ + // U+00B7: "·" MIDDLE DOT + /* 53 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)", + /* 54~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, + /* ~107 */ + /* 108 */ "?,\u00B7", + /* 109~ */ + null, null, null, null, null, null, null, null, null, + /* ~117 */ + // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA + /* 118 */ "\u00E7", }; /* Language cs: Czech */ @@ -1253,6 +1272,11 @@ public final class KeyboardTextsSet { /* 109 */ "\"", /* 110 */ "\'", /* 111 */ "\'", + /* 112~ */ + null, null, null, null, null, null, + /* ~117 */ + // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE + /* 118 */ "\u00F1", }; /* Language et: Estonian */ diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java index 4916a15b5..c1f374964 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java @@ -30,7 +30,6 @@ import java.util.Arrays; public class MatrixUtils { private static final String TAG = MatrixUtils.class.getSimpleName(); public static class MatrixOperationFailedException extends Exception { - private static final String TAG = MatrixOperationFailedException.class.getSimpleName(); private static final long serialVersionUID = 4384485606788583829L; public MatrixOperationFailedException(String msg) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java new file mode 100644 index 000000000..a0935b985 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 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.keyboard.internal; + +import android.util.Log; +import android.view.MotionEvent; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler; +import com.android.inputmethod.latin.utils.CoordinateUtils; + +public final class NonDistinctMultitouchHelper { + private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName(); + + private int mOldPointerCount = 1; + private Key mOldKey; + private int[] mLastCoords = CoordinateUtils.newInstance(); + + public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) { + final int pointerCount = me.getPointerCount(); + final int oldPointerCount = mOldPointerCount; + mOldPointerCount = pointerCount; + // Ignore continuous multi-touch events because we can't trust the coordinates + // in multi-touch events. + if (pointerCount > 1 && oldPointerCount > 1) { + return; + } + + // Use only main (id=0) pointer tracker. + final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler); + final int action = me.getActionMasked(); + final int index = me.getActionIndex(); + final long eventTime = me.getEventTime(); + final long downTime = me.getDownTime(); + + // In single-touch. + if (oldPointerCount == 1 && pointerCount == 1) { + if (me.getPointerId(index) == mainTracker.mPointerId) { + mainTracker.processMotionEvent(me, keyEventHandler); + return; + } + // Inject a copied event. + injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime, + mainTracker, keyEventHandler); + return; + } + + // Single-touch to multi-touch transition. + if (oldPointerCount == 1 && pointerCount == 2) { + // Send an up event for the last pointer, be cause we can't trust the coordinates of + // this multi-touch event. + mainTracker.getLastCoordinates(mLastCoords); + final int x = CoordinateUtils.x(mLastCoords); + final int y = CoordinateUtils.y(mLastCoords); + mOldKey = mainTracker.getKeyOn(x, y); + // Inject an artifact up event for the old key. + injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime, + mainTracker, keyEventHandler); + return; + } + + // Multi-touch to single-touch transition. + if (oldPointerCount == 2 && pointerCount == 1) { + // Send a down event for the latest pointer if the key is different from the previous + // key. + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + final Key newKey = mainTracker.getKeyOn(x, y); + if (mOldKey != newKey) { + // Inject an artifact down event for the new key. + // An artifact up event for the new key will usually be injected as a single-touch. + injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime, + mainTracker, keyEventHandler); + if (action == MotionEvent.ACTION_UP) { + // Inject an artifact up event for the new key also. + injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime, + mainTracker, keyEventHandler); + } + } + return; + } + + Log.w(TAG, "Unknown touch panel behavior: pointer count is " + + pointerCount + " (previously " + oldPointerCount + ")"); + } + + private static void injectMotionEvent(final int action, final float x, final float y, + final long downTime, final long eventTime, final PointerTracker tracker, + final KeyEventHandler handler) { + final MotionEvent me = MotionEvent.obtain( + downTime, eventTime, action, x, y, 0 /* metaState */); + try { + tracker.processMotionEvent(me, handler); + } finally { + me.recycle(); + } + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 3388c5701..4c8607da8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -37,7 +37,10 @@ public final class PreviewPlacerView extends RelativeLayout { public PreviewPlacerView(final Context context, final AttributeSet attrs) { super(context, attrs); setWillNotDraw(false); + } + public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { + if (!enabled) return; final Paint layerPaint = new Paint(); layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); setLayerType(LAYER_TYPE_HARDWARE, layerPaint); diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java index 5c9d36702..2787ebfb9 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java @@ -32,7 +32,7 @@ import com.android.inputmethod.latin.utils.CoordinateUtils; public final class SlidingKeyInputPreview extends AbstractDrawingPreview { private final float mPreviewBodyRadius; - private boolean mShowSlidingKeyInputPreview; + private boolean mShowsSlidingKeyInputPreview; private final int[] mPreviewFrom = CoordinateUtils.newInstance(); private final int[] mPreviewTo = CoordinateUtils.newInstance(); @@ -62,7 +62,7 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview { } public void dismissSlidingKeyInputPreview() { - mShowSlidingKeyInputPreview = false; + mShowsSlidingKeyInputPreview = false; getDrawingView().invalidate(); } @@ -72,7 +72,7 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview { */ @Override public void drawPreview(final Canvas canvas) { - if (!isPreviewEnabled() || !mShowSlidingKeyInputPreview) { + if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) { return; } @@ -90,13 +90,9 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview { */ @Override public void setPreviewPosition(final PointerTracker tracker) { - if (!tracker.isInSlidingKeyInputFromModifier()) { - mShowSlidingKeyInputPreview = false; - return; - } tracker.getDownCoordinates(mPreviewFrom); tracker.getLastCoordinates(mPreviewTo); - mShowSlidingKeyInputPreview = true; + mShowsSlidingKeyInputPreview = true; getDrawingView().invalidate(); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java index e5665bcdd..10847f62d 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java +++ b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java @@ -62,7 +62,7 @@ public class SmoothingUtils { for (int j = 0; j < COEFF_COUNT; ++j) { final int pow = i + j; for (int k = 0; k < N; ++k) { - m0[i][j] += (float) Math.pow((double) xs[k], pow); + m0[i][j] += (float) Math.pow(xs[k], pow); } } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java deleted file mode 100644 index fddd9856e..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.keyboard.internal; - -import android.content.Context; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.research.ResearchLogger; - -public final class TouchScreenRegulator { - private static final String TAG = TouchScreenRegulator.class.getSimpleName(); - private static boolean DEBUG_MODE = LatinImeLogger.sDBG; - - public interface ProcessMotionEvent { - public boolean processMotionEvent(MotionEvent me); - } - - private final ProcessMotionEvent mView; - private final boolean mNeedsSuddenJumpingHack; - - /** Whether we've started dropping move events because we found a big jump */ - private boolean mDroppingEvents; - /** - * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has - * occured - */ - private boolean mDisableDisambiguation; - /** The distance threshold at which we start treating the touch session as a multi-touch */ - private int mJumpThresholdSquare = Integer.MAX_VALUE; - private int mLastX; - private int mLastY; - // One-seventh of the keyboard width seems like a reasonable threshold - private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f; - - public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) { - mView = view; - mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue( - context.getResources(), R.array.sudden_jumping_touch_event_device_list)); - } - - public void setKeyboardGeometry(final int keyboardWidth) { - final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH; - mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold); - } - - /** - * This function checks to see if we need to handle any sudden jumps in the pointer location - * that could be due to a multi-touch being treated as a move by the firmware or hardware. - * Once a sudden jump is detected, all subsequent move events are discarded - * until an UP is received.<P> - * When a sudden jump is detected, an UP event is simulated at the last position and when - * the sudden moves subside, a DOWN event is simulated for the second key. - * @param me the motion event - * @return true if the event was consumed, so that it doesn't continue to be handled by - * {@link MainKeyboardView}. - */ - private boolean handleSuddenJumping(final MotionEvent me) { - if (!mNeedsSuddenJumpingHack) - return false; - final int action = me.getAction(); - final int x = (int) me.getX(); - final int y = (int) me.getY(); - boolean result = false; - - // Real multi-touch event? Stop looking for sudden jumps - if (me.getPointerCount() > 1) { - mDisableDisambiguation = true; - } - if (mDisableDisambiguation) { - // If UP, reset the multi-touch flag - if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false; - return false; - } - - switch (action) { - case MotionEvent.ACTION_DOWN: - // Reset the "session" - mDroppingEvents = false; - mDisableDisambiguation = false; - break; - case MotionEvent.ACTION_MOVE: - // Is this a big jump? - final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y); - // Check the distance. - if (distanceSquare > mJumpThresholdSquare) { - // If we're not yet dropping events, start dropping and send an UP event - if (!mDroppingEvents) { - mDroppingEvents = true; - // Send an up event - MotionEvent translated = MotionEvent.obtain( - me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_UP, - mLastX, mLastY, me.getMetaState()); - mView.processMotionEvent(translated); - translated.recycle(); - } - result = true; - } else if (mDroppingEvents) { - // If moves are small and we're already dropping events, continue dropping - result = true; - } - break; - case MotionEvent.ACTION_UP: - if (mDroppingEvents) { - // Send a down event first, as we dropped a bunch of sudden jumps and assume that - // the user is releasing the touch on the second key. - MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(), - MotionEvent.ACTION_DOWN, - x, y, me.getMetaState()); - mView.processMotionEvent(translated); - translated.recycle(); - mDroppingEvents = false; - // Let the up event get processed as well, result = false - } - break; - } - // Track the previous coordinate - mLastX = x; - mLastY = y; - return result; - } - - public boolean onTouchEvent(final MotionEvent me) { - // If there was a sudden jump, return without processing the actual motion event. - if (handleSuddenJumping(me)) { - if (DEBUG_MODE) - Log.w(TAG, "onTouchEvent: ignore sudden jump " + me); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.suddenJumpingTouchEventHandler_onTouchEvent(me); - } - return true; - } - return mView.processMotionEvent(me); - } -} diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 31a892e19..fa301b5a6 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -18,7 +18,6 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.res.AssetFileDescriptor; import android.util.Log; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 9366abd73..719c7c81f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -76,7 +76,8 @@ import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper; -import com.android.inputmethod.latin.personalization.UserHistoryDictionary; +import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary; +import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsActivity; import com.android.inputmethod.latin.settings.SettingsValues; @@ -169,7 +170,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean mIsMainDictionaryAvailable; private UserBinaryDictionary mUserDictionary; - private UserHistoryDictionary mUserHistoryDictionary; + private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary; + private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary; private boolean mIsUserDictionaryAvailable; private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; @@ -539,34 +541,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final String localeStr = subtypeLocale.toString(); - final ContactsBinaryDictionary oldContactsDictionary; - if (mSuggest != null) { - oldContactsDictionary = mSuggest.getContactsDictionary(); - mSuggest.close(); - } else { - oldContactsDictionary = null; - } - mSuggest = new Suggest(this /* Context */, subtypeLocale, + final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale, this /* SuggestInitializationListener */); - if (mSettings.getCurrent().mCorrectionEnabled) { - mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold); + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.mCorrectionEnabled) { + newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold); } mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().initSuggest(mSuggest); + ResearchLogger.getInstance().initSuggest(newSuggest); } mUserDictionary = new UserBinaryDictionary(this, localeStr); mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); - mSuggest.setUserDictionary(mUserDictionary); - - resetContactsDictionary(oldContactsDictionary); + newSuggest.setUserDictionary(mUserDictionary); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - mUserHistoryDictionary = - PersonalizationDictionaryHelper.getUserHistoryDictionary(this, localeStr, prefs); - mSuggest.setUserHistoryDictionary(mUserHistoryDictionary); + + mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper + .getUserHistoryPredictionDictionary(this, localeStr, prefs); + newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary); + mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper + .getPersonalizationPredictionDictionary(this, localeStr, prefs); + newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary); + + final Suggest oldSuggest = mSuggest; + resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null); + mSuggest = newSuggest; + if (oldSuggest != null) oldSuggest.close(); } /** @@ -578,8 +581,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @param oldContactsDictionary an optional dictionary to use, or null */ private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) { + final Suggest suggest = mSuggest; final boolean shouldSetDictionary = - (null != mSuggest && mSettings.getCurrent().mUseContactsDict); + (null != suggest && mSettings.getCurrent().mUseContactsDict); final ContactsBinaryDictionary dictionaryToUse; if (!shouldSetDictionary) { @@ -606,8 +610,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - if (null != mSuggest) { - mSuggest.setContactsDictionary(dictionaryToUse); + if (null != suggest) { + suggest.setContactsDictionary(dictionaryToUse); } } @@ -619,8 +623,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onDestroy() { - if (mSuggest != null) { - mSuggest.close(); + final Suggest suggest = mSuggest; + if (suggest != null) { + suggest.close(); mSuggest = null; } mSettings.onDestroy(); @@ -714,7 +719,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onStartInputView(editorInfo, restarting); final KeyboardSwitcher switcher = mKeyboardSwitcher; final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); - final SettingsValues currentSettingsValues = mSettings.getCurrent(); + // If we are starting input in a different text field from before, we'll have to reload + // settings, so currentSettingsValues can't be final. + SettingsValues currentSettingsValues = mSettings.getCurrent(); if (editorInfo == null) { Log.e(TAG, "Null EditorInfo in onStartInputView()"); @@ -792,7 +799,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) { + final Suggest suggest = mSuggest; + if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { initSuggest(); } if (mSuggestionStripView != null) { @@ -808,9 +816,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isDifferentTextField) { mainKeyboardView.closing(); loadSettings(); - // TODO: Need to update currentSettingsValues after loadSettings() - if (mSuggest != null && currentSettingsValues.mCorrectionEnabled) { - mSuggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); + currentSettingsValues = mSettings.getCurrent(); + + if (suggest != null && currentSettingsValues.mCorrectionEnabled) { + suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); } switcher.loadKeyboard(editorInfo, currentSettingsValues); @@ -890,6 +899,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { mainKeyboardView.cancelAllOngoingEvents(); + mainKeyboardView.deallocateMemory(); } // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestionStrip(); @@ -1213,10 +1223,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void resetEntireInputState(final int newCursorPosition) { final boolean shouldFinishComposition = mWordComposer.isComposingWord(); resetComposingState(true /* alsoResetLastComposedWord */); - if (mSettings.getCurrent().mBigramPredictionEnabled) { + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(settingsValues.mSuggestPuncList, false); } mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition); } @@ -1290,8 +1301,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean maybeDoubleSpacePeriod() { - if (!mSettings.getCurrent().mCorrectionEnabled) return false; - if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false; + final SettingsValues settingsValues = mSettings.getCurrent(); + if (!settingsValues.mCorrectionEnabled) return false; + if (!settingsValues.mUseDoubleSpacePeriod) return false; if (!mHandler.isAcceptingDoubleSpacePeriod()) return false; final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 @@ -1551,12 +1563,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int spaceState) { mSpaceState = SPACE_STATE_NONE; final boolean didAutoCorrect; - if (mSettings.getCurrent().isWordSeparator(primaryCode)) { + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.isWordSeparator(primaryCode)) { didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); } else { didAutoCorrect = false; if (SPACE_STATE_PHANTOM == spaceState) { - if (mSettings.isInternal()) { + if (settingsValues.mIsInternal) { if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { LatinImeLoggerUtils.onAutoCorrection( "", mWordComposer.getTypedWord(), " ", mWordComposer); @@ -1616,8 +1629,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen BatchInputUpdater.getInstance().onStartBatchInput(this); mHandler.cancelUpdateSuggestionStrip(); mConnection.beginBatchEdit(); + final SettingsValues settingsValues = mSettings.getCurrent(); if (mWordComposer.isComposingWord()) { - if (mSettings.isInternal()) { + if (settingsValues.mIsInternal) { if (mWordComposer.isBatchMode()) { LatinImeLoggerUtils.onAutoCorrection( "", mWordComposer.getTypedWord(), " ", mWordComposer); @@ -1646,7 +1660,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); if (Character.isLetterOrDigit(codePointBeforeCursor) - || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) { + || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) { mSpaceState = SPACE_STATE_PHANTOM; } mConnection.endBatchEdit(); @@ -1866,8 +1880,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mConnection.deleteSurroundingText(1, 0); } } else { + final SettingsValues currentSettings = mSettings.getCurrent(); if (mLastComposedWord.canRevertCommit()) { - if (mSettings.isInternal()) { + if (currentSettings.mIsInternal) { LatinImeLoggerUtils.onAutoCorrectionCancellation(); } revertCommit(); @@ -1944,7 +1959,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } } - if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) { restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(); } } @@ -1961,8 +1976,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) && isFromSuggestionStrip) { - if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false; - if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true; + final SettingsValues currentSettings = mSettings.getCurrent(); + if (currentSettings.isUsuallyPrecededBySpace(code)) return false; + if (currentSettings.isUsuallyFollowedBySpace(code)) return true; mConnection.removeTrailingSpace(); } return false; @@ -1974,8 +1990,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead. // See onStartBatchInput() to see how to do it. - if (SPACE_STATE_PHANTOM == spaceState && - !mSettings.getCurrent().isWordConnector(primaryCode)) { + final SettingsValues currentSettings = mSettings.getCurrent(); + if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) { if (isComposingWord) { // Sanity check throw new RuntimeException("Should not be composing here"); @@ -1993,9 +2009,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // thread here. if (!isComposingWord && (isAlphabet(primaryCode) - || mSettings.getCurrent().isWordConnector(primaryCode)) - && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) && - !mConnection.isCursorTouchingWord(mSettings.getCurrent())) { + || currentSettings.isWordConnector(primaryCode)) + && currentSettings.isSuggestionsRequested(mDisplayOrientation) && + !mConnection.isCursorTouchingWord(currentSettings)) { // Reset entirely the composing state anyway, then start composing a new word unless // the character is a single quote. The idea here is, single quote is not a // separator and it should be treated as a normal character, except in the first @@ -2038,7 +2054,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint(); } mHandler.postUpdateSuggestionStrip(); - if (mSettings.isInternal()) { + if (currentSettings.mIsInternal) { LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y); } } @@ -2051,9 +2067,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final CharSequence selectedText = mConnection.getSelectedText(0 /* flags, 0 for no styles */); if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection + final SettingsValues currentSettings = mSettings.getCurrent(); mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd, - selectedText.toString(), mSettings.getCurrentLocale(), - mSettings.getWordSeparators()); + selectedText.toString(), currentSettings.mLocale, + currentSettings.mWordSeparators); // We trim leading and trailing whitespace. mRecapitalizeStatus.trim(); // Trimming the object may have changed the length of the string, and we need to @@ -2087,8 +2104,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // first so that we can insert the separator at the current cursor position. resetEntireInputState(mLastSelectionStart); } + final SettingsValues currentSettings = mSettings.getCurrent(); if (mWordComposer.isComposingWord()) { - if (mSettings.getCurrent().mCorrectionEnabled) { + if (currentSettings.mCorrectionEnabled) { // TODO: maybe cache Strings in an <String> sparse array or something commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1)); didAutoCorrect = true; @@ -2101,7 +2119,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Constants.SUGGESTION_STRIP_COORDINATE == x); if (SPACE_STATE_PHANTOM == spaceState && - mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) { + currentSettings.isUsuallyPrecededBySpace(primaryCode)) { promotePhantomSpace(); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -2110,7 +2128,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen sendKeyCodePoint(primaryCode); if (Constants.CODE_SPACE == primaryCode) { - if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (maybeDoubleSpacePeriod()) { mSpaceState = SPACE_STATE_DOUBLE; } else if (!isShowingPunctuationList()) { @@ -2125,7 +2143,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen swapSwapperAndSpace(); mSpaceState = SPACE_STATE_SWAP_PUNCTUATION; } else if (SPACE_STATE_PHANTOM == spaceState - && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) { + && currentSettings.isUsuallyFollowedBySpace(primaryCode)) { // If we are in phantom space state, and the user presses a separator, we want to // stay in phantom space state so that the next keypress has a chance to add the // space. For example, if I type "Good dat", pick "day" from the suggestion strip @@ -2143,7 +2161,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // already displayed or not, so it's okay. setPunctuationSuggestions(); } - if (mSettings.isInternal()) { + if (currentSettings.mIsInternal) { LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y); } @@ -2176,17 +2194,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean isSuggestionsStripVisible() { + final SettingsValues currentSettings = mSettings.getCurrent(); if (mSuggestionStripView == null) return false; if (mSuggestionStripView.isShowingAddToDictionaryHint()) return true; - if (null == mSettings.getCurrent()) + if (null == currentSettings) return false; - if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation)) + if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation)) return false; - if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) + if (currentSettings.isApplicationSpecifiedCompletionsOn()) return true; - return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation); + return currentSettings.isSuggestionsRequested(mDisplayOrientation); } private void clearSuggestionStrip() { @@ -2219,10 +2238,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void updateSuggestionStrip() { mHandler.cancelUpdateSuggestionStrip(); + final SettingsValues currentSettings = mSettings.getCurrent(); // Check if we have a suggestion engine attached. if (mSuggest == null - || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " + "requested!"); @@ -2230,7 +2250,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) { + if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) { setPunctuationSuggestions(); return; } @@ -2251,12 +2271,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we // should just skip whitespace if any, so 1. // TODO: this is slow (2-way IPC) - we should probably cache this instead. + final SettingsValues currentSettings = mSettings.getCurrent(); final String prevWord = - mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, + mConnection.getNthPreviousWord(currentSettings.mWordSeparators, mWordComposer.isComposingWord() ? 2 : 1); return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(), - mSettings.getBlockPotentiallyOffensive(), - mSettings.getCurrent().mCorrectionEnabled, sessionId); + currentSettings.mBlockPotentiallyOffensive, + currentSettings.mCorrectionEnabled, sessionId); } private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) { @@ -2382,18 +2403,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mConnection.beginBatchEdit(); + final SettingsValues currentSettings = mSettings.getCurrent(); if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0 // In the batch input mode, a manually picked suggested word should just replace // the current batch input text and there is no need for a phantom space. && !mWordComposer.isBatchMode()) { final int firstChar = Character.codePointAt(suggestion, 0); - if (!mSettings.getCurrent().isWordSeparator(firstChar) - || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) { + if (!currentSettings.isWordSeparator(firstChar) + || currentSettings.isUsuallyPrecededBySpace(firstChar)) { promotePhantomSpace(); } } - if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn() + if (currentSettings.isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { mSuggestedWords = SuggestedWords.EMPTY; @@ -2431,20 +2453,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // AND it's in none of our current dictionaries (main, user or otherwise). // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint + final Suggest suggest = mSuggest; final boolean showingAddToDictionaryHint = (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) - && mSuggest != null + && suggest != null // If the suggestion is not in the dictionary, the hint should be shown. - && !AutoCorrectionUtils.isValidWord(mSuggest, suggestion, true); + && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true); - if (mSettings.isInternal()) { + if (currentSettings.mIsInternal) { LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { mSuggestionStripView.showAddToDictionaryHint( - suggestion, mSettings.getCurrent().mHintToSaveText); + suggestion, currentSettings.mHintToSaveText); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. mHandler.postUpdateSuggestionStrip(); @@ -2470,10 +2493,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void setPunctuationSuggestions() { - if (mSettings.getCurrent().mBigramPredictionEnabled) { + final SettingsValues currentSettings = mSettings.getCurrent(); + if (currentSettings.mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(currentSettings.mSuggestPuncList, false); } setAutoCorrectionIndicator(false); setSuggestionStripShown(isSuggestionsStripVisible()); @@ -2481,21 +2505,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private String addToUserHistoryDictionary(final String suggestion) { if (TextUtils.isEmpty(suggestion)) return null; - if (mSuggest == null) return null; + final Suggest suggest = mSuggest; + if (suggest == null) return null; // If correction is not enabled, we don't add words to the user history dictionary. // That's to avoid unintended additions in some sensitive fields, or fields that // expect to receive non-words. - if (!mSettings.getCurrent().mCorrectionEnabled) return null; + final SettingsValues currentSettings = mSettings.getCurrent(); + if (!currentSettings.mCorrectionEnabled) return null; - final Suggest suggest = mSuggest; - final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary; - if (suggest == null || userHistoryDictionary == null) { - // Avoid concurrent issue - return null; - } - final String prevWord - = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2); + final UserHistoryPredictionDictionary userHistoryPredictionDictionary = + mUserHistoryPredictionDictionary; + if (userHistoryPredictionDictionary == null) return null; + + final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2); final String secondWord; if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) { secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); @@ -2507,7 +2530,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int maxFreq = AutoCorrectionUtils.getMaxFrequency( suggest.getUnigramDictionaries(), suggestion); if (maxFreq == 0) return null; - userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0); + userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0); return prevWord; } @@ -2524,8 +2547,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mLastSelectionStart != mLastSelectionEnd) return; // If we don't know the cursor location, return. if (mLastSelectionStart < 0) return; - if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; - final TextRange range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), + final SettingsValues currentSettings = mSettings.getCurrent(); + if (!mConnection.isCursorTouchingWord(currentSettings)) return; + final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators, 0 /* additionalPrecedingWordsCount */); if (null == range) return; // Happens if we don't have an input connection at all // If for some strange reason (editor bug or so) we measure the text before the cursor as @@ -2639,7 +2663,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { - mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); + mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord); } mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1); if (mSettings.isInternal()) { diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index d07fa47d6..b69e3f8d2 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -56,11 +56,14 @@ public final class RichInputConnection { private static final int INVALID_CURSOR_POSITION = -1; /** - * This variable contains the value LatinIME thinks the cursor position should be at now. - * This is a few steps in advance of what the TextView thinks it is, because TextView will - * only know after the IPC calls gets through. + * This variable contains an expected value for the cursor position. This is where the + * cursor may end up after all the keyboard-triggered updates have passed. We keep this to + * compare it to the actual cursor position to guess whether the move was caused by a + * keyboard command or not. + * It's not really the cursor position: the cursor may not be there yet, and it's also expected + * there be cases where it never actually comes to be there. */ - private int mCurrentCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points + private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points /** * This contains the committed text immediately preceding the cursor and the composing * text if any. It is refreshed when the cursor moves by calling upon the TextView. @@ -101,16 +104,16 @@ public final class RichInputConnection { final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString() : beforeCursor.subSequence(beforeCursor.length() - actualLength, beforeCursor.length()).toString(); - if (et.selectionStart != mCurrentCursorPosition + if (et.selectionStart != mExpectedCursorPosition || !(reference.equals(internal.toString()))) { - final String context = "Expected cursor position = " + mCurrentCursorPosition + final String context = "Expected cursor position = " + mExpectedCursorPosition + "\nActual cursor position = " + et.selectionStart + "\nExpected text = " + internal.length() + " " + internal + "\nActual text = " + reference.length() + " " + reference; ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); } else { Log.e(TAG, DebugLogUtils.getStackTrace(2)); - Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart); + Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart); } } @@ -141,7 +144,7 @@ public final class RichInputConnection { public void resetCachesUponCursorMove(final int newCursorPosition, final boolean shouldFinishComposition) { - mCurrentCursorPosition = newCursorPosition; + mExpectedCursorPosition = newCursorPosition; mComposingText.setLength(0); mCommittedTextBeforeComposingText.setLength(0); final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0); @@ -166,7 +169,7 @@ public final class RichInputConnection { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(mComposingText); - mCurrentCursorPosition += mComposingText.length(); + mExpectedCursorPosition += mComposingText.length(); mComposingText.setLength(0); if (null != mIC) { mIC.finishComposingText(); @@ -180,7 +183,7 @@ public final class RichInputConnection { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(text); - mCurrentCursorPosition += text.length() - mComposingText.length(); + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); if (null != mIC) { mIC.commitText(text, i); @@ -193,7 +196,7 @@ public final class RichInputConnection { } public boolean canDeleteCharacters() { - return mCurrentCursorPosition > 0; + return mExpectedCursorPosition > 0; } /** @@ -230,7 +233,7 @@ public final class RichInputConnection { // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so. // getCapsMode should be updated to be able to return a "not enough info" result so that // we can get more context only when needed. - if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mCurrentCursorPosition) { + if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) { mCommittedTextBeforeComposingText.append( getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0)); } @@ -251,7 +254,7 @@ public final class RichInputConnection { mCommittedTextBeforeComposingText.length() + mComposingText.length(); // If we have enough characters to satisfy the request, or if we have all characters in // the text field, then we can return the cached version right away. - if (cachedLength >= n || cachedLength >= mCurrentCursorPosition) { + if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) { final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); s.append(mComposingText); if (s.length() > n) { @@ -284,10 +287,10 @@ public final class RichInputConnection { + remainingChars, 0); mCommittedTextBeforeComposingText.setLength(len); } - if (mCurrentCursorPosition > beforeLength) { - mCurrentCursorPosition -= beforeLength; + if (mExpectedCursorPosition > beforeLength) { + mExpectedCursorPosition -= beforeLength; } else { - mCurrentCursorPosition = 0; + mExpectedCursorPosition = 0; } if (null != mIC) { mIC.deleteSurroundingText(beforeLength, afterLength); @@ -321,7 +324,7 @@ public final class RichInputConnection { switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_ENTER: mCommittedTextBeforeComposingText.append("\n"); - mCurrentCursorPosition += 1; + mExpectedCursorPosition += 1; break; case KeyEvent.KEYCODE_DEL: if (0 == mComposingText.length()) { @@ -333,18 +336,18 @@ public final class RichInputConnection { } else { mComposingText.delete(mComposingText.length() - 1, mComposingText.length()); } - if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1; + if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1; break; case KeyEvent.KEYCODE_UNKNOWN: if (null != keyEvent.getCharacters()) { mCommittedTextBeforeComposingText.append(keyEvent.getCharacters()); - mCurrentCursorPosition += keyEvent.getCharacters().length(); + mExpectedCursorPosition += keyEvent.getCharacters().length(); } break; default: final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1); mCommittedTextBeforeComposingText.append(text); - mCurrentCursorPosition += text.length(); + mExpectedCursorPosition += text.length(); break; } } @@ -378,7 +381,7 @@ public final class RichInputConnection { public void setComposingText(final CharSequence text, final int newCursorPosition) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); - mCurrentCursorPosition += text.length() - mComposingText.length(); + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); mComposingText.append(text); // TODO: support values of i != 1. At this time, this is never called with i != 1. @@ -400,7 +403,7 @@ public final class RichInputConnection { ResearchLogger.richInputConnection_setSelection(start, end); } } - mCurrentCursorPosition = start; + mExpectedCursorPosition = start; mCommittedTextBeforeComposingText.setLength(0); mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0)); } @@ -423,7 +426,7 @@ public final class RichInputConnection { // text should never be null, but just in case, it's better to insert nothing than to crash if (null == text) text = ""; mCommittedTextBeforeComposingText.append(text); - mCurrentCursorPosition += text.length() - mComposingText.length(); + mExpectedCursorPosition += text.length() - mComposingText.length(); mComposingText.setLength(0); if (null != mIC) { mIC.commitCompletion(completionInfo); @@ -705,14 +708,14 @@ public final class RichInputConnection { */ public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) { // If this is an update that arrives at our expected position, it's a belated update. - if (newSelStart == mCurrentCursorPosition) return true; + if (newSelStart == mExpectedCursorPosition) return true; // If this is an update that moves the cursor from our expected position, it must be // an explicit move. - if (oldSelStart == mCurrentCursorPosition) return false; + if (oldSelStart == mExpectedCursorPosition) return false; // The following returns true if newSelStart is between oldSelStart and // mCurrentCursorPosition. We assume that if the updated position is between the old // position and the expected position, then it must be a belated update. - return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0; + return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0; } /** diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 647c6f6e1..c2fdcb552 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -17,12 +17,16 @@ package com.android.inputmethod.latin; import android.content.Context; +import android.preference.PreferenceManager; import android.text.TextUtils; +import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.personalization.UserHistoryDictionary; +import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary; +import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary; +import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.BoundedTreeSet; import com.android.inputmethod.latin.utils.CollectionUtils; @@ -54,21 +58,22 @@ public final class Suggest { // Close to -2**31 private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; + public static final int MAX_SUGGESTIONS = 18; + public interface SuggestInitializationListener { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); } private static final boolean DBG = LatinImeLogger.sDBG; - private Dictionary mMainDictionary; - private ContactsBinaryDictionary mContactsDict; private final ConcurrentHashMap<String, Dictionary> mDictionaries = CollectionUtils.newConcurrentHashMap(); + private HashSet<String> mOnlyDictionarySetForDebug = null; + private Dictionary mMainDictionary; + private ContactsBinaryDictionary mContactsDict; @UsedForTesting private boolean mIsCurrentlyWaitingForMainDictionary = false; - public static final int MAX_SUGGESTIONS = 18; - private float mAutoCorrectionThreshold; // Locale used for upper- and title-casing words @@ -78,6 +83,13 @@ public final class Suggest { final SuggestInitializationListener listener) { initAsynchronously(context, locale, listener); mLocale = locale; + // initialize a debug flag for the personalization + if (Settings.readUseOnlyPersonalizationDictionaryForDebug( + PreferenceManager.getDefaultSharedPreferences(context))) { + mOnlyDictionarySetForDebug = new HashSet<String>(); + mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION); + mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA); + } } @UsedForTesting @@ -86,7 +98,7 @@ public final class Suggest { false /* useFullEditDistance */, locale); mLocale = locale; mMainDictionary = mainDict; - addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict); + addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict); } private void initAsynchronously(final Context context, final Locale locale, @@ -94,6 +106,14 @@ public final class Suggest { resetMainDict(context, locale, listener); } + private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) { + if (mOnlyDictionarySetForDebug != null && mOnlyDictionarySetForDebug.contains(key)) { + Log.w(TAG, "Ignore add " + key + " dictionary for debug."); + return; + } + addOrReplaceDictionary(mDictionaries, key, dict); + } + private static void addOrReplaceDictionary( final ConcurrentHashMap<String, Dictionary> dictionaries, final String key, final Dictionary dict) { @@ -117,7 +137,7 @@ public final class Suggest { public void run() { final DictionaryCollection newMainDict = DictionaryFactory.createMainDictionaryFromManager(context, locale); - addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict); + addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict); mMainDictionary = newMainDict; if (listener != null) { listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); @@ -155,7 +175,7 @@ public final class Suggest { * before the main dictionary, if set. This refers to the system-managed user dictionary. */ public void setUserDictionary(final UserBinaryDictionary userDictionary) { - addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary); + addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary); } /** @@ -165,11 +185,19 @@ public final class Suggest { */ public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) { mContactsDict = contactsDictionary; - addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary); + addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary); + } + + public void setUserHistoryPredictionDictionary( + final UserHistoryPredictionDictionary userHistoryPredictionDictionary) { + addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, + userHistoryPredictionDictionary); } - public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) { - addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary); + public void setPersonalizationPredictionDictionary( + final PersonalizationPredictionDictionary personalizationPredictionDictionary) { + addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA, + personalizationPredictionDictionary); } public void setAutoCorrectionThreshold(float threshold) { diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index 000c25270..167c6915c 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -982,6 +982,7 @@ public final class BinaryDictIOUtils { return null; } + private static final int HEADER_READING_BUFFER_SIZE = 16384; /** * Convenience method to read the header of a binary file. * @@ -991,7 +992,6 @@ public final class BinaryDictIOUtils { * @param offset The offset in the file where to start reading the data. * @param length The length of the data file. */ - private static final int HEADER_READING_BUFFER_SIZE = 16384; public static FileHeader getDictionaryFileHeader( final File file, final long offset, final long length) throws FileNotFoundException, IOException, UnsupportedFormatException { diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index 5a2b24c58..118dc22b8 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -34,6 +34,8 @@ import java.util.LinkedList; public final class FusionDictionary implements Iterable<Word> { private static final boolean DBG = MakedictLog.DBG; + private static int CHARACTER_NOT_FOUND_INDEX = -1; + /** * A node of the dictionary, containing several CharGroups. * @@ -473,7 +475,7 @@ public final class FusionDictionary implements Iterable<Word> { CharGroup currentGroup = null; int differentCharIndex = 0; // Set by the loop to the index of the char that differs int nodeIndex = findIndexOfChar(mRoot, word[charIndex]); - while (CHARACTER_NOT_FOUND != nodeIndex) { + while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) { currentGroup = currentNode.mData.get(nodeIndex); differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex); if (ARRAYS_ARE_EQUAL != differentCharIndex @@ -485,7 +487,7 @@ public final class FusionDictionary implements Iterable<Word> { nodeIndex = findIndexOfChar(currentNode, word[charIndex]); } - if (-1 == nodeIndex) { + if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) { // No node at this point to accept the word. Create one. final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]); final CharGroup newGroup = new CharGroup( @@ -612,20 +614,18 @@ public final class FusionDictionary implements Iterable<Word> { return result >= 0 ? result : -result - 1; } - private static int CHARACTER_NOT_FOUND = -1; - /** * Find the index of a char in a node, if it exists. * * @param node the node to search in. * @param character the character to search for. - * @return the position of the character if it's there, or CHARACTER_NOT_FOUND = -1 else. + * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else. */ private static int findIndexOfChar(final Node node, int character) { final int insertionIndex = findInsertionIndex(node, character); - if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND; + if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX; return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex - : CHARACTER_NOT_FOUND; + : CHARACTER_NOT_FOUND_INDEX; } /** @@ -640,7 +640,7 @@ public final class FusionDictionary implements Iterable<Word> { CharGroup currentGroup; do { int indexOfGroup = findIndexOfChar(node, codePoints[index]); - if (CHARACTER_NOT_FOUND == indexOfGroup) return null; + if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null; currentGroup = node.mData.get(indexOfGroup); if (codePoints.length - index < currentGroup.mChars.length) return null; diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java index c76dea0bb..9d041f4eb 100644 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2013 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. @@ -24,7 +24,6 @@ import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.ExpandableDictionary; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -47,16 +46,17 @@ import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; /** - * Locally gathers stats about the words user types and various other signals like auto-correction - * cancellation or manual picks. This allows the keyboard to adapt to the typist over time. + * This class is a base class of a dictionary for the personalized prediction language model. */ -public class UserHistoryDictionary extends ExpandableDictionary { - private static final String TAG = UserHistoryDictionary.class.getSimpleName(); - private static final String NAME = UserHistoryDictionary.class.getSimpleName(); +public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary { + public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) { + // TODO: Implement + } + + private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName(); public static final boolean DBG_SAVE_RESTORE = false; - public static final boolean DBG_STRESS_TEST = false; - public static final boolean DBG_ALWAYS_WRITE = false; - public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG; + private static final boolean DBG_STRESS_TEST = false; + private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG; private static final FormatOptions VERSION3 = new FormatOptions(3, true /* supportsDynamicUpdate */); @@ -65,14 +65,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { private static final int FREQUENCY_FOR_TYPED = 2; /** Maximum number of pairs. Pruning will start when databases goes above this number. */ - public static final int MAX_HISTORY_BIGRAMS = 10000; - - /** - * When it hits maximum bigram pair, it will delete until you are left with - * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs. - * Do not keep this number small to avoid deleting too often. - */ - public static final int DELETE_HISTORY_BIGRAMS = 1000; + private static final int MAX_HISTORY_BIGRAMS = 10000; /** Locale for which this user history dictionary is storing words */ private final String mLocale; @@ -85,9 +78,9 @@ public class UserHistoryDictionary extends ExpandableDictionary { // Should always be false except when we use this class for test @UsedForTesting boolean isTest = false; - /* package */ UserHistoryDictionary(final Context context, final String locale, - final SharedPreferences sp) { - super(context, Dictionary.TYPE_USER_HISTORY); + /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale, + final SharedPreferences sp, final String dictionaryType) { + super(context, dictionaryType); mLocale = locale; mPrefs = sp; if (mLocale != null && mLocale.length() > 1) { @@ -102,8 +95,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { // Also, the database is written to somewhat frequently, so it needs to be kept alive // throughout the life of the process. // mOpenHelper.close(); - // Ignore close because we cache UserHistoryDictionary for each language. See getInstance() - // above. + // Ignore close because we cache PersonalizationPredictionDictionary for each language. + // See getInstance() above. // super.close(); } @@ -184,7 +177,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { } @Override - public void loadDictionaryAsync() { + public final void loadDictionaryAsync() { // This must be run on non-main thread mBigramListLock.lock(); try { @@ -194,48 +187,47 @@ public class UserHistoryDictionary extends ExpandableDictionary { } } - private int profTotal; - private void loadDictionaryAsyncLocked() { + final int[] profTotalCount = { 0 }; + final String locale = getLocale(); if (DBG_STRESS_TEST) { try { - Log.w(TAG, "Start stress in loading: " + mLocale); + Log.w(TAG, "Start stress in loading: " + locale); Thread.sleep(15000); Log.w(TAG, "End stress in loading"); } catch (InterruptedException e) { } } - final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale); + final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale); final boolean initializing = last == 0; final long now = System.currentTimeMillis(); - profTotal = 0; - final String fileName = NAME + "." + mLocale + ".dict"; + final String fileName = getDictionaryFileName(); final ExpandableDictionary dictionary = this; final OnAddWordListener listener = new OnAddWordListener() { @Override public void setUnigram(final String word, final String shortcutTarget, final int frequency) { - profTotal++; if (DBG_SAVE_RESTORE) { Log.d(TAG, "load unigram: " + word + "," + frequency); } dictionary.addWord(word, shortcutTarget, frequency); - mBigramList.addBigram(null, word, (byte)frequency); + ++profTotalCount[0]; + addToBigramListLocked(null, word, (byte)frequency); } @Override public void setBigram(final String word1, final String word2, final int frequency) { if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) { - profTotal++; if (DBG_SAVE_RESTORE) { Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency); } + ++profTotalCount[0]; dictionary.setBigramAndGetFrequency( word1, word2, initializing ? new ForgettingCurveParams(true) : new ForgettingCurveParams(frequency, now, last)); } - mBigramList.addBigram(word1, word2, (byte)frequency); + addToBigramListLocked(word1, word2, (byte)frequency); } }; @@ -264,11 +256,21 @@ public class UserHistoryDictionary extends ExpandableDictionary { if (PROFILE_SAVE_RESTORE) { final long diff = System.currentTimeMillis() - now; Log.d(TAG, "PROF: Load UserHistoryDictionary: " - + mLocale + ", " + diff + "ms. load " + profTotal + "entries."); + + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries."); } } } + protected abstract String getDictionaryFileName(); + + protected String getLocale() { + return mLocale; + } + + private void addToBigramListLocked(String word0, String word1, byte fcValue) { + mBigramList.addBigram(word0, word1, fcValue); + } + /** * Async task to write pending words to the binarydicts. */ @@ -277,16 +279,16 @@ public class UserHistoryDictionary extends ExpandableDictionary { private final UserHistoryDictionaryBigramList mBigramList; private final boolean mAddLevel0Bigrams; private final String mLocale; - private final UserHistoryDictionary mUserHistoryDictionary; + private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary; private final SharedPreferences mPrefs; private final Context mContext; public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites, - final String locale, final UserHistoryDictionary dict, + final String locale, final DynamicPredictionDictionaryBase dict, final SharedPreferences prefs, final Context context) { mBigramList = pendingWrites; mLocale = locale; - mUserHistoryDictionary = dict; + mDynamicPredictionDictionary = dict; mPrefs = prefs; mContext = context; mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS; @@ -294,19 +296,19 @@ public class UserHistoryDictionary extends ExpandableDictionary { @Override protected Void doInBackground(final Void... v) { - if (mUserHistoryDictionary.isTest) { + if (mDynamicPredictionDictionary.isTest) { // If isTest == true, wait until the lock is released. - mUserHistoryDictionary.mBigramListLock.lock(); + mDynamicPredictionDictionary.mBigramListLock.lock(); try { doWriteTaskLocked(); } finally { - mUserHistoryDictionary.mBigramListLock.unlock(); + mDynamicPredictionDictionary.mBigramListLock.unlock(); } - } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) { + } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) { try { doWriteTaskLocked(); } finally { - mUserHistoryDictionary.mBigramListLock.unlock(); + mDynamicPredictionDictionary.mBigramListLock.unlock(); } } return null; @@ -324,7 +326,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { } final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0; - final String fileName = NAME + "." + mLocale + ".dict"; + final String fileName = + mDynamicPredictionDictionary.getDictionaryFileName(); final File file = new File(mContext.getFilesDir(), fileName); FileOutputStream out = null; @@ -360,7 +363,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { freq = FREQUENCY_FOR_TYPED; final byte prevFc = mBigramList.getBigrams(word1).get(word2); } else { // bigram - final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2); + final NextWord nw = + mDynamicPredictionDictionary.getBigramWord(word1, word2); if (nw != null) { final ForgettingCurveParams fcp = nw.getFcParams(); final byte prevFc = mBigramList.getBigrams(word1).get(word2); diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDicitonary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java index d3e2dfec9..19554d639 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDicitonary.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java @@ -24,7 +24,7 @@ import android.content.Context; /** * This class is a dictionary for the personalized language model that uses binary dictionary. */ -public class PersonalizationDicitonary extends ExpandableBinaryDictionary { +public class PersonalizationDictionary extends ExpandableBinaryDictionary { private static final String NAME = "personalization"; public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) { @@ -35,7 +35,7 @@ public class PersonalizationDicitonary extends ExpandableBinaryDictionary { private final String mLocale; // Singleton - private PersonalizationDicitonary(final Context context, final String locale) { + private PersonalizationDictionary(final Context context, final String locale) { super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION); mLocale = locale; } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java index e09e834bf..9f013df1c 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java @@ -29,24 +29,53 @@ public class PersonalizationDictionaryHelper { private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName(); private static final boolean DEBUG = false; - private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> - sLangDictCache = CollectionUtils.newConcurrentHashMap(); + private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>> + sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap(); - public static UserHistoryDictionary getUserHistoryDictionary( + private static final ConcurrentHashMap<String, + SoftReference<PersonalizationPredictionDictionary>> + sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap(); + + public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary( + final Context context, final String locale, final SharedPreferences sp) { + synchronized (sLangUserHistoryDictCache) { + if (sLangUserHistoryDictCache.containsKey(locale)) { + final SoftReference<UserHistoryPredictionDictionary> ref = + sLangUserHistoryDictCache.get(locale); + final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get(); + if (dict != null) { + if (DEBUG) { + Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale); + } + return dict; + } + } + final UserHistoryPredictionDictionary dict = + new UserHistoryPredictionDictionary(context, locale, sp); + sLangUserHistoryDictCache.put( + locale, new SoftReference<UserHistoryPredictionDictionary>(dict)); + return dict; + } + } + + public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary( final Context context, final String locale, final SharedPreferences sp) { - synchronized (sLangDictCache) { - if (sLangDictCache.containsKey(locale)) { - final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale); - final UserHistoryDictionary dict = ref == null ? null : ref.get(); + synchronized (sLangPersonalizationDictCache) { + if (sLangPersonalizationDictCache.containsKey(locale)) { + final SoftReference<PersonalizationPredictionDictionary> ref = + sLangPersonalizationDictCache.get(locale); + final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get(); if (dict != null) { if (DEBUG) { - Log.w(TAG, "Use cached UserHistoryDictionary for " + locale); + Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale); } return dict; } } - final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp); - sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict)); + final PersonalizationPredictionDictionary dict = + new PersonalizationPredictionDictionary(context, locale, sp); + sLangPersonalizationDictCache.put( + locale, new SoftReference<PersonalizationPredictionDictionary>(dict)); return dict; } } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java index 2ec0dc00c..c78e5a95b 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java @@ -16,6 +16,6 @@ package com.android.inputmethod.latin.personalization; -public class PersonalizationDictionaryUpdateListener { +public interface PersonalizationDictionaryUpdateListener { // TODO: Implement } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java new file mode 100644 index 000000000..955bd2762 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 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.latin.personalization; + +import com.android.inputmethod.latin.Dictionary; + +import android.content.Context; +import android.content.SharedPreferences; + +public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase { + private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName(); + + /* package */ PersonalizationPredictionDictionary(final Context context, final String locale, + final SharedPreferences sp) { + super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA); + } + + @Override + protected String getDictionaryFileName() { + return NAME + "." + getLocale() + ".dict"; + } +} diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java index b93630a18..f21db25a6 100644 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java +++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java @@ -53,7 +53,7 @@ public final class UserHistoryDictionaryBigramList { * Called when loaded from the SQL DB. */ public void addBigram(String word1, String word2, byte fcValue) { - if (UserHistoryDictionary.DBG_SAVE_RESTORE) { + if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) { Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue); } final HashMap<String, Byte> map; @@ -73,7 +73,7 @@ public final class UserHistoryDictionaryBigramList { * Called when inserted to the SQL DB. */ public void updateBigram(String word1, String word2, byte fcValue) { - if (UserHistoryDictionary.DBG_SAVE_RESTORE) { + if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) { Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue); } final HashMap<String, Byte> map; diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDicitonary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java index 3e7772584..d11784454 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDicitonary.java +++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java @@ -17,30 +17,23 @@ package com.android.inputmethod.latin.personalization; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.ExpandableDictionary; import android.content.Context; import android.content.SharedPreferences; /** - * This class is a dictionary for the personalized prediction language model implemented in Java. + * Locally gathers stats about the words user types and various other signals like auto-correction + * cancellation or manual picks. This allows the keyboard to adapt to the typist over time. */ -public class PersonalizationPredictionDicitonary extends ExpandableDictionary { - public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) { - // TODO: Implement - } - - /** Locale for which this user history dictionary is storing words */ - private final String mLocale; - private final SharedPreferences mPrefs; - - // Singleton - private PersonalizationPredictionDicitonary(final Context context, final String locale, +public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase { + private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName(); + /* package */ UserHistoryPredictionDictionary(final Context context, final String locale, final SharedPreferences sp) { - super(context, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA); - mLocale = locale; - mPrefs = sp; + super(context, locale, sp, Dictionary.TYPE_USER_HISTORY); } - // TODO: Implement + @Override + protected String getDictionaryFileName() { + return NAME + "." + getLocale() + ".dict"; + } } diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java index 34ea2279d..b1cd88729 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java @@ -32,12 +32,13 @@ import com.android.inputmethod.latin.utils.ApplicationUtils; public final class DebugSettings extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = DebugSettings.class.getSimpleName(); public static final String PREF_DEBUG_MODE = "debug_mode"; public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch"; public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; public static final String PREF_STATISTICS_LOGGING = "enable_logging"; + public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG = + "use_only_personalization_dictionary_for_debug"; private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary"; private static final boolean SHOW_STATISTICS_LOGGING = false; @@ -67,7 +68,7 @@ public final class DebugSettings extends PreferenceFragment } } - PreferenceScreen readExternalDictionary = + final PreferenceScreen readExternalDictionary = (PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY); if (null != readExternalDictionary) { readExternalDictionary.setOnPreferenceClickListener( @@ -112,6 +113,8 @@ public final class DebugSettings extends PreferenceFragment } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH) || key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) { mServiceNeedsRestart = true; + } else if (key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) { + mServiceNeedsRestart = true; } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index b690fed78..d432087d3 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -27,12 +27,14 @@ import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; +import com.android.inputmethod.latin.utils.DebugLogUtils; import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.RunInLocale; import java.util.HashMap; import java.util.Locale; +import java.util.concurrent.locks.ReentrantLock; public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = Settings.class.getSimpleName(); @@ -93,8 +95,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang private Resources mRes; private SharedPreferences mPrefs; - private Locale mCurrentLocale; private SettingsValues mSettingsValues; + private final ReentrantLock mSettingsValuesLock = new ReentrantLock(); private static final Settings sInstance = new Settings(); @@ -122,25 +124,34 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - if (mSettingsValues == null) { - // TODO: Introduce a static function to register this class and ensure that - // loadSettings must be called before "onSharedPreferenceChanged" is called. - Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); - return; + mSettingsValuesLock.lock(); + try { + if (mSettingsValues == null) { + // TODO: Introduce a static function to register this class and ensure that + // loadSettings must be called before "onSharedPreferenceChanged" is called. + Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); + return; + } + loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes); + } finally { + mSettingsValuesLock.unlock(); } - loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes); } public void loadSettings(final Locale locale, final InputAttributes inputAttributes) { - mCurrentLocale = locale; - final SharedPreferences prefs = mPrefs; - final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() { - @Override - protected SettingsValues job(final Resources res) { - return new SettingsValues(prefs, res, inputAttributes); - } - }; - mSettingsValues = job.runInLocale(mRes, locale); + mSettingsValuesLock.lock(); + try { + final SharedPreferences prefs = mPrefs; + final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() { + @Override + protected SettingsValues job(final Resources res) { + return new SettingsValues(prefs, locale, res, inputAttributes); + } + }; + mSettingsValues = job.runInLocale(mRes, locale); + } finally { + mSettingsValuesLock.unlock(); + } } // TODO: Remove this method and add proxy method to SettingsValues. @@ -160,10 +171,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return mSettingsValues.isWordSeparator(code); } - public Locale getCurrentLocale() { - return mCurrentLocale; - } - public boolean getBlockPotentiallyOffensive() { return mSettingsValues.mBlockPotentiallyOffensive; } @@ -330,4 +337,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static boolean isInternal(final SharedPreferences prefs) { return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false); } + + public static boolean readUseOnlyPersonalizationDictionaryForDebug( + final SharedPreferences prefs) { + return prefs.getBoolean( + DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false); + } } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 3fa00bb2e..8aafb0738 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -32,11 +32,11 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.InputTypeUtils; -import com.android.inputmethod.latin.utils.RunInLocale; import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; /** * When you call the constructor of this class, you may want to change the current system locale by @@ -75,6 +75,7 @@ public final class SettingsValues { public final boolean mGestureFloatingPreviewTextEnabled; public final boolean mSlidingKeyInputPreviewEnabled; public final int mKeyLongpressTimeout; + public final Locale mLocale; // From the input box public final InputAttributes mInputAttributes; @@ -97,8 +98,9 @@ public final class SettingsValues { // Debug settings public final boolean mIsInternal; - public SettingsValues(final SharedPreferences prefs, final Resources res, + public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res, final InputAttributes inputAttributes) { + mLocale = locale; // Get the resources mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); mSymbolsPrecededBySpace = diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java index ba5a68460..21426d1eb 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -274,4 +274,8 @@ public class UserDictionaryAddWordContents { localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale return localesList; } + + public String getCurrentUserDictionaryLocale() { + return mLocale; + } } diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java index 8b8bd5e03..4fc132f68 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java @@ -60,6 +60,7 @@ public class UserDictionaryAddWordFragment extends Fragment public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); + getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary); // Keep the instance so that we remember mContents when configuration changes (eg rotation) setRetainInstance(true); } @@ -82,6 +83,8 @@ public class UserDictionaryAddWordFragment extends Fragment mContents = new UserDictionaryAddWordContents(mRootView, mContents /* oldInstanceToBeEdited */); } + getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName( + getActivity(), mContents.getCurrentUserDictionaryLocale())); return mRootView; } @@ -100,7 +103,7 @@ public class UserDictionaryAddWordFragment extends Fragment /** * Callback for the framework when a menu option is pressed. * - * @param MenuItem the item that was pressed + * @param item the item that was pressed * @return false to allow normal menu processing to proceed, true to consume it here */ @Override diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java index 159ebb1b9..36b927eea 100644 --- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java @@ -22,7 +22,7 @@ import java.util.ArrayList; /** * Utility methods for parsing and serializing Comma-Separated Values. The public APIs of this - * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String)}, + * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String...)}, * {@link #join(int,String...)}, and {@link #join(int,int[],String...)}. * * This class implements CSV parsing and serializing methods conforming to RFC 4180 with an diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java index ef9cacf61..06826dac0 100644 --- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.util.Log; +import android.view.MotionEvent; import com.android.inputmethod.latin.LatinImeLogger; @@ -109,6 +110,43 @@ public final class UsabilityStudyLogUtils { LatinImeLogger.onPrintAllUsabilityStudyLogs(); } + public static void writeMotionEvent(final MotionEvent me) { + final int action = me.getActionMasked(); + final long eventTime = me.getEventTime(); + final int pointerCount = me.getPointerCount(); + for (int index = 0; index < pointerCount; index++) { + final int id = me.getPointerId(index); + final int x = (int)me.getX(index); + final int y = (int)me.getY(index); + final float size = me.getSize(index); + final float pressure = me.getPressure(index); + + final String eventTag; + switch (action) { + case MotionEvent.ACTION_UP: + eventTag = "[Up]"; + break; + case MotionEvent.ACTION_DOWN: + eventTag = "[Down]"; + break; + case MotionEvent.ACTION_POINTER_UP: + eventTag = "[PointerUp]"; + break; + case MotionEvent.ACTION_POINTER_DOWN: + eventTag = "[PointerDown]"; + break; + case MotionEvent.ACTION_MOVE: + eventTag = "[Move]"; + break; + default: + eventTag = "[Action" + action + "]"; + break; + } + getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + + "," + pressure); + } + } + public void write(final String log) { mLoggingHandler.post(new Runnable() { @Override @@ -191,7 +229,7 @@ public final class UsabilityStudyLogUtils { Log.w(USABILITY_TAG, e2); return; } - if (destFile == null || !destFile.exists()) { + if (!destFile.exists()) { Log.w(USABILITY_TAG, "Dest file doesn't exist."); return; } diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 3482153b4..6df7c1708 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -119,9 +119,9 @@ public abstract class MainLogBuffer extends FixedLogBuffer { * * @param logUnits a LogUnit list to check for publishability * @param nGramSize the smallest n-gram acceptable to be published. if - * {@link ResearchLogger.IS_LOGGING_EVERYTHING} is true, then publish if there are more than + * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than * {@code minNGramSize} words in the logUnits, otherwise wait. if {@link - * ResearchLogger.IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize + * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize * words in the LogUnits. * * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class. diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java index fbfd9b531..3388645b7 100644 --- a/java/src/com/android/inputmethod/research/MotionEventReader.java +++ b/java/src/com/android/inputmethod/research/MotionEventReader.java @@ -315,16 +315,6 @@ public class MotionEventReader { return pointerCoords; } - /** - * Tests that {@code x} is uninitialized. - * - * Assumes that {@code x} will never be given a valid value less than 0, and that - * UNINITIALIZED_FLOAT is less than 0.0f. - */ - private boolean isUninitializedFloat(final float x) { - return x < 0.0f; - } - private void addMotionEventData(final ReplayData replayData, final int actionType, final long time, final PointerProperties[] pointerProperties, final PointerCoords[] pointerCoords) { diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index ed047e13a..25187ced1 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -180,7 +180,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private ResearchLogDirectory mResearchLogDirectory; private SplashScreen mSplashScreen; - private Intent mUploadIntent; private Intent mUploadNowIntent; /* package for test */ LogUnit mCurrentLogUnit = new LogUnit(); @@ -233,7 +232,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang resetLogBuffers(); // Initialize external services - mUploadIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -319,12 +317,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang restart(); } - private void setLoggingAllowed(final boolean enableLogging) { - if (mPrefs == null) return; - sIsLogging = enableLogging; - ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging); - } - private void checkForEmptyEditor() { if (mLatinIME == null) { return; @@ -1074,22 +1066,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT = new LogStatement("MotionEvent", true, false, "action", LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent"); - public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action, - final long eventTime, final int index, final int id, final int x, final int y) { - if (me != null) { - final String actionString = LoggingUtils.getMotionEventActionTypeString(action); - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT, - actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me)); - if (action == MotionEvent.ACTION_DOWN) { - // Subtract 1 from eventTime so the down event is included in the later - // LogUnit, not the earlier (the test is for inequality). - researchLogger.setSavedDownEventTime(eventTime - 1); - } - // Refresh the timer in case we are capturing user feedback. - if (researchLogger.isMakingUserRecording()) { - researchLogger.resetRecordingTimer(); - } + public static void mainKeyboardView_processMotionEvent(final MotionEvent me) { + if (me == null) { + return; + } + final int action = me.getActionMasked(); + final long eventTime = me.getEventTime(); + final String actionString = LoggingUtils.getMotionEventActionTypeString(action); + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT, + actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me)); + if (action == MotionEvent.ACTION_DOWN) { + // Subtract 1 from eventTime so the down event is included in the later + // LogUnit, not the earlier (the test is for inequality). + researchLogger.setSavedDownEventTime(eventTime - 1); + } + // Refresh the timer in case we are capturing user feedback. + if (researchLogger.isMakingUserRecording()) { + researchLogger.resetRecordingTimer(); } } @@ -1261,10 +1255,23 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY = new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index", "suggestion", "x", "y", "isBatchMode", "score", "kind", "sourceDict"); + /** + * Log a call to LatinIME.pickSuggestionManually(). + * + * @param replacedWord the typed word that this manual suggestion replaces. May not be null. + * @param index the index in the suggestion strip + * @param suggestion the committed suggestion. May not be null. + * @param isBatchMode whether this was input in batch mode, aka gesture. + * @param score the internal score of the suggestion, as output by the dictionary + * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants + * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants. + */ public static void latinIME_pickSuggestionManually(final String replacedWord, final int index, final String suggestion, final boolean isBatchMode, final int score, final int kind, final String sourceDict) { final ResearchLogger researchLogger = getInstance(); + // Note : suggestion can't be null here, because it's only called in a place where it + // can't be null. if (!replacedWord.equals(suggestion.toString())) { // The user chose something other than what was already there. researchLogger.setCurrentLogUnitContainsUserDeletions(); @@ -1273,7 +1280,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String scrubbedWord = scrubDigitsFromString(suggestion); researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY, scrubDigitsFromString(replacedWord), index, - suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE, + scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict); researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode); researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis()); |