From 630d9c95e596c902b80dcb57eb0386e94290406d Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 25 Sep 2012 14:23:23 +0900 Subject: [PATCH] Introduce typing aware gesture detection Bug: 7218902 Change-Id: I11ad85914bf991eca513e04ed8b5d12779101dda --- .../keyboard/MainKeyboardView.java | 9 ++-- .../inputmethod/keyboard/PointerTracker.java | 16 +++++-- .../keyboard/internal/GestureStroke.java | 47 ++++++++++++++++--- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index f5c1b7a0c..f1fcfe785 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -318,12 +318,13 @@ public class MainKeyboardView extends KeyboardView implements PointerTracker.Key return hasMessages(MSG_TYPING_STATE_EXPIRED); } + // TODO: Remove "gesture off while fast typing" related dead code. @Override public void startGestureOffWhileFastTypingTimer() { - removeMessages(MSG_DISABLE_GESTURE_EXPIRED); - PointerTracker.setGestureOffWhileFastTyping(); - sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED), - mDisableGestureWhileFastTypingTimeout); +// removeMessages(MSG_DISABLE_GESTURE_EXPIRED); +// PointerTracker.setGestureOffWhileFastTyping(); +// sendMessageDelayed(obtainMessage(MSG_DISABLE_GESTURE_EXPIRED), +// mDisableGestureWhileFastTypingTimeout); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 0778ad902..d6c567ef7 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -169,6 +169,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { private boolean mIsDetectingGesture = false; // per PointerTracker. private static boolean sInGesture = false; private static long sGestureFirstDownTime; + private static long sLastLetterTypingUpTime; private static final InputPointers sAggregratedPointers = new InputPointers( GestureStroke.DEFAULT_CAPACITY); 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) { mIsDetectingGesture = true; + mGestureStrokeWithPreviewPoints.setLastLetterTypingTime(eventTime, sLastLetterTypingUpTime); final int elapsedTimeFromFirstDown = (int)(eventTime - sGestureFirstDownTime); mGestureStrokeWithPreviewPoints.addPoint(x, y, elapsedTimeFromFirstDown, true /* isMajorEvent */); @@ -842,7 +844,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); } - onUpEventInternal(); + onUpEventInternal(eventTime); onDownEventInternal(x, y, eventTime); } else { // HACK: If there are currently multiple touches, register the key even if @@ -852,7 +854,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { // this hack. if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { - onUpEventInternal(); + onUpEventInternal(eventTime); } if (!mIsDetectingGesture) { mKeyAlreadyProcessed = true; @@ -897,7 +899,7 @@ public class PointerTracker implements PointerTrackerQueue.Element { } } } - onUpEventInternal(); + onUpEventInternal(eventTime); if (queue != null) { queue.remove(this); } @@ -911,11 +913,11 @@ public class PointerTracker implements PointerTrackerQueue.Element { if (DEBUG_EVENT) { printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); } - onUpEventInternal(); + onUpEventInternal(eventTime); mKeyAlreadyProcessed = true; } - private void onUpEventInternal() { + private void onUpEventInternal(final long eventTime) { mTimerProxy.cancelKeyTimers(); mIsInSlidingKeyInput = false; mIsDetectingGesture = false; @@ -943,6 +945,10 @@ public class PointerTracker implements PointerTrackerQueue.Element { } if (currentKey != null && !currentKey.isRepeatable()) { detectAndSendKey(currentKey, mKeyX, mKeyY); + final int code = currentKey.mCode; + if (Keyboard.isLetterCode(code) && code != Keyboard.CODE_SPACE) { + sLastLetterTypingUpTime = eventTime; + } } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 193c3a4ba..9fe6fa3f8 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -34,8 +34,10 @@ public class GestureStroke { private long mLastMajorEventTime; private int mLastMajorEventX; private int mLastMajorEventY; + private boolean mAfterFastTyping; private int mKeyWidth; + private int mStartGestureLengthThresholdAfterFastTyping; // pixel private int mStartGestureLengthThreshold; // pixel private int mMinGestureSamplingLength; // pixel private int mGestureRecognitionSpeedThreshold; // pixel / sec @@ -45,7 +47,11 @@ public class GestureStroke { private int mDetectFastMoveY; // 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 MIN_GESTURE_RECOGNITION_TIME = 100; // msec 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) { mKeyWidth = keyWidth; // 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 = (int)(keyWidth * START_GESTURE_LENGTH_THRESHOLD_RATIO_TO_KEY_WIDTH); mMinGestureSamplingLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH); @@ -75,10 +83,33 @@ public class GestureStroke { mDetectFastMoveSpeedThreshold = (int)(keyWidth * DETECT_FAST_MOVE_SPEED_THRESHOLD_RATIO_TO_KEY_WIDTH); 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() { if (mDetectFastMoveTime == 0) { return false; @@ -92,10 +123,12 @@ public class GestureStroke { final int deltaLength = getDistance( mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex), mDetectFastMoveX, mDetectFastMoveY); + final int startGestureLengthThreshold = getStartGestureLengthThreshold(deltaTime); final boolean isStartOfAGesture = deltaTime > START_GESTURE_DURATION_THRESHOLD - && deltaLength > mStartGestureLengthThreshold; + && deltaLength > startGestureLengthThreshold; 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" : "")); } return isStartOfAGesture; @@ -109,6 +142,7 @@ public class GestureStroke { mYCoordinates.setLength(0); mLastMajorEventTime = 0; mDetectFastMoveTime = 0; + mAfterFastTyping = false; } 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; if (DEBUG) { 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) if (mDetectFastMoveTime == 0 && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) { 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; mDetectFastMoveX = x;