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
main
Tadashi G. Takaoka 2012-09-27 19:01:17 +09:00
parent ad6b493c16
commit 58fe5a421f
4 changed files with 172 additions and 129 deletions

View File

@ -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));
}
}

View File

@ -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());
}

View File

@ -38,7 +38,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
}
@Override
public void reset() {
protected void reset() {
super.reset();
mStrokeId++;
mLastPreviewSize = 0;

View File

@ -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)) {