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 long sLastLetterTypingUpTime;
private static final InputPointers sAggregratedPointers = new InputPointers( private static final InputPointers sAggregratedPointers = new InputPointers(
GestureStroke.DEFAULT_CAPACITY); GestureStroke.DEFAULT_CAPACITY);
private static int sLastRecognitionPointSize = 0; private static int sLastRecognitionPointSize = 0; // synchronized using sAggregratedPointers
private static long sLastRecognitionTime = 0; private static long sLastRecognitionTime = 0; // synchronized using sAggregratedPointers
// The position and time at which first down event occurred. // The position and time at which first down event occurred.
private long mDownTime; private long mDownTime;
@ -306,9 +306,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onPress : " + KeyDetector.printableCode(key) Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId,
+ " ignoreModifier=" + ignoreModifierKey KeyDetector.printableCode(key),
+ " enabled=" + key.isEnabled()); ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "" : " disabled"));
} }
if (ignoreModifierKey) { if (ignoreModifierKey) {
return false; return false;
@ -331,10 +332,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode; final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) final String output = code == Keyboard.CODE_OUTPUT_TEXT
+ " text=" + key.getOutputText() + " x=" + x + " y=" + y ? key.getOutputText() : Keyboard.printableCode(code);
+ " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y,
+ " enabled=" + key.isEnabled()); output, ignoreModifierKey ? " ignoreModifier" : "",
altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled"));
} }
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
@ -362,9 +364,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId,
+ " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey Keyboard.printableCode(primaryCode),
+ " enabled="+ key.isEnabled()); withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
key.isEnabled() ? "": " disabled"));
} }
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
@ -380,7 +383,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private void callListenerOnCancelInput() { private void callListenerOnCancelInput() {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onCancelInput"); Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
} }
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_callListenerOnCancelInput(); ResearchLogger.pointerTracker_callListenerOnCancelInput();
@ -389,6 +392,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
private void setKeyDetectorInner(final KeyDetector keyDetector) { private void setKeyDetectorInner(final KeyDetector keyDetector) {
final Keyboard keyboard = keyDetector.getKeyboard();
if (keyDetector == mKeyDetector && keyboard == mKeyboard) {
return;
}
mKeyDetector = keyDetector; mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard(); mKeyboard = keyDetector.getKeyboard();
mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth); mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth);
@ -551,10 +558,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
return; return;
} }
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onStartBatchInput"); Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
} }
sInGesture = true; sInGesture = true;
mListener.onStartBatchInput(); synchronized (sAggregratedPointers) {
sAggregratedPointers.reset();
sLastRecognitionPointSize = 0;
sLastRecognitionTime = 0;
mListener.onStartBatchInput();
}
final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
} }
@ -569,7 +581,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
sLastRecognitionPointSize = size; sLastRecognitionPointSize = size;
sLastRecognitionTime = eventTime; sLastRecognitionTime = eventTime;
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size); Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d",
mPointerId, size));
} }
mListener.onUpdateBatchInput(sAggregratedPointers); mListener.onUpdateBatchInput(sAggregratedPointers);
} }
@ -582,36 +595,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
private void mayEndBatchInput() { private void mayEndBatchInput() {
synchronized (sAggregratedPointers) { synchronized (sAggregratedPointers) {
mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers);
mGestureStrokeWithPreviewPoints.reset();
if (getActivePointerTrackerCount() == 1) { if (getActivePointerTrackerCount() == 1) {
if (DEBUG_LISTENER) { if (DEBUG_LISTENER) {
Log.d(TAG, "onEndBatchInput: batchPoints=" Log.d(TAG, String.format("[%d] onEndBatchInput : batchPoints=%d",
+ sAggregratedPointers.getPointerSize()); mPointerId, sAggregratedPointers.getPointerSize()));
} }
sInGesture = false; sInGesture = false;
mListener.onEndBatchInput(sAggregratedPointers); mListener.onEndBatchInput(sAggregratedPointers);
clearBatchInputPointsOfAllPointerTrackers();
} }
} }
final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); 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, public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
final KeyEventHandler handler) { final KeyEventHandler handler) {
switch (action) { switch (action) {
@ -681,18 +677,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (getActivePointerTrackerCount() == 1) { if (getActivePointerTrackerCount() == 1) {
sGestureFirstDownTime = eventTime; 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) { private void onDownEventInternal(final int x, final int y, final long eventTime) {
Key key = onDownKey(x, y, eventTime); Key key = onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding // 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(); mayEndBatchInput();
return; 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) { if (mKeyAlreadyProcessed) {
return; return;
} }
@ -941,7 +928,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
} }
public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) {
abortBatchInput();
onLongPressed(); onLongPressed();
mIsShowingMoreKeysPanel = true; mIsShowingMoreKeysPanel = true;
onDownEvent(x, y, SystemClock.uptimeMillis(), handler); onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
@ -1029,7 +1015,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final long eventTime) { final long eventTime) {
final Key key = mKeyDetector.detectHitKey(x, y); final Key key = mKeyDetector.detectHitKey(x, y);
final String code = KeyDetector.printableCode(key); final String code = KeyDetector.printableCode(key);
Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
(mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code)); (mKeyAlreadyProcessed ? "-" : " "), title, x, y, eventTime, code));
} }
} }

View File

@ -22,6 +22,7 @@ import com.android.inputmethod.latin.ResizableIntArray;
public class GestureStroke { public class GestureStroke {
private static final String TAG = GestureStroke.class.getSimpleName(); private static final String TAG = GestureStroke.class.getSimpleName();
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static final boolean DEBUG_SPEED = false;
public static final int DEFAULT_CAPACITY = 128; 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 mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mYCoordinates = 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 mKeyWidth; // pixel
private int mStartGestureLengthThresholdAfterFastTyping; // pixel // Static threshold for starting gesture detection
private int mStartGestureLengthThreshold; // pixel
private int mMinGestureSamplingLength; // pixel
private int mGestureRecognitionSpeedThreshold; // pixel / sec
private int mDetectFastMoveSpeedThreshold; // pixel /sec private int mDetectFastMoveSpeedThreshold; // pixel /sec
private int mDetectFastMoveTime; private int mDetectFastMoveTime;
private int mDetectFastMoveX; private int mDetectFastMoveX;
private int mDetectFastMoveY; 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. // 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( // Static threshold for gesture after fast typing
final long currentTime, final long lastRecognitionTime) { public static final int GESTURE_STATIC_TIME_THRESHOLD_AFTER_FAST_TYPING = 350; // msec
return currentTime > lastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME;
} // 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) { public GestureStroke(final int pointerId) {
mPointerId = pointerId; mPointerId = pointerId;
@ -73,41 +84,58 @@ public class GestureStroke {
public void setKeyboardGeometry(final int keyWidth) { public void setKeyboardGeometry(final int keyWidth) {
mKeyWidth = keyWidth; mKeyWidth = keyWidth;
// TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
mStartGestureLengthThresholdAfterFastTyping = (int)(keyWidth mDetectFastMoveSpeedThreshold = (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD);
* START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH); mGestureDynamicDistanceThresholdFrom =
mStartGestureLengthThreshold = (int)(keyWidth * GESTURE_DYNAMIC_DISTANCE_THRESHOLD_FROM);
(int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH); mGestureDynamicDistanceThresholdTo =
mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); (int)(keyWidth * GESTURE_DYNAMIC_DISTANCE_THRESHOLD_TO);
mGestureSamplingMinimumDistance = (int)(keyWidth * GESTURE_SAMPLING_MINIMUM_DISTANCE);
mGestureRecognitionSpeedThreshold = mGestureRecognitionSpeedThreshold =
(int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD);
mDetectFastMoveSpeedThreshold =
(int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "[" + mPointerId + "] setKeyboardGeometry: keyWidth=" + keyWidth Log.d(TAG, String.format(
+ " tL0=" + mStartGestureLengthThresholdAfterFastTyping "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d",
+ " tL=" + mStartGestureLengthThreshold); mPointerId, keyWidth,
GESTURE_DYNAMIC_TIME_THRESHOLD_FROM,
GESTURE_DYNAMIC_TIME_THRESHOLD_TO,
mGestureDynamicDistanceThresholdFrom,
mGestureDynamicDistanceThresholdTo));
} }
} }
public void setLastLetterTypingTime(final long downTime, final long lastTypingTime) { public void onDownEvent(final int x, final int y, final long downTime,
final long elpasedTimeAfterTyping = downTime - lastTypingTime; final long gestureFirstDownTime, final long lastTypingTime) {
if (elpasedTimeAfterTyping < GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD) { reset();
final long elapsedTimeAfterTyping = downTime - lastTypingTime;
if (elapsedTimeAfterTyping < GESTURE_STATIC_TIME_THRESHOLD_AFTER_FAST_TYPING) {
mAfterFastTyping = true; mAfterFastTyping = true;
} }
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "[" + mPointerId + "] setLastTypingTime: dT=" + elpasedTimeAfterTyping Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId,
+ " afterFastTyping=" + mAfterFastTyping); elapsedTimeAfterTyping, mAfterFastTyping ? " afterFastTyping" : ""));
} }
final int elapsedTimeFromFirstDown = (int)(downTime - gestureFirstDownTime);
addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */);
} }
private int getStartGestureLengthThreshold(final int deltaTime) { private int getGestureDynamicDistanceThreshold(final int deltaTime) {
if (!mAfterFastTyping || deltaTime >= START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION) { if (!mAfterFastTyping || deltaTime >= GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION) {
return mStartGestureLengthThreshold; return mGestureDynamicDistanceThresholdTo;
} }
final int decayedThreshold = final int decayedThreshold =
(mStartGestureLengthThresholdAfterFastTyping - mStartGestureLengthThreshold) (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo)
* deltaTime / START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION; * deltaTime / GESTURE_DYNAMIC_THRESHOLD_DECAY_DURATION;
return mStartGestureLengthThresholdAfterFastTyping - decayedThreshold; 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() { public boolean isStartOfAGesture() {
@ -120,21 +148,24 @@ public class GestureStroke {
} }
final int lastIndex = size - 1; final int lastIndex = size - 1;
final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime; final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime;
final int deltaLength = getDistance( final int deltaDistance = getDistance(
mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
mDetectFastMoveX, mDetectFastMoveY); mDetectFastMoveX, mDetectFastMoveY);
final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime); final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime);
final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime);
&& deltaLength > startGestureLengthThreshold; final boolean isStartOfAGesture = deltaTime >= timeThreshold
&& deltaDistance >= distanceThreshold;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "[" + mPointerId + "] isStartOfAGesture: dT=" + deltaTime Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s",
+ " dL=" + deltaLength + " tL=" + startGestureLengthThreshold mPointerId, deltaTime, timeThreshold,
+ " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : "")); deltaDistance, distanceThreshold,
mAfterFastTyping ? " afterFastTyping" : "",
isStartOfAGesture ? " startOfAGesture" : ""));
} }
return isStartOfAGesture; return isStartOfAGesture;
} }
public void reset() { protected void reset() {
mIncrementalRecognitionSize = 0; mIncrementalRecognitionSize = 0;
mLastIncrementalBatchSize = 0; mLastIncrementalBatchSize = 0;
mEventTimes.setLength(0); mEventTimes.setLength(0);
@ -167,15 +198,17 @@ public class GestureStroke {
if (msecs > 0) { if (msecs > 0) {
final int pixels = getDistance(lastX, lastY, x, y); final int pixels = getDistance(lastX, lastY, x, y);
final int pixelsPerSec = pixels * MSEC_PER_SEC; final int pixelsPerSec = pixels * MSEC_PER_SEC;
if (DEBUG) { if (DEBUG_SPEED) {
final float speed = (float)pixelsPerSec / msecs / mKeyWidth; 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) // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "[" + mPointerId + "] detect fast move: T=" final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
+ time + " points = " + size); Log.d(TAG, String.format(
"[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove",
mPointerId, speed, time, size));
} }
mDetectFastMoveTime = time; mDetectFastMoveTime = time;
mDetectFastMoveX = x; mDetectFastMoveX = x;
@ -192,8 +225,8 @@ public class GestureStroke {
appendPoint(x, y, time); appendPoint(x, y, time);
updateMajorEvent(x, y, time); updateMajorEvent(x, y, time);
} else { } else {
final int dist = detectFastMove(x, y, time); final int distance = detectFastMove(x, y, time);
if (dist > mMinGestureSamplingLength) { if (distance > mGestureSamplingMinimumDistance) {
appendPoint(x, y, time); 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) { public void appendAllBatchPoints(final InputPointers out) {
appendBatchPoints(out, mEventTimes.getLength()); appendBatchPoints(out, mEventTimes.getLength());
} }

View File

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

View File

@ -1402,6 +1402,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override @Override
public void onStartBatchInput() { public void onStartBatchInput() {
BatchInputUpdater.getInstance().onStartBatchInput();
mConnection.beginBatchEdit(); mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) { if (mWordComposer.isComposingWord()) {
if (ProductionFlag.IS_INTERNAL) { 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 static final class BatchInputUpdater implements Handler.Callback {
private final Handler mHandler; private final Handler mHandler;
private LatinIME mLatinIme; private LatinIME mLatinIme;
private boolean mInBatchInput; // synchornized using "this".
private BatchInputUpdater() { private BatchInputUpdater() {
final HandlerThread handlerThread = new HandlerThread( final HandlerThread handlerThread = new HandlerThread(
@ -1456,17 +1458,32 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
public boolean handleMessage(final Message msg) { public boolean handleMessage(final Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP: case MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP:
final SuggestedWords suggestedWords = getSuggestedWordsGesture( updateBatchInput((InputPointers)msg.obj, mLatinIme);
(InputPointers)msg.obj, mLatinIme);
showGesturePreviewAndSuggestionStrip(
suggestedWords, false /* dismissGestureFloatingPreviewText */, mLatinIme);
break; break;
} }
return true; 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) { 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; mLatinIme = latinIme;
if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) { if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
return; return;
@ -1476,15 +1493,20 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
.sendToTarget(); .sendToTarget();
} }
public void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, // Run in the UI thread.
final boolean dismissGestureFloatingPreviewText, final LatinIME latinIme) { public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers,
final LatinIME latinIme) {
mInBatchInput = false;
final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(
batchPointers, latinIme);
latinIme.mHandler.showGesturePreviewAndSuggestionStrip( latinIme.mHandler.showGesturePreviewAndSuggestionStrip(
suggestedWords, dismissGestureFloatingPreviewText); suggestedWords, true /* dismissGestureFloatingPreviewText */);
return suggestedWords;
} }
// {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
// be synchronized. // be synchronized.
public synchronized SuggestedWords getSuggestedWordsGesture( private static SuggestedWords getSuggestedWordsGestureLocked(
final InputPointers batchPointers, final LatinIME latinIme) { final InputPointers batchPointers, final LatinIME latinIme) {
latinIme.mWordComposer.setBatchInputPointers(batchPointers); latinIme.mWordComposer.setBatchInputPointers(batchPointers);
return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE); return latinIme.getSuggestedWords(Suggest.SESSION_GESTURE);
@ -1505,16 +1527,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override @Override
public void onUpdateBatchInput(final InputPointers batchPointers) { public void onUpdateBatchInput(final InputPointers batchPointers) {
BatchInputUpdater.getInstance().updateGesturePreviewAndSuggestionStrip(batchPointers, this); BatchInputUpdater.getInstance().onUpdateBatchInput(batchPointers, this);
} }
@Override @Override
public void onEndBatchInput(final InputPointers batchPointers) { public void onEndBatchInput(final InputPointers batchPointers) {
final BatchInputUpdater batchInputUpdater = BatchInputUpdater.getInstance(); final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput(
final SuggestedWords suggestedWords = batchInputUpdater.getSuggestedWordsGesture(
batchPointers, this); batchPointers, this);
batchInputUpdater.showGesturePreviewAndSuggestionStrip(
suggestedWords, true /* dismissGestureFloatingPreviewText */, this);
final String batchInputText = (suggestedWords.size() > 0) final String batchInputText = (suggestedWords.size() > 0)
? suggestedWords.getWord(0) : null; ? suggestedWords.getWord(0) : null;
if (TextUtils.isEmpty(batchInputText)) { if (TextUtils.isEmpty(batchInputText)) {