am 3088e0c9: Merge "Fix gesture start detection algorithm" into jb-mr1-dev
* commit '3088e0c9e97cee4763d1563eb847691adc1da317': Fix gesture start detection algorithmmain
commit
58d9cc3da7
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue