From 58fe5a421f3334641209300c5bc60c0e6a842220 Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Thu, 27 Sep 2012 19:01:17 +0900 Subject: [PATCH] Fix gesture detection algorithm This change also * Introduces adaptive gesture detecting threshold for time domain in addition to length domain. * Tunes the parameters for detecting gesture after fast typing. * Fixes a bug in dismissing gesture floating preview text. * Cleanup debug messages Bug: 7218902 Change-Id: Iafccd872c6efe0c3b5ae65fa40b04c80d9f139c7 --- .../inputmethod/keyboard/PointerTracker.java | 86 ++++----- .../keyboard/internal/GestureStroke.java | 166 +++++++++++------- .../GestureStrokeWithPreviewPoints.java | 2 +- .../android/inputmethod/latin/LatinIME.java | 47 +++-- 4 files changed, 172 insertions(+), 129 deletions(-) diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 5248b8abb..100a9c618 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -168,8 +168,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private static long sLastLetterTypingUpTime; private static final InputPointers sAggregratedPointers = new InputPointers( GestureStroke.DEFAULT_CAPACITY); - private static int sLastRecognitionPointSize = 0; - private static long sLastRecognitionTime = 0; + private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers + private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers // The position and time at which first down event occurred. private long mDownTime; @@ -306,9 +306,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); if (DEBUG_LISTENER) { - Log.d(TAG, "onPress : " + KeyDetector.printableCode(key) - + " ignoreModifier=" + ignoreModifierKey - + " enabled=" + key.isEnabled()); + Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId, + KeyDetector.printableCode(key), + ignoreModifierKey ? " ignoreModifier" : "", + key.isEnabled() ? "" : " disabled")); } if (ignoreModifierKey) { return false; @@ -331,10 +332,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { - Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) - + " text=" + key.getOutputText() + " x=" + x + " y=" + y - + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode - + " enabled=" + key.isEnabled()); + final String output = code == Keyboard.CODE_OUTPUT_TEXT + ? key.getOutputText() : Keyboard.printableCode(code); + Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, + output, ignoreModifierKey ? " ignoreModifier" : "", + altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); } if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, @@ -362,9 +364,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); if (DEBUG_LISTENER) { - Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) - + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey - + " enabled="+ key.isEnabled()); + Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, + Keyboard.printableCode(primaryCode), + withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", + key.isEnabled() ? "": " disabled")); } if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, @@ -380,7 +383,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private void callListenerOnCancelInput() { if (DEBUG_LISTENER) { - Log.d(TAG, "onCancelInput"); + Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); } if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_callListenerOnCancelInput(); @@ -389,6 +392,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } private void setKeyDetectorInner(final KeyDetector keyDetector) { + final Keyboard keyboard = keyDetector.getKeyboard(); + if (keyDetector == mKeyDetector && keyboard == mKeyboard) { + return; + } mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth); @@ -551,10 +558,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return; } if (DEBUG_LISTENER) { - Log.d(TAG, "onStartBatchInput"); + Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId)); } sInGesture = true; - mListener.onStartBatchInput(); + synchronized (sAggregratedPointers) { + sAggregratedPointers.reset(); + sLastRecognitionPointSize = 0; + sLastRecognitionTime = 0; + mListener.onStartBatchInput(); + } final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); } @@ -569,7 +581,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sLastRecognitionPointSize = size; sLastRecognitionTime = eventTime; if (DEBUG_LISTENER) { - Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size); + Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", + mPointerId, size)); } mListener.onUpdateBatchInput(sAggregratedPointers); } @@ -582,36 +595,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private void mayEndBatchInput() { synchronized (sAggregratedPointers) { mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); - mGestureStrokeWithPreviewPoints.reset(); if (getActivePointerTrackerCount() == 1) { if (DEBUG_LISTENER) { - Log.d(TAG, "onEndBatchInput: batchPoints=" - + sAggregratedPointers.getPointerSize()); + Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d", + mPointerId, sAggregratedPointers.getPointerSize())); } sInGesture = false; mListener.onEndBatchInput(sAggregratedPointers); - clearBatchInputPointsOfAllPointerTrackers(); } } final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); } - private static void abortBatchInput() { - clearBatchInputPointsOfAllPointerTrackers(); - } - - private static void clearBatchInputPointsOfAllPointerTrackers() { - final int trackersSize = sTrackers.size(); - for (int i = 0; i < trackersSize; ++i) { - final PointerTracker tracker = sTrackers.get(i); - tracker.mGestureStrokeWithPreviewPoints.reset(); - } - sAggregratedPointers.reset(); - sLastRecognitionPointSize = 0; - sLastRecognitionTime = 0; - } - public void processMotionEvent(final int action, final int x, final int y, final long eventTime, final KeyEventHandler handler) { switch (action) { @@ -681,18 +677,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (getActivePointerTrackerCount() == 1) { sGestureFirstDownTime = eventTime; } - onGestureDownEvent(x, y, eventTime); + mGestureStrokeWithPreviewPoints.onDownEvent(x, y, eventTime, sGestureFirstDownTime, + sLastLetterTypingUpTime); } } - private void onGestureDownEvent(final int x, final int y, final long eventTime) { - mIsDetectingGesture = true; - mGestureStrokeWithPreviewPoints.setLastLetterTypingTime(eventTime, sLastLetterTypingUpTime); - final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); - mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, - true /* isMajorEvent */); - } - private void onDownEventInternal(final int x, final int y, final long eventTime) { Key key = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding @@ -925,9 +914,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mayEndBatchInput(); return; } - // This event will be recognized as a regular code input. Clear unused possible batch points - // so they are not mistakenly displayed as preview. - clearBatchInputPointsOfAllPointerTrackers(); + if (mKeyAlreadyProcessed) { return; } @@ -941,7 +928,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { - abortBatchInput(); onLongPressed(); mIsShowingMoreKeysPanel = true; onDownEvent(x, y, SystemClock.uptimeMillis(), handler); @@ -1029,7 +1015,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final long eventTime) { final Key key = mKeyDetector.detectHitKey(x, y); final String code = KeyDetector.printableCode(key); - Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, - (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code)); + Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId, + (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code)); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 9fe6fa3f8..c0e92df32 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -22,6 +22,7 @@ import com.android.inputmethod.latin.ResizableIntArray; public class GestureStroke { private static final String TAG = GestureStroke.class.getSimpleName(); private static final boolean DEBUG = false; + private static final boolean DEBUG_SPEED = false; public static final int DEFAULT_CAPACITY = 128; @@ -29,42 +30,52 @@ public class GestureStroke { private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); - private int mIncrementalRecognitionSize; - private int mLastIncrementalBatchSize; - private long mLastMajorEventTime; - private int mLastMajorEventX; - private int mLastMajorEventY; - private boolean mAfterFastTyping; - private int mKeyWidth; - private int mStartGestureLengthThresholdAfterFastTyping; // pixel - private int mStartGestureLengthThreshold; // pixel - private int mMinGestureSamplingLength; // pixel - private int mGestureRecognitionSpeedThreshold; // pixel / sec + private int mKeyWidth; // pixel + // Static threshold for starting gesture detection private int mDetectFastMoveSpeedThreshold; // pixel /sec private int mDetectFastMoveTime; private int mDetectFastMoveX; private int mDetectFastMoveY; + // Dynamic threshold for gesture after fast typing + private boolean mAfterFastTyping; + private int mGestureDynamicDistanceThresholdFrom; // pixel + private int mGestureDynamicDistanceThresholdTo; // pixel + // Variables for gesture sampling + private int mGestureSamplingMinimumDistance; // pixel + private long mLastMajorEventTime; + private int mLastMajorEventX; + private int mLastMajorEventY; + // Variables for gesture recognition + private int mGestureRecognitionSpeedThreshold; // pixel / sec + private int mIncrementalRecognitionSize; + private int mLastIncrementalBatchSize; // TODO: Move some of these to resource. - private static final int GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD = 350; // msec - private static final float START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH = - 8.0f; - private static final int START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION = 400; // msec - private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.6f; - private static final int START_GESTURE_DURATION_THRESHOLD = 70; // msec - private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec - private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f; - private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH = - 5.5f; // keyWidth / sec - private static final float DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH = - 5.0f; // keyWidth / sec - private static final int MSEC_PER_SEC = 1000; - public static final boolean hasRecognitionTimePast( - final long currentTime, final long lastRecognitionTime) { - return currentTime > lastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME; - } + // Static threshold for gesture after fast typing + public static final int GESTURE_STATIC_TIME_THRESHOLD_AFTER_FAST_TYPING = 350; // msec + + // Static threshold for starting gesture detection + private static final float DETECT_FAST_MOVE_SPEED_THRESHOLD = 1.5f; // keyWidth / sec + + // Dynamic threshold for gesture after fast typing + private static final int GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION = 450; // msec + // Time based threshold values + private static final int GESTURE_DYNAMIC_TIME_THRESHOLD_FROM = 300; // msec + private static final int GESTURE_DYNAMIC_TIME_THRESHOLD_TO = 20; // msec + // Distance based threshold values + private static final float GESTURE_DYNAMIC_DISTANCE_THRESHOLD_FROM = 6.0f; // keyWidth + private static final float GESTURE_DYNAMIC_DISTANCE_THRESHOLD_TO = 0.35f; // keyWidth + + // Parameters for gesture sampling + private static final float GESTURE_SAMPLING_MINIMUM_DISTANCE = 1.0f / 6.0f; // keyWidth + + // Parameters for gesture recognition + private static final int GESTURE_RECOGNITION_MINIMUM_TIME = 100; // msec + private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD = 5.5f; // keyWidth / sec + + private static final int MSEC_PER_SEC = 1000; public GestureStroke(final int pointerId) { mPointerId = pointerId; @@ -73,41 +84,58 @@ public class GestureStroke { public void setKeyboardGeometry(final int keyWidth) { mKeyWidth = keyWidth; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? - mStartGestureLengthThresholdAfterFastTyping = (int)(keyWidth - * START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH); - mStartGestureLengthThreshold = - (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH); - mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); + mDetectFastMoveSpeedThreshold = (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD); + mGestureDynamicDistanceThresholdFrom = + (int)(keyWidth * GESTURE_DYNAMIC_DISTANCE_THRESHOLD_FROM); + mGestureDynamicDistanceThresholdTo = + (int)(keyWidth * GESTURE_DYNAMIC_DISTANCE_THRESHOLD_TO); + mGestureSamplingMinimumDistance = (int)(keyWidth * GESTURE_SAMPLING_MINIMUM_DISTANCE); mGestureRecognitionSpeedThreshold = - (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); - mDetectFastMoveSpeedThreshold = - (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); + (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD); if (DEBUG) { - Log.d(TAG, "[" + mPointerId + "] setKeyboardGeometry: keyWidth=" + keyWidth - + " tL0=" + mStartGestureLengthThresholdAfterFastTyping - + " tL=" + mStartGestureLengthThreshold); + Log.d(TAG, String.format( + "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d", + mPointerId, keyWidth, + GESTURE_DYNAMIC_TIME_THRESHOLD_FROM, + GESTURE_DYNAMIC_TIME_THRESHOLD_TO, + mGestureDynamicDistanceThresholdFrom, + mGestureDynamicDistanceThresholdTo)); } } - public void setLastLetterTypingTime(final long downTime, final long lastTypingTime) { - final long elpasedTimeAfterTyping = downTime - lastTypingTime; - if (elpasedTimeAfterTyping < GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD) { + public void onDownEvent(final int x, final int y, final long downTime, + final long gestureFirstDownTime, final long lastTypingTime) { + reset(); + final long elapsedTimeAfterTyping = downTime - lastTypingTime; + if (elapsedTimeAfterTyping < GESTURE_STATIC_TIME_THRESHOLD_AFTER_FAST_TYPING) { mAfterFastTyping = true; } if (DEBUG) { - Log.d(TAG, "[" + mPointerId + "] setLastTypingTime: dT=" + elpasedTimeAfterTyping - + " afterFastTyping=" + mAfterFastTyping); + Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId, + elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : "")); } + final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime); + addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); } - private int getStartGestureLengthThreshold(final int deltaTime) { - if (!mAfterFastTyping || deltaTime >= START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION) { - return mStartGestureLengthThreshold; + private int getGestureDynamicDistanceThreshold(final int deltaTime) { + if (!mAfterFastTyping || deltaTime >= GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION) { + return mGestureDynamicDistanceThresholdTo; } final int decayedThreshold = - (mStartGestureLengthThresholdAfterFastTyping - mStartGestureLengthThreshold) - * deltaTime / START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION; - return mStartGestureLengthThresholdAfterFastTyping - decayedThreshold; + (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo) + * deltaTime / GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION; + return mGestureDynamicDistanceThresholdFrom - decayedThreshold; + } + + private int getGestureDynamicTimeThreshold(final int deltaTime) { + if (!mAfterFastTyping || deltaTime >= GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION) { + return GESTURE_DYNAMIC_TIME_THRESHOLD_TO; + } + final int decayedThreshold = + (GESTURE_DYNAMIC_TIME_THRESHOLD_FROM - GESTURE_DYNAMIC_TIME_THRESHOLD_TO) + * deltaTime / GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION; + return GESTURE_DYNAMIC_TIME_THRESHOLD_FROM - decayedThreshold; } public boolean isStartOfAGesture() { @@ -120,21 +148,24 @@ public class GestureStroke { } final int lastIndex = size - 1; final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime; - final int deltaLength = getDistance( + final int deltaDistance = getDistance( mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), mDetectFastMoveX, mDetectFastMoveY); - final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime); - final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD - && deltaLength > startGestureLengthThreshold; + final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime); + final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime); + final boolean isStartOfAGesture = deltaTime >= timeThreshold + && deltaDistance >= distanceThreshold; if (DEBUG) { - Log.d(TAG, "[" + mPointerId + "] isStartOfAGesture: dT=" + deltaTime - + " dL=" + deltaLength + " tL=" + startGestureLengthThreshold - + " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : "")); + Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s", + mPointerId, deltaTime, timeThreshold, + deltaDistance, distanceThreshold, + mAfterFastTyping ? " afterFastTyping" : "", + isStartOfAGesture ? " startOfAGesture" : "")); } return isStartOfAGesture; } - public void reset() { + protected void reset() { mIncrementalRecognitionSize = 0; mLastIncrementalBatchSize = 0; mEventTimes.setLength(0); @@ -167,15 +198,17 @@ public class GestureStroke { if (msecs > 0) { final int pixels = getDistance(lastX, lastY, x, y); final int pixelsPerSec = pixels * MSEC_PER_SEC; - if (DEBUG) { + if (DEBUG_SPEED) { final float speed = (float)pixelsPerSec / msecs / mKeyWidth; - Log.d(TAG, String.format("[" + mPointerId + "] speed=%.3f", speed)); + Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed)); } // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC) if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { if (DEBUG) { - Log.d(TAG, "[" + mPointerId + "] detect fast move: T=" - + time + " points = " + size); + final float speed = (float)pixelsPerSec / msecs / mKeyWidth; + Log.d(TAG, String.format( + "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove", + mPointerId, speed, time, size)); } mDetectFastMoveTime = time; mDetectFastMoveX = x; @@ -192,8 +225,8 @@ public class GestureStroke { appendPoint(x, y, time); updateMajorEvent(x, y, time); } else { - final int dist = detectFastMove(x, y, time); - if (dist > mMinGestureSamplingLength) { + final int distance = detectFastMove(x, y, time); + if (distance > mGestureSamplingMinimumDistance) { appendPoint(x, y, time); } } @@ -216,6 +249,11 @@ public class GestureStroke { } } + public static final boolean hasRecognitionTimePast( + final long currentTime, final long lastRecognitionTime) { + return currentTime > lastRecognitionTime + GESTURE_RECOGNITION_MINIMUM_TIME; + } + public void appendAllBatchPoints(final InputPointers out) { appendBatchPoints(out, mEventTimes.getLength()); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 5b3f31805..05e0a2ec3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -38,7 +38,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } @Override - public void reset() { + protected void reset() { super.reset(); mStrokeId++; mLastPreviewSize = 0; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 3e893303f..268d36ad2 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1402,6 +1402,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onStartBatchInput() { + BatchInputUpdater.getInstance().onStartBatchInput(); mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { if (ProductionFlag.IS_INTERNAL) { @@ -1433,6 +1434,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private static final class BatchInputUpdater implements Handler.Callback { private final Handler mHandler; private LatinIME mLatinIme; + private boolean mInBatchInput; // synchornized using "this". private BatchInputUpdater() { final HandlerThread handlerThread = new HandlerThread( @@ -1456,17 +1458,32 @@ public final class LatinIME extends InputMethodService implements KeyboardAction public boolean handleMessage(final Message msg) { switch (msg.what) { case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: - final SuggestedWords suggestedWords = getSuggestedWordsGesture( - (InputPointers)msg.obj, mLatinIme); - showGesturePreviewAndSuggestionStrip( - suggestedWords, false /* dismissGestureFloatingPreviewText */, mLatinIme); + updateBatchInput((InputPointers)msg.obj, mLatinIme); break; } return true; } - public void updateGesturePreviewAndSuggestionStrip(final InputPointers batchPointers, + // Run in the UI thread. + public synchronized void onStartBatchInput() { + mInBatchInput = true; + } + + // Run in the Handler thread. + private synchronized void updateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { + if (!mInBatchInput) { + // Batch input has ended while the message was being delivered. + return; + } + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked( + batchPointers, latinIme); + latinIme.mHandler.showGesturePreviewAndSuggestionStrip( + suggestedWords, false /* dismissGestureFloatingPreviewText */); + } + + // Run in the UI thread. + public void onUpdateBatchInput(final InputPointers batchPointers, final LatinIME latinIme) { mLatinIme = latinIme; if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) { return; @@ -1476,15 +1493,20 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .sendToTarget(); } - public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, - final boolean dismissGestureFloatingPreviewText, final LatinIME latinIme) { + // Run in the UI thread. + public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers, + final LatinIME latinIme) { + mInBatchInput = false; + final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked( + batchPointers, latinIme); latinIme.mHandler.showGesturePreviewAndSuggestionStrip( - suggestedWords, dismissGestureFloatingPreviewText); + suggestedWords, true /* dismissGestureFloatingPreviewText */); + return suggestedWords; } // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to // be synchronized. - public synchronized SuggestedWords getSuggestedWordsGesture( + private static SuggestedWords getSuggestedWordsGestureLocked( final InputPointers batchPointers, final LatinIME latinIme) { latinIme.mWordComposer.setBatchInputPointers(batchPointers); return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE); @@ -1505,16 +1527,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onUpdateBatchInput(final InputPointers batchPointers) { - BatchInputUpdater.getInstance().updateGesturePreviewAndSuggestionStrip(batchPointers, this); + BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers, this); } @Override public void onEndBatchInput(final InputPointers batchPointers) { - final BatchInputUpdater batchInputUpdater = BatchInputUpdater.getInstance(); - final SuggestedWords suggestedWords = batchInputUpdater.getSuggestedWordsGesture( + final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput( batchPointers, this); - batchInputUpdater.showGesturePreviewAndSuggestionStrip( - suggestedWords, true /* dismissGestureFloatingPreviewText */, this); final String batchInputText = (suggestedWords.size() > 0) ? suggestedWords.getWord(0) : null; if (TextUtils.isEmpty(batchInputText)) {