am 630d9c95: Introduce typing aware gesture detection

* commit '630d9c95e596c902b80dcb57eb0386e94290406d':
  Introduce typing aware gesture detection
main
Tadashi G. Takaoka 2012-09-25 05:46:39 -07:00 committed by Android Git Automerger
commit be6df93239
3 changed files with 57 additions and 15 deletions

View File

@ -318,12 +318,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key
return hasMessages(MSG_TYPING_STATE_EXPIRED); return hasMessages(MSG_TYPING_STATE_EXPIRED);
} }
// TODO: Remove "gesture off while fast typing" related dead code.
@Override @Override
public void startGestureOffWhileFastTypingTimer() { public void startGestureOffWhileFastTypingTimer() {
removeMessages(MSG_DISABLE_GESTURE_EXPIRED); // removeMessages(MSG_DISABLE_GESTURE_EXPIRED);
PointerTracker.setGestureOffWhileFastTyping(); // PointerTracker.setGestureOffWhileFastTyping();
sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED), // sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED),
mDisableGestureWhileFastTypingTimeout); // mDisableGestureWhileFastTypingTimeout);
} }
@Override @Override

View File

@ -169,6 +169,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private boolean mIsDetectingGesture = false; // per PointerTracker. private boolean mIsDetectingGesture = false; // per PointerTracker.
private static boolean sInGesture = false; private static boolean sInGesture = false;
private static long sGestureFirstDownTime; private static long sGestureFirstDownTime;
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;
@ -698,6 +699,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
private void onGestureDownEvent(final int x, final int y, final long eventTime) { private void onGestureDownEvent(final int x, final int y, final long eventTime) {
mIsDetectingGesture = true; mIsDetectingGesture = true;
mGestureStrokeWithPreviewPoints.setLastLetterTypingTime(eventTime, sLastLetterTypingUpTime);
final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime);
mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown,
true /* isMajorEvent */); true /* isMajorEvent */);
@ -842,7 +844,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (ProductionFlag.IS_EXPERIMENTAL) { if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
} }
onUpEventInternal(); onUpEventInternal(eventTime);
onDownEventInternal(x, y, eventTime); onDownEventInternal(x, y, eventTime);
} else { } else {
// HACK: If there are currently multiple touches, register the key even if // HACK: If there are currently multiple touches, register the key even if
@ -852,7 +854,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
// this hack. // this hack.
if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
onUpEventInternal(); onUpEventInternal(eventTime);
} }
if (!mIsDetectingGesture) { if (!mIsDetectingGesture) {
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
@ -897,7 +899,7 @@ public class PointerTracker implements PointerTrackerQueue.Element {
} }
} }
} }
onUpEventInternal(); onUpEventInternal(eventTime);
if (queue != null) { if (queue != null) {
queue.remove(this); queue.remove(this);
} }
@ -911,11 +913,11 @@ public class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_EVENT) { if (DEBUG_EVENT) {
printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
} }
onUpEventInternal(); onUpEventInternal(eventTime);
mKeyAlreadyProcessed = true; mKeyAlreadyProcessed = true;
} }
private void onUpEventInternal() { private void onUpEventInternal(final long eventTime) {
mTimerProxy.cancelKeyTimers(); mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false; mIsInSlidingKeyInput = false;
mIsDetectingGesture = false; mIsDetectingGesture = false;
@ -943,6 +945,10 @@ public class PointerTracker implements PointerTrackerQueue.Element {
} }
if (currentKey != null && !currentKey.isRepeatable()) { if (currentKey != null && !currentKey.isRepeatable()) {
detectAndSendKey(currentKey, mKeyX, mKeyY); detectAndSendKey(currentKey, mKeyX, mKeyY);
final int code = currentKey.mCode;
if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) {
sLastLetterTypingUpTime = eventTime;
}
} }
} }

View File

@ -34,8 +34,10 @@ public class GestureStroke {
private long mLastMajorEventTime; private long mLastMajorEventTime;
private int mLastMajorEventX; private int mLastMajorEventX;
private int mLastMajorEventY; private int mLastMajorEventY;
private boolean mAfterFastTyping;
private int mKeyWidth; private int mKeyWidth;
private int mStartGestureLengthThresholdAfterFastTyping; // pixel
private int mStartGestureLengthThreshold; // pixel private int mStartGestureLengthThreshold; // pixel
private int mMinGestureSamplingLength; // pixel private int mMinGestureSamplingLength; // pixel
private int mGestureRecognitionSpeedThreshold; // pixel / sec private int mGestureRecognitionSpeedThreshold; // pixel / sec
@ -45,7 +47,11 @@ public class GestureStroke {
private int mDetectFastMoveY; private int mDetectFastMoveY;
// TODO: Move some of these to resource. // TODO: Move some of these to resource.
private static final float START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH = 0.60f; 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 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;
@ -67,6 +73,8 @@ 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
* START_GESTURE_LENGTH_THRESHOLD_AFTER_FAST_TYPING_RATIO_TO_KEY_WIDTH);
mStartGestureLengthThreshold = mStartGestureLengthThreshold =
(int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH); (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH);
mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
@ -75,10 +83,33 @@ public class GestureStroke {
mDetectFastMoveSpeedThreshold = mDetectFastMoveSpeedThreshold =
(int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "setKeyboardGeometry: keyWidth=" + keyWidth); Log.d(TAG, "[" + mPointerId + "] setKeyboardGeometry: keyWidth=" + keyWidth
+ " tL0=" + mStartGestureLengthThresholdAfterFastTyping
+ " tL=" + mStartGestureLengthThreshold);
} }
} }
public void setLastLetterTypingTime(final long downTime, final long lastTypingTime) {
final long elpasedTimeAfterTyping = downTime - lastTypingTime;
if (elpasedTimeAfterTyping < GESTURE_AFTER_FAST_TYPING_DURATION_THRESHOLD) {
mAfterFastTyping = true;
}
if (DEBUG) {
Log.d(TAG, "[" + mPointerId + "] setLastTypingTime: dT=" + elpasedTimeAfterTyping
+ " afterFastTyping=" + mAfterFastTyping);
}
}
private int getStartGestureLengthThreshold(final int deltaTime) {
if (!mAfterFastTyping || deltaTime >= START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION) {
return mStartGestureLengthThreshold;
}
final int decayedThreshold =
(mStartGestureLengthThresholdAfterFastTyping - mStartGestureLengthThreshold)
* deltaTime / START_GESTURE_LENGTH_THRESHOLD_DECAY_DURATION;
return mStartGestureLengthThresholdAfterFastTyping - decayedThreshold;
}
public boolean isStartOfAGesture() { public boolean isStartOfAGesture() {
if (mDetectFastMoveTime == 0) { if (mDetectFastMoveTime == 0) {
return false; return false;
@ -92,10 +123,12 @@ public class GestureStroke {
final int deltaLength = getDistance( final int deltaLength = getDistance(
mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
mDetectFastMoveX, mDetectFastMoveY); mDetectFastMoveX, mDetectFastMoveY);
final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime);
final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD
&& deltaLength > mStartGestureLengthThreshold; && deltaLength > startGestureLengthThreshold;
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "isStartOfAGesture: dT=" + deltaTime + " dL=" + deltaLength Log.d(TAG, "[" + mPointerId + "] isStartOfAGesture: dT=" + deltaTime
+ " dL=" + deltaLength + " tL=" + startGestureLengthThreshold
+ " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : "")); + " points=" + size + (isStartOfAGesture ? " Detect start of a gesture" : ""));
} }
return isStartOfAGesture; return isStartOfAGesture;
@ -109,6 +142,7 @@ public class GestureStroke {
mYCoordinates.setLength(0); mYCoordinates.setLength(0);
mLastMajorEventTime = 0; mLastMajorEventTime = 0;
mDetectFastMoveTime = 0; mDetectFastMoveTime = 0;
mAfterFastTyping = false;
} }
private void appendPoint(final int x, final int y, final int time) { private void appendPoint(final int x, final int y, final int time) {
@ -135,12 +169,13 @@ public class GestureStroke {
final int pixelsPerSec = pixels * MSEC_PER_SEC; final int pixelsPerSec = pixels * MSEC_PER_SEC;
if (DEBUG) { if (DEBUG) {
final float speed = (float)pixelsPerSec / msecs / mKeyWidth; final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
Log.d(TAG, String.format("Speed=%.3f keyWidth/sec", speed)); Log.d(TAG, String.format("[" + mPointerId + "] speed=%.3f", 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, "Detect fast move: T=" + time + " points = " + size); Log.d(TAG, "[" + mPointerId + "] detect fast move: T="
+ time + " points = " + size);
} }
mDetectFastMoveTime = time; mDetectFastMoveTime = time;
mDetectFastMoveX = x; mDetectFastMoveX = x;