am 6052a81e: Merge "Fix gesture detection algorithm" into jb-mr1-dev
* commit '6052a81e08758c738b80fb8bde6babe97bcdd261': Fix gesture detection algorithmmain
commit
e43623afb4
|
@ -172,8 +172,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;
|
||||||
|
@ -310,9 +310,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;
|
||||||
|
@ -335,10 +336,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,
|
||||||
|
@ -367,9 +369,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,
|
||||||
|
@ -385,7 +388,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();
|
||||||
|
@ -394,6 +397,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);
|
||||||
|
@ -564,10 +571,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;
|
||||||
|
synchronized (sAggregratedPointers) {
|
||||||
|
sAggregratedPointers.reset();
|
||||||
|
sLastRecognitionPointSize = 0;
|
||||||
|
sLastRecognitionTime = 0;
|
||||||
mListener.onStartBatchInput();
|
mListener.onStartBatchInput();
|
||||||
|
}
|
||||||
final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
|
final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this;
|
||||||
mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
|
mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker);
|
||||||
}
|
}
|
||||||
|
@ -582,7 +594,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);
|
||||||
}
|
}
|
||||||
|
@ -595,37 +608,20 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
|
||||||
private void mayEndBatchInput(final long eventTime) {
|
private void mayEndBatchInput(final long eventTime) {
|
||||||
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;
|
||||||
sLastBatchInputTime = eventTime;
|
sLastBatchInputTime = eventTime;
|
||||||
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) {
|
||||||
|
@ -695,18 +691,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
|
||||||
|
@ -939,9 +928,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
|
||||||
mayEndBatchInput(eventTime);
|
mayEndBatchInput(eventTime);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -955,7 +942,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);
|
||||||
|
@ -1043,7 +1029,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
Loading…
Reference in New Issue