diff options
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/PointerTracker.java')
-rw-r--r-- | java/src/com/android/inputmethod/keyboard/PointerTracker.java | 265 |
1 files changed, 230 insertions, 35 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index babf6ec99..ea4d93a4a 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -22,7 +22,10 @@ import android.view.MotionEvent; import android.view.View; import android.widget.TextView; +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; +import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.define.ProductionFlag; @@ -36,6 +39,11 @@ public class PointerTracker { private static final boolean DEBUG_LISTENER = false; private static boolean DEBUG_MODE = LatinImeLogger.sDBG; + // TODO: There should be an option to turn on/off the gesture input. + private static boolean sIsGestureEnabled = true; + + private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec + public interface KeyEventHandler { /** * Get KeyDetector object that is used for this PointerTracker. @@ -110,9 +118,14 @@ public class PointerTracker { private static LatinKeyboardView.PointerTrackerParams sParams; private static int sTouchNoiseThresholdDistanceSquared; private static boolean sNeedsPhantomSuddenMoveEventHack; + private static boolean sConfigGestureInputEnabledByBuildConfig; private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); private static PointerTrackerQueue sPointerTrackerQueue; + // HACK: Change gesture detection criteria depending on this variable. + // TODO: Find more comprehensive ways to detect a gesture start. + // True when the previous user input was a gesture input, not a typing input. + private static boolean sWasInGesture; public final int mPointerId; @@ -125,6 +138,14 @@ public class PointerTracker { private int mKeyQuarterWidthSquared; private final TextView mKeyPreviewText; + private boolean mIsAlphabetKeyboard; + private boolean mIsPossibleGesture = false; + private boolean mInGesture = false; + + // TODO: Remove these variables + private int mLastRecognitionPointSize = 0; + private long mLastRecognitionTime = 0; + // The position and time at which first down event occurred. private long mDownTime; private long mUpTime; @@ -148,9 +169,6 @@ public class PointerTracker { // true if this pointer has been long-pressed and is showing a more keys panel. private boolean mIsShowingMoreKeysPanel; - // true if this pointer is repeatable key - private boolean mIsRepeatableKey; - // true if this pointer is in sliding key input boolean mIsInSlidingKeyInput; @@ -164,16 +182,21 @@ public class PointerTracker { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); + private final GestureStroke mGestureStroke; + public static void init(boolean hasDistinctMultitouch, - boolean needsPhantomSuddenMoveEventHack) { + boolean needsPhantomSuddenMoveEventHack, + boolean gestureInputEnabledByBuildConfig) { if (hasDistinctMultitouch) { sPointerTrackerQueue = new PointerTrackerQueue(); } else { sPointerTrackerQueue = null; } sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; + sConfigGestureInputEnabledByBuildConfig = gestureInputEnabledByBuildConfig; setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT); + updateGestureInputEnabledState(null); } public static void setParameters(LatinKeyboardView.PointerTrackerParams params) { @@ -182,6 +205,16 @@ public class PointerTracker { params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); } + private static void updateGestureInputEnabledState(Keyboard keyboard) { + if (!sConfigGestureInputEnabledByBuildConfig + || AccessibilityUtils.getInstance().isTouchExplorationEnabled() + || (keyboard != null && keyboard.mId.passwordInput())) { + sIsGestureEnabled = false; + } else { + sIsGestureEnabled = true; + } + } + public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { final ArrayList<PointerTracker> trackers = sTrackers; @@ -210,6 +243,8 @@ public class PointerTracker { // Mark that keyboard layout has been changed. tracker.mKeyboardLayoutHasBeenChanged = true; } + final Keyboard keyboard = keyDetector.getKeyboard(); + updateGestureInputEnabledState(keyboard); } public static void dismissAllKeyPreviews() { @@ -219,10 +254,41 @@ public class PointerTracker { } } - public PointerTracker(int id, KeyEventHandler handler) { + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + private static InputPointers getIncrementalBatchPoints() { + // TODO: Avoid creating a new instance here? + final InputPointers pointers = new InputPointers(GestureStroke.DEFAULT_CAPACITY); + for (final PointerTracker tracker : sTrackers) { + tracker.mGestureStroke.appendIncrementalBatchPoints(pointers); + } + return pointers; + } + + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + private static InputPointers getAllBatchPoints() { + // TODO: Avoid creating a new instance here? + final InputPointers pointers = new InputPointers(GestureStroke.DEFAULT_CAPACITY); + for (final PointerTracker tracker : sTrackers) { + tracker.mGestureStroke.appendAllBatchPoints(pointers); + } + return pointers; + } + + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + public static void clearBatchInputPointsOfAllPointerTrackers() { + for (final PointerTracker tracker : sTrackers) { + tracker.mGestureStroke.reset(); + } + } + + private PointerTracker(int id, KeyEventHandler handler) { if (handler == null) throw new NullPointerException(); mPointerId = id; + mGestureStroke = new GestureStroke(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -236,16 +302,15 @@ public class PointerTracker { // Returns true if keyboard has been changed by this callback. private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { + if (mInGesture) { + return false; + } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); if (DEBUG_LISTENER) { Log.d(TAG, "onPress : " + KeyDetector.printableCode(key) + " ignoreModifier=" + ignoreModifierKey + " enabled=" + key.isEnabled()); } - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange(key, - ignoreModifierKey); - } if (ignoreModifierKey) { return false; } @@ -293,6 +358,9 @@ public class PointerTracker { // Note that we need primaryCode argument because the keyboard may in shifted state and the // primaryCode is different from {@link Key#mCode}. private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { + if (mInGesture) { + return; + } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); if (DEBUG_LISTENER) { Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) @@ -323,6 +391,16 @@ public class PointerTracker { private void setKeyDetectorInner(KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); + mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); + mGestureStroke.setGestureSampleLength( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); + final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); + if (newKey != mCurrentKey) { + if (mDrawingProxy != null) { + setReleasedKeyGraphics(mCurrentKey); + } + mCurrentKey = newKey; + } final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; } @@ -386,7 +464,7 @@ public class PointerTracker { return; } - if (!key.noKeyPreview()) { + if (!key.noKeyPreview() && !mInGesture) { mDrawingProxy.showKeyPreview(this); } updatePressKeyGraphics(key); @@ -457,6 +535,53 @@ public class PointerTracker { return newKey; } + private void startBatchInput() { + if (DEBUG_LISTENER) { + Log.d(TAG, "onStartBatchInput"); + } + mInGesture = true; + mListener.onStartBatchInput(); + } + + private void updateBatchInput(InputPointers batchPoints) { + if (DEBUG_LISTENER) { + Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); + } + mListener.onUpdateBatchInput(batchPoints); + } + + private void endBatchInput(InputPointers batchPoints) { + if (DEBUG_LISTENER) { + Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); + } + mListener.onEndBatchInput(batchPoints); + clearBatchInputRecognitionStateOfThisPointerTracker(); + clearBatchInputPointsOfAllPointerTrackers(); + sWasInGesture = true; + } + + private void abortBatchInput() { + clearBatchInputRecognitionStateOfThisPointerTracker(); + clearBatchInputPointsOfAllPointerTrackers(); + } + + private void clearBatchInputRecognitionStateOfThisPointerTracker() { + mIsPossibleGesture = false; + mInGesture = false; + mLastRecognitionPointSize = 0; + mLastRecognitionTime = 0; + } + + private boolean updateBatchInputRecognitionState(long eventTime, int size) { + if (size > mLastRecognitionPointSize + && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) { + mLastRecognitionPointSize = size; + mLastRecognitionTime = eventTime; + return true; + } + return false; + } + public void processMotionEvent(int action, int x, int y, long eventTime, KeyEventHandler handler) { switch (action) { @@ -469,7 +594,7 @@ public class PointerTracker { onUpEvent(x, y, eventTime); break; case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime); + onMoveEvent(x, y, eventTime, null); break; case MotionEvent.ACTION_CANCEL: onCancelEvent(x, y, eventTime); @@ -504,8 +629,8 @@ public class PointerTracker { } final PointerTrackerQueue queue = sPointerTrackerQueue; + final Key key = getKeyOn(x, y); if (queue != null) { - final Key key = getKeyOn(x, y); if (key != null && key.isModifier()) { // Before processing a down event of modifier key, all pointers already being // tracked should be released. @@ -514,6 +639,15 @@ public class PointerTracker { queue.add(this); } onDownEventInternal(x, y, eventTime); + if (queue != null && queue.size() == 1) { + mIsPossibleGesture = false; + // A gesture should start only from the letter key. + if (sIsGestureEnabled && mIsAlphabetKeyboard && key != null + && Keyboard.isLetterCode(key.mCode)) { + mIsPossibleGesture = true; + mGestureStroke.addPoint(x, y, 0, false); + } + } } private void onDownEventInternal(int x, int y, long eventTime) { @@ -525,7 +659,6 @@ public class PointerTracker { || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; mKeyAlreadyProcessed = false; - mIsRepeatableKey = false; mIsInSlidingKeyInput = false; mIgnoreModifierKey = false; if (key != null) { @@ -549,16 +682,59 @@ public class PointerTracker { mIsInSlidingKeyInput = true; } - public void onMoveEvent(int x, int y, long eventTime) { + private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime, + boolean isHistorical, Key key) { + final int gestureTime = (int)(eventTime - tracker.getDownTime()); + if (sIsGestureEnabled && mIsPossibleGesture) { + final GestureStroke stroke = mGestureStroke; + stroke.addPoint(x, y, gestureTime, isHistorical); + if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) { + startBatchInput(); + } + } + + if (key != null && mInGesture) { + final InputPointers batchPoints = getIncrementalBatchPoints(); + if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { + updateBatchInput(batchPoints); + } + } + } + + public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) { if (DEBUG_MOVE_EVENT) printTouchEvent("onMoveEvent:", x, y, eventTime); if (mKeyAlreadyProcessed) return; + if (me != null) { + // Add historical points to gesture path. + final int pointerIndex = me.findPointerIndex(mPointerId); + final int historicalSize = me.getHistorySize(); + for (int h = 0; h < historicalSize; h++) { + final int historicalX = (int)me.getHistoricalX(pointerIndex, h); + final int historicalY = (int)me.getHistoricalY(pointerIndex, h); + final long historicalTime = me.getHistoricalEventTime(h); + onGestureMoveEvent(this, historicalX, historicalY, historicalTime, + true /* isHistorical */, null); + } + } + final int lastX = mLastX; final int lastY = mLastY; final Key oldKey = mCurrentKey; Key key = onMoveKey(x, y); + + // Register move event on gesture tracker. + onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); + if (mInGesture) { + mIgnoreModifierKey = true; + mTimerProxy.cancelLongPressTimer(); + mIsInSlidingKeyInput = true; + mCurrentKey = null; + setReleasedKeyGraphics(oldKey); + } + if (key != null) { if (oldKey == null) { // The pointer has been slid in to the new key, but the finger was not on any keys. @@ -608,9 +784,18 @@ public class PointerTracker { if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); } - onUpEventInternal(); + onUpEventInternal(x, y, eventTime); onDownEventInternal(x, y, eventTime); } else { + // HACK: If there are currently multiple touches, register the key even if + // the finger slides off the key. This defends against noise from some + // touch panels when there are close multiple touches. + // Caveat: When in chording input mode with a modifier key, we don't use + // this hack. + if (me != null && me.getPointerCount() > 1 + && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { + onUpEventInternal(x, y, eventTime); + } mKeyAlreadyProcessed = true; setReleasedKeyGraphics(oldKey); } @@ -639,16 +824,18 @@ public class PointerTracker { final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - if (mCurrentKey != null && mCurrentKey.isModifier()) { - // Before processing an up event of modifier key, all pointers already being - // tracked should be released. - queue.releaseAllPointersExcept(this, eventTime); - } else { - queue.releaseAllPointersOlderThan(this, eventTime); + if (!mInGesture) { + if (mCurrentKey != null && mCurrentKey.isModifier()) { + // Before processing an up event of modifier key, all pointers already being + // tracked should be released. + queue.releaseAllPointersExcept(this, eventTime); + } else { + queue.releaseAllPointersOlderThan(this, eventTime); + } } queue.remove(this); } - onUpEventInternal(); + onUpEventInternal(x, y, eventTime); } // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. @@ -657,11 +844,11 @@ public class PointerTracker { public void onPhantomUpEvent(int x, int y, long eventTime) { if (DEBUG_EVENT) printTouchEvent("onPhntEvent:", x, y, eventTime); - onUpEventInternal(); + onUpEventInternal(x, y, eventTime); mKeyAlreadyProcessed = true; } - private void onUpEventInternal() { + private void onUpEventInternal(int x, int y, long eventTime) { mTimerProxy.cancelKeyTimers(); mIsInSlidingKeyInput = false; // Release the last pressed key. @@ -670,14 +857,28 @@ public class PointerTracker { mDrawingProxy.dismissMoreKeysPanel(); mIsShowingMoreKeysPanel = false; } + + if (mInGesture) { + // Register up event on gesture tracker. + // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, + // and/or tapping mode? + endBatchInput(getAllBatchPoints()); + if (mCurrentKey != null) { + callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true); + mCurrentKey = null; + } + return; + } + if (mKeyAlreadyProcessed) return; - if (!mIsRepeatableKey) { + if (mCurrentKey != null && !mCurrentKey.isRepeatable()) { detectAndSendKey(mCurrentKey, mKeyX, mKeyY); } } public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { + abortBatchInput(); onLongPressed(); onDownEvent(x, y, SystemClock.uptimeMillis(), handler); mIsShowingMoreKeysPanel = true; @@ -715,12 +916,9 @@ public class PointerTracker { } private void startRepeatKey(Key key) { - if (key != null && key.isRepeatable()) { + if (key != null && key.isRepeatable() && !mInGesture) { onRegisterKey(key); mTimerProxy.startKeyRepeatTimer(this); - mIsRepeatableKey = true; - } else { - mIsRepeatableKey = false; } } @@ -748,7 +946,7 @@ public class PointerTracker { } private void startLongPressTimer(Key key) { - if (key != null && key.isLongPressEnabled()) { + if (key != null && key.isLongPressEnabled() && !mInGesture) { mTimerProxy.startLongPressTimer(this); } } @@ -762,16 +960,13 @@ public class PointerTracker { int code = key.mCode; callListenerOnCodeInput(key, code, x, y); callListenerOnRelease(key, code, false); + sWasInGesture = false; } - private long mPreviousEventTime; - private void printTouchEvent(String title, int x, int y, long eventTime) { final Key key = mKeyDetector.detectHitKey(x, y); final String code = KeyDetector.printableCode(key); - final long delta = eventTime - mPreviousEventTime; Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, - (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code)); - mPreviousEventTime = eventTime; + (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code)); } } |