am 3088e0c9: Merge "Fix gesture start detection algorithm" into jb-mr1-dev

* commit '3088e0c9e97cee4763d1563eb847691adc1da317':
  Fix gesture start detection algorithm
main
Tadashi G. Takaoka 2012-09-21 17:45:09 -07:00 committed by Android Git Automerger
commit a6f3431fb5
5 changed files with 126 additions and 62 deletions

View File

@ -688,7 +688,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
mIsDetectingGesture = true; mIsDetectingGesture = true;
final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
false /* isHistorical */); 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) {
@ -724,10 +724,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
} }
private void onGestureMoveEvent(final int x, final int y, final long eventTime, private void onGestureMoveEvent(final int x, final int y, final long eventTime,
final boolean isHistorical, final Key key) { final boolean isMajorEvent, final Key key) {
final int gestureTime = (int)(eventTime - sGestureFirstDownTime); final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (mIsDetectingGesture) { if (mIsDetectingGesture) {
mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isHistorical); mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent);
mayStartBatchInput(); mayStartBatchInput();
if (sInGesture && key != null) { if (sInGesture && key != null) {
updateBatchInput(eventTime); updateBatchInput(eventTime);
@ -752,7 +752,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
final int historicalY = (int)me.getHistoricalY(pointerIndex, h); final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
final long historicalTime = me.getHistoricalEventTime(h); final long historicalTime = me.getHistoricalEventTime(h);
onGestureMoveEvent(historicalX, historicalY, historicalTime, onGestureMoveEvent(historicalX, historicalY, historicalTime,
true /* isHistorical */, null); false /* isMajorEvent */, null);
} }
} }
@ -767,7 +767,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (sShouldHandleGesture) { if (sShouldHandleGesture) {
// Register move event on gesture tracker. // Register move event on gesture tracker.
onGestureMoveEvent(x, y, eventTime, false /* isHistorical */, key); onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key);
if (sInGesture) { if (sInGesture) {
mIgnoreModifierKey = true; mIgnoreModifierKey = true;
mTimerProxy.cancelLongPressTimer(); mTimerProxy.cancelLongPressTimer();

View File

@ -14,34 +14,45 @@
package com.android.inputmethod.keyboard.internal; package com.android.inputmethod.keyboard.internal;
import android.util.Log;
import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.ResizableIntArray; import com.android.inputmethod.latin.ResizableIntArray;
public class GestureStroke { public class GestureStroke {
private static final String TAG = GestureStroke.class.getSimpleName();
private static final boolean DEBUG = false;
public static final int DEFAULT_CAPACITY = 128; public static final int DEFAULT_CAPACITY = 128;
private final int mPointerId; private final int mPointerId;
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 float mLength;
private int mIncrementalRecognitionSize; private int mIncrementalRecognitionSize;
private int mLastIncrementalBatchSize; private int mLastIncrementalBatchSize;
private long mLastPointTime; private long mLastMajorEventTime;
private int mLastPointX; private int mLastMajorEventX;
private int mLastPointY; private int mLastMajorEventY;
private int mMinGestureLength; // pixel private int mKeyWidth;
private int mMinGestureSampleLength; // pixel private int mStartGestureLengthThreshold; // pixel
private int mGestureRecognitionThreshold; // pixel / sec private int mMinGestureSamplingLength; // pixel
private int mGestureRecognitionSpeedThreshold; // pixel / sec
private int mDetectFastMoveSpeedThreshold; // pixel /sec
private int mDetectFastMoveTime;
private int mDetectFastMoveX;
private int mDetectFastMoveY;
// TODO: Move some of these to resource. // TODO: Move some of these to resource.
private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f; private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.75f;
private static final int MIN_GESTURE_START_DURATION = 100; // msec private static final int START_GESTURE_DURATION_THRESHOLD = 70; // msec
private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // 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 MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH = private static final float GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH =
5.5f; // keyWidth / sec 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; private static final int MSEC_PER_SEC = 1000;
public static final boolean hasRecognitionTimePast( public static final boolean hasRecognitionTimePast(
@ -54,65 +65,121 @@ public class GestureStroke {
} }
public void setKeyboardGeometry(final int keyWidth) { public void setKeyboardGeometry(final int 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?
mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH); mStartGestureLengthThreshold =
mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH);
mGestureRecognitionThreshold = mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
mGestureRecognitionSpeedThreshold =
(int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); (int)(keyWidth * GESTURE_RECOGNITION_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
mDetectFastMoveSpeedThreshold =
(int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
if (DEBUG) {
Log.d(TAG, "setKeyboardGeometry: keyWidth=" + keyWidth);
}
} }
public boolean isStartOfAGesture() { public boolean isStartOfAGesture() {
if (mDetectFastMoveTime == 0) {
return false;
}
final int size = mEventTimes.getLength(); final int size = mEventTimes.getLength();
final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0; if (size <= 0) {
return downDuration > MIN_GESTURE_START_DURATION && mLength > mMinGestureLength; return false;
}
final int lastIndex = size - 1;
final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime;
final int deltaLength = getDistance(
mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
mDetectFastMoveX, mDetectFastMoveY);
final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD
&& deltaLength > mStartGestureLengthThreshold;
if (DEBUG) {
Log.d(TAG, "isStartOfAGesture: dT=" + deltaTime + " dL=" + deltaLength
+ " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : ""));
}
return isStartOfAGesture;
} }
public void reset() { public void reset() {
mLength = 0;
mIncrementalRecognitionSize = 0; mIncrementalRecognitionSize = 0;
mLastIncrementalBatchSize = 0; mLastIncrementalBatchSize = 0;
mLastPointTime = 0;
mEventTimes.setLength(0); mEventTimes.setLength(0);
mXCoordinates.setLength(0); mXCoordinates.setLength(0);
mYCoordinates.setLength(0); mYCoordinates.setLength(0);
mLastMajorEventTime = 0;
mDetectFastMoveTime = 0;
} }
public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { private void appendPoint(final int x, final int y, final int time) {
final boolean needsSampling;
final int size = mEventTimes.getLength();
if (size == 0) {
needsSampling = true;
} else {
final int lastIndex = size - 1;
final int lastX = mXCoordinates.get(lastIndex);
final int lastY = mYCoordinates.get(lastIndex);
final float dist = getDistance(lastX, lastY, x, y);
needsSampling = dist > mMinGestureSampleLength;
mLength += dist;
}
if (needsSampling) {
mEventTimes.add(time); mEventTimes.add(time);
mXCoordinates.add(x); mXCoordinates.add(x);
mYCoordinates.add(y); mYCoordinates.add(y);
} }
if (!isHistorical) {
private void updateMajorEvent(final int x, final int y, final int time) {
mLastMajorEventTime = time;
mLastMajorEventX = x;
mLastMajorEventY = y;
}
private int detectFastMove(final int x, final int y, final int time) {
final int size = mEventTimes.getLength();
final int lastIndex = size - 1;
final int lastX = mXCoordinates.get(lastIndex);
final int lastY = mYCoordinates.get(lastIndex);
final int dist = getDistance(lastX, lastY, x, y);
final int msecs = time - mEventTimes.get(lastIndex);
if (msecs > 0) {
final int pixels = getDistance(lastX, lastY, x, y);
final int pixelsPerSec = pixels * MSEC_PER_SEC;
if (DEBUG) {
final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
Log.d(TAG, String.format("Speed=%.3f keyWidth/sec", speed));
}
// Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
if (DEBUG) {
Log.d(TAG, "Detect fast move: T=" + time + " points = " + size);
}
mDetectFastMoveTime = time;
mDetectFastMoveX = x;
mDetectFastMoveY = y;
}
}
return dist;
}
public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
final int size = mEventTimes.getLength();
if (size <= 0) {
// Down event
appendPoint(x, y, time);
updateMajorEvent(x, y, time);
} else {
final int dist = detectFastMove(x, y, time);
if (dist > mMinGestureSamplingLength) {
appendPoint(x, y, time);
}
}
if (isMajorEvent) {
updateIncrementalRecognitionSize(x, y, time); updateIncrementalRecognitionSize(x, y, time);
updateMajorEvent(x, y, time);
} }
} }
private void updateIncrementalRecognitionSize(final int x, final int y, final int time) { private void updateIncrementalRecognitionSize(final int x, final int y, final int time) {
final int msecs = (int)(time - mLastPointTime); final int msecs = (int)(time - mLastMajorEventTime);
if (msecs > 0) { if (msecs <= 0) {
final int pixels = (int)getDistance(mLastPointX, mLastPointY, x, y); return;
}
final int pixels = getDistance(mLastMajorEventX, mLastMajorEventY, x, y);
final int pixelsPerSec = pixels * MSEC_PER_SEC;
// Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC) // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
if (pixels * MSEC_PER_SEC < mGestureRecognitionThreshold * msecs) { if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) {
mIncrementalRecognitionSize = mEventTimes.getLength(); mIncrementalRecognitionSize = mEventTimes.getLength();
} }
} }
mLastPointTime = time;
mLastPointX = x;
mLastPointY = y;
}
public void appendAllBatchPoints(final InputPointers out) { public void appendAllBatchPoints(final InputPointers out) {
appendBatchPoints(out, mEventTimes.getLength()); appendBatchPoints(out, mEventTimes.getLength());
@ -132,11 +199,11 @@ public class GestureStroke {
mLastIncrementalBatchSize = size; mLastIncrementalBatchSize = size;
} }
private static float getDistance(final int x1, final int y1, final int x2, final int y2) { private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
final float dx = x1 - x2; final int dx = x1 - x2;
final float dy = y1 - y2; final int dy = y1 - y2;
// Note that, in recent versions of Android, FloatMath is actually slower than // Note that, in recent versions of Android, FloatMath is actually slower than
// java.lang.Math due to the way the JIT optimizes java.lang.Math. // java.lang.Math due to the way the JIT optimizes java.lang.Math.
return (float)Math.sqrt(dx * dx + dy * dy); return (int)Math.sqrt(dx * dx + dy * dy);
} }
} }

View File

@ -65,21 +65,18 @@ public class GestureStrokeWithPreviewPoints extends GestureStroke {
private boolean needsSampling(final int x, final int y) { private boolean needsSampling(final int x, final int y) {
final int dx = x - mLastX; final int dx = x - mLastX;
final int dy = y - mLastY; final int dy = y - mLastY;
final boolean needsSampling = (dx * dx + dy * dy >= mMinPreviewSampleLengthSquare); return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
if (needsSampling) {
mLastX = x;
mLastY = y;
}
return needsSampling;
} }
@Override @Override
public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { public void addPoint(final int x, final int y, final int time, final boolean isMajorEvent) {
super.addPoint(x, y, time, isHistorical); super.addPoint(x, y, time, isMajorEvent);
if (mPreviewEventTimes.getLength() == 0 || isHistorical || needsSampling(x, y)) { if (isMajorEvent || needsSampling(x, y)) {
mPreviewEventTimes.add(time); mPreviewEventTimes.add(time);
mPreviewXCoordinates.add(x); mPreviewXCoordinates.add(x);
mPreviewYCoordinates.add(y); mPreviewYCoordinates.add(y);
mLastX = x;
mLastY = y;
} }
} }

View File

@ -45,7 +45,7 @@ public class BinaryDictionary extends Dictionary {
public static final int MAX_WORDS = 18; public static final int MAX_WORDS = 18;
public static final int MAX_SPACES = 16; public static final int MAX_SPACES = 16;
private static final String TAG = "BinaryDictionary"; private static final String TAG = BinaryDictionary.class.getSimpleName();
private static final int MAX_PREDICTIONS = 60; private static final int MAX_PREDICTIONS = 60;
private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS); private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS);

View File

@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantLock;
* cancellation or manual picks. This allows the keyboard to adapt to the typist over time. * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
*/ */
public class UserHistoryDictionary extends ExpandableDictionary { public class UserHistoryDictionary extends ExpandableDictionary {
private static final String TAG = "UserHistoryDictionary"; private static final String TAG = UserHistoryDictionary.class.getSimpleName();
public static final boolean DBG_SAVE_RESTORE = false; public static final boolean DBG_SAVE_RESTORE = false;
public static final boolean DBG_STRESS_TEST = false; public static final boolean DBG_STRESS_TEST = false;
public static final boolean DBG_ALWAYS_WRITE = false; public static final boolean DBG_ALWAYS_WRITE = false;