diff options
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/PointerTracker.java')
-rw-r--r-- | java/src/com/android/inputmethod/keyboard/PointerTracker.java | 496 |
1 files changed, 374 insertions, 122 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index babf6ec99..d4902ec28 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -16,26 +16,37 @@ package com.android.inputmethod.keyboard; +import android.content.res.TypedArray; import android.os.SystemClock; import android.util.Log; 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.GestureStrokeWithPreviewPoints; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.ResearchLogger; +import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; -public class PointerTracker { +public class PointerTracker implements PointerTrackerQueue.Element { private static final String TAG = PointerTracker.class.getSimpleName(); private static final boolean DEBUG_EVENT = false; private static final boolean DEBUG_MOVE_EVENT = false; private static final boolean DEBUG_LISTENER = false; private static boolean DEBUG_MODE = LatinImeLogger.sDBG; + /** True if {@link PointerTracker}s should handle gesture events. */ + private static boolean sShouldHandleGesture = false; + private static boolean sMainDictionaryAvailable = false; + private static boolean sGestureHandlingEnabledByInputField = false; + private static boolean sGestureHandlingEnabledByUser = false; + public interface KeyEventHandler { /** * Get KeyDetector object that is used for this PointerTracker. @@ -65,13 +76,13 @@ public class PointerTracker { public interface DrawingProxy extends MoreKeysPanel.Controller { public void invalidateKey(Key key); - public TextView inflateKeyPreviewText(); public void showKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker); + public void showGesturePreviewTrail(PointerTracker tracker, boolean isOldestTracker); } public interface TimerProxy { - public void startTypingStateTimer(); + public void startTypingStateTimer(Key typedKey); public boolean isTypingState(); public void startKeyRepeatTimer(PointerTracker tracker); public void startLongPressTimer(PointerTracker tracker); @@ -84,7 +95,7 @@ public class PointerTracker { public static class Adapter implements TimerProxy { @Override - public void startTypingStateTimer() {} + public void startTypingStateTimer(Key typedKey) {} @Override public boolean isTypingState() { return false; } @Override @@ -106,12 +117,39 @@ public class PointerTracker { } } + static class PointerTrackerParams { + public final boolean mSlidingKeyInputEnabled; + public final int mTouchNoiseThresholdTime; + public final float mTouchNoiseThresholdDistance; + public final int mTouchNoiseThresholdDistanceSquared; + + public static final PointerTrackerParams DEFAULT = new PointerTrackerParams(); + + private PointerTrackerParams() { + mSlidingKeyInputEnabled = false; + mTouchNoiseThresholdTime = 0; + mTouchNoiseThresholdDistance = 0.0f; + mTouchNoiseThresholdDistanceSquared = 0; + } + + public PointerTrackerParams(TypedArray mainKeyboardViewAttr) { + mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean( + R.styleable.MainKeyboardView_slidingKeyInputEnable, false); + mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); + final float touchNouseThresholdDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); + mTouchNoiseThresholdDistance = touchNouseThresholdDistance; + mTouchNoiseThresholdDistanceSquared = + (int)(touchNouseThresholdDistance * touchNouseThresholdDistance); + } + } + // Parameters for pointer handling. - private static LatinKeyboardView.PointerTrackerParams sParams; - private static int sTouchNoiseThresholdDistanceSquared; + private static PointerTrackerParams sParams; private static boolean sNeedsPhantomSuddenMoveEventHack; - private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); + private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); private static PointerTrackerQueue sPointerTrackerQueue; public final int mPointerId; @@ -123,7 +161,14 @@ public class PointerTracker { private Keyboard mKeyboard; private int mKeyQuarterWidthSquared; - private final TextView mKeyPreviewText; + + private boolean mIsDetectingGesture = false; // per PointerTracker. + private static boolean sInGesture = false; + private static long sGestureFirstDownTime; + private static final InputPointers sAggregratedPointers = new InputPointers( + GestureStroke.DEFAULT_CAPACITY); + private static int sLastRecognitionPointSize = 0; + private static long sLastRecognitionTime = 0; // The position and time at which first down event occurred. private long mDownTime; @@ -148,9 +193,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,6 +206,8 @@ public class PointerTracker { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); + private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; + public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { if (hasDistinctMultitouch) { @@ -172,17 +216,32 @@ public class PointerTracker { sPointerTrackerQueue = null; } sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; + sParams = PointerTrackerParams.DEFAULT; + } + + public static void setParameters(final TypedArray mainKeyboardViewAttr) { + sParams = new PointerTrackerParams(mainKeyboardViewAttr); + } - setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT); + private static void updateGestureHandlingMode() { + sShouldHandleGesture = sMainDictionaryAvailable + && sGestureHandlingEnabledByInputField + && sGestureHandlingEnabledByUser + && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); } - public static void setParameters(LatinKeyboardView.PointerTrackerParams params) { - sParams = params; - sTouchNoiseThresholdDistanceSquared = (int)( - params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); + // Note that this method is called from a non-UI thread. + public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { + sMainDictionaryAvailable = mainDictionaryAvailable; + updateGestureHandlingMode(); } - public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { + public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) { + sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; + updateGestureHandlingMode(); + } + + public static PointerTracker getPointerTracker(final int id, final KeyEventHandler handler) { final ArrayList<PointerTracker> trackers = sTrackers; // Create pointer trackers until we can get 'id+1'-th tracker, if needed. @@ -198,54 +257,58 @@ public class PointerTracker { return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; } - public static void setKeyboardActionListener(KeyboardActionListener listener) { - for (final PointerTracker tracker : sTrackers) { + public static void setKeyboardActionListener(final KeyboardActionListener listener) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.mListener = listener; } } - public static void setKeyDetector(KeyDetector keyDetector) { - for (final PointerTracker tracker : sTrackers) { + public static void setKeyDetector(final KeyDetector keyDetector) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.setKeyDetectorInner(keyDetector); // Mark that keyboard layout has been changed. tracker.mKeyboardLayoutHasBeenChanged = true; } + final Keyboard keyboard = keyDetector.getKeyboard(); + sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput(); + updateGestureHandlingMode(); } - public static void dismissAllKeyPreviews() { - for (final PointerTracker tracker : sTrackers) { - tracker.getKeyPreviewText().setVisibility(View.INVISIBLE); + public static void setReleasedKeyGraphicsToAllKeys() { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.setReleasedKeyGraphics(tracker.mCurrentKey); } } - public PointerTracker(int id, KeyEventHandler handler) { - if (handler == null) + private PointerTracker(final int id, final KeyEventHandler handler) { + if (handler == null) { throw new NullPointerException(); + } mPointerId = id; + mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); mTimerProxy = handler.getTimerProxy(); - mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText(); - } - - public TextView getKeyPreviewText() { - return mKeyPreviewText; } // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) { + if (sInGesture) { + 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; } @@ -253,9 +316,7 @@ public class PointerTracker { mListener.onPressKey(key.mCode); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; - if (!key.altCodeWhileTyping() && !key.isModifier()) { - mTimerProxy.startTypingStateTimer(); - } + mTimerProxy.startTypingStateTimer(key); return keyboardLayoutHasBeenChanged; } return false; @@ -263,13 +324,14 @@ 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 callListenerOnCodeInput(Key key, int primaryCode, int x, int y) { + private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x, + final int y) { final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); - final int code = altersCode ? key.mAltCode : primaryCode; + final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { - Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText - + " x=" + x + " y=" + y + Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + + " text=" + key.getOutputText() + " x=" + x + " y=" + y + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode + " enabled=" + key.isEnabled()); } @@ -283,7 +345,7 @@ public class PointerTracker { // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. if (key.isEnabled() || altersCode) { if (code == Keyboard.CODE_OUTPUT_TEXT) { - mListener.onTextInput(key.mOutputText); + mListener.onTextInput(key.getOutputText()); } else if (code != Keyboard.CODE_UNSPECIFIED) { mListener.onCodeInput(code, x, y); } @@ -292,7 +354,11 @@ 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) { + private void callListenerOnRelease(final Key key, final int primaryCode, + final boolean withSliding) { + if (sInGesture) { + return; + } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); if (DEBUG_LISTENER) { Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) @@ -312,21 +378,31 @@ public class PointerTracker { } private void callListenerOnCancelInput() { - if (DEBUG_LISTENER) + if (DEBUG_LISTENER) { Log.d(TAG, "onCancelInput"); + } if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_callListenerOnCancelInput(); } mListener.onCancelInput(); } - private void setKeyDetectorInner(KeyDetector keyDetector) { + private void setKeyDetectorInner(final KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); + mGestureStrokeWithPreviewPoints.setKeyboardGeometry(mKeyboard.mMostCommonKeyWidth); + final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); + if (newKey != mCurrentKey) { + if (mDrawingProxy != null) { + setReleasedKeyGraphics(mCurrentKey); + } + // Keep {@link #mCurrentKey} that comes from previous keyboard. + } final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; } + @Override public boolean isInSlidingKeyInput() { return mIsInSlidingKeyInput; } @@ -335,15 +411,16 @@ public class PointerTracker { return mCurrentKey; } + @Override public boolean isModifier() { return mCurrentKey != null && mCurrentKey.isModifier(); } - public Key getKeyOn(int x, int y) { + public Key getKeyOn(final int x, final int y) { return mKeyDetector.detectHitKey(x, y); } - private void setReleasedKeyGraphics(Key key) { + private void setReleasedKeyGraphics(final Key key) { mDrawingProxy.dismissKeyPreview(this); if (key == null) { return; @@ -361,20 +438,20 @@ public class PointerTracker { } if (key.altCodeWhileTyping()) { - final int altCode = key.mAltCode; + final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { updateReleaseKeyGraphics(altKey); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.mAltCode == altCode) { + if (k != key && k.getAltCode() == altCode) { updateReleaseKeyGraphics(k); } } } } - private void setPressedKeyGraphics(Key key) { + private void setPressedKeyGraphics(final Key key) { if (key == null) { return; } @@ -386,7 +463,7 @@ public class PointerTracker { return; } - if (!key.noKeyPreview()) { + if (!key.noKeyPreview() && !sInGesture) { mDrawingProxy.showKeyPreview(this); } updatePressKeyGraphics(key); @@ -400,29 +477,33 @@ public class PointerTracker { } if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { - final int altCode = key.mAltCode; + final int altCode = key.getAltCode(); final Key altKey = mKeyboard.getKey(altCode); if (altKey != null) { updatePressKeyGraphics(altKey); } for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { - if (k != key && k.mAltCode == altCode) { + if (k != key && k.getAltCode() == altCode) { updatePressKeyGraphics(k); } } } } - private void updateReleaseKeyGraphics(Key key) { + private void updateReleaseKeyGraphics(final Key key) { key.onReleased(); mDrawingProxy.invalidateKey(key); } - private void updatePressKeyGraphics(Key key) { + private void updatePressKeyGraphics(final Key key) { key.onPressed(); mDrawingProxy.invalidateKey(key); } + public GestureStrokeWithPreviewPoints getGestureStrokeWithPreviewPoints() { + return mGestureStrokeWithPreviewPoints; + } + public int getLastX() { return mLastX; } @@ -435,30 +516,98 @@ public class PointerTracker { return mDownTime; } - private Key onDownKey(int x, int y, long eventTime) { + private Key onDownKey(final int x, final int y, final long eventTime) { mDownTime = eventTime; return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); } - private Key onMoveKeyInternal(int x, int y) { + private Key onMoveKeyInternal(final int x, final int y) { mLastX = x; mLastY = y; return mKeyDetector.detectHitKey(x, y); } - private Key onMoveKey(int x, int y) { + private Key onMoveKey(final int x, final int y) { return onMoveKeyInternal(x, y); } - private Key onMoveToNewKey(Key newKey, int x, int y) { + private Key onMoveToNewKey(final Key newKey, final int x, final int y) { mCurrentKey = newKey; mKeyX = x; mKeyY = y; return newKey; } - public void processMotionEvent(int action, int x, int y, long eventTime, - KeyEventHandler handler) { + private static int getActivePointerTrackerCount() { + return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); + } + + private void mayStartBatchInput() { + if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { + return; + } + if (DEBUG_LISTENER) { + Log.d(TAG, "onStartBatchInput"); + } + sInGesture = true; + mListener.onStartBatchInput(); + final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; + mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + } + + private void updateBatchInput(final long eventTime) { + synchronized (sAggregratedPointers) { + mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers); + final int size = sAggregratedPointers.getPointerSize(); + if (size > sLastRecognitionPointSize + && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { + sLastRecognitionPointSize = size; + sLastRecognitionTime = eventTime; + if (DEBUG_LISTENER) { + Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size); + } + mListener.onUpdateBatchInput(sAggregratedPointers); + } + } + final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; + mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + } + + private void mayEndBatchInput() { + synchronized (sAggregratedPointers) { + mGestureStrokeWithPreviewPoints.appendAllBatchPoints(sAggregratedPointers); + mGestureStrokeWithPreviewPoints.reset(); + if (getActivePointerTrackerCount() == 1) { + if (DEBUG_LISTENER) { + Log.d(TAG, "onEndBatchInput: batchPoints=" + + sAggregratedPointers.getPointerSize()); + } + sInGesture = false; + mListener.onEndBatchInput(sAggregratedPointers); + clearBatchInputPointsOfAllPointerTrackers(); + } + } + final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; + 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, + final KeyEventHandler handler) { switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: @@ -469,7 +618,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); @@ -477,9 +626,11 @@ public class PointerTracker { } } - public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) { - if (DEBUG_EVENT) + public void onDownEvent(final int x, final int y, final long eventTime, + final KeyEventHandler handler) { + if (DEBUG_EVENT) { printTouchEvent("onDownEvent:", x, y, eventTime); + } mDrawingProxy = handler.getDrawingProxy(); mTimerProxy = handler.getTimerProxy(); @@ -491,7 +642,7 @@ public class PointerTracker { final int dx = x - mLastX; final int dy = y - mLastY; final int distanceSquared = (dx * dx + dy * dy); - if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { + if (distanceSquared < sParams.mTouchNoiseThresholdDistanceSquared) { if (DEBUG_MODE) Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT + " distance=" + distanceSquared); @@ -503,9 +654,9 @@ public class PointerTracker { } } + final Key key = getKeyOn(x, y); final PointerTrackerQueue queue = sPointerTrackerQueue; 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,9 +665,33 @@ public class PointerTracker { queue.add(this); } onDownEventInternal(x, y, eventTime); + if (!sShouldHandleGesture) { + return; + } + final int activePointerTrackerCount = getActivePointerTrackerCount(); + if (activePointerTrackerCount == 1) { + mIsDetectingGesture = false; + // A gesture should start only from the letter key. + final boolean isAlphabetKeyboard = (mKeyboard != null) + && mKeyboard.mId.isAlphabetKeyboard(); + if (isAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null + && Keyboard.isLetterCode(key.mCode)) { + sGestureFirstDownTime = eventTime; + onGestureDownEvent(x, y, eventTime); + } + } else if (sInGesture && activePointerTrackerCount > 1) { + onGestureDownEvent(x, y, eventTime); + } } - private void onDownEventInternal(int x, int y, long eventTime) { + private void onGestureDownEvent(final int x, final int y, final long eventTime) { + mIsDetectingGesture = true; + 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) { Key key = onDownKey(x, y, eventTime); // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. @@ -525,7 +700,6 @@ public class PointerTracker { || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; mKeyAlreadyProcessed = false; - mIsRepeatableKey = false; mIsInSlidingKeyInput = false; mIgnoreModifierKey = false; if (key != null) { @@ -542,23 +716,68 @@ public class PointerTracker { } } - private void startSlidingKeyInput(Key key) { + private void startSlidingKeyInput(final Key key) { if (!mIsInSlidingKeyInput) { mIgnoreModifierKey = key.isModifier(); } mIsInSlidingKeyInput = true; } - public void onMoveEvent(int x, int y, long eventTime) { - if (DEBUG_MOVE_EVENT) + private void onGestureMoveEvent(final int x, final int y, final long eventTime, + final boolean isMajorEvent, final Key key) { + final int gestureTime = (int)(eventTime - sGestureFirstDownTime); + if (mIsDetectingGesture) { + mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); + mayStartBatchInput(); + if (sInGesture && key != null) { + updateBatchInput(eventTime); + } + } + } + + public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) { + if (DEBUG_MOVE_EVENT) { printTouchEvent("onMoveEvent:", x, y, eventTime); - if (mKeyAlreadyProcessed) + } + if (mKeyAlreadyProcessed) { return; + } + + if (sShouldHandleGesture && 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(historicalX, historicalY, historicalTime, + false /* isMajorEvent */, null); + } + } + onMoveEventInternal(x, y, eventTime); + } + + private void onMoveEventInternal(final int x, final int y, final long eventTime) { final int lastX = mLastX; final int lastY = mLastY; final Key oldKey = mCurrentKey; Key key = onMoveKey(x, y); + + if (sShouldHandleGesture) { + // Register move event on gesture tracker. + onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key); + if (sInGesture) { + mIgnoreModifierKey = true; + mTimerProxy.cancelLongPressTimer(); + mIsInSlidingKeyInput = true; + mCurrentKey = null; + setReleasedKeyGraphics(oldKey); + return; + } + } + if (key != null) { if (oldKey == null) { // The pointer has been slid in to the new key, but the finger was not on any keys. @@ -598,20 +817,34 @@ public class PointerTracker { final int dx = x - lastX; final int dy = y - lastY; final int lastMoveSquared = dx * dx + dy * dy; + // TODO: Should find a way to balance gesture detection and this hack. if (sNeedsPhantomSuddenMoveEventHack - && lastMoveSquared >= mKeyQuarterWidthSquared) { + && lastMoveSquared >= mKeyQuarterWidthSquared + && !mIsDetectingGesture) { if (DEBUG_MODE) { Log.w(TAG, String.format("onMoveEvent:" + " phantom sudden move event is translated to " + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); } + // TODO: This should be moved to outside of this nested if-clause? if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); } onUpEventInternal(); onDownEventInternal(x, y, eventTime); } else { - mKeyAlreadyProcessed = true; + // 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 (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null + && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { + onUpEventInternal(); + } + if (!mIsDetectingGesture) { + mKeyAlreadyProcessed = true; + } setReleasedKeyGraphics(oldKey); } } @@ -627,36 +860,45 @@ public class PointerTracker { if (mIsAllowedSlidingKeyInput) { onMoveToNewKey(key, x, y); } else { - mKeyAlreadyProcessed = true; + if (!mIsDetectingGesture) { + mKeyAlreadyProcessed = true; + } } } } } - public void onUpEvent(int x, int y, long eventTime) { - if (DEBUG_EVENT) + public void onUpEvent(final int x, final int y, final long eventTime) { + if (DEBUG_EVENT) { printTouchEvent("onUpEvent :", x, y, eventTime); + } 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 (!sInGesture) { + 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(); + if (queue != null) { + queue.remove(this); + } } // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a // "virtual" up event. - public void onPhantomUpEvent(int x, int y, long eventTime) { - if (DEBUG_EVENT) - printTouchEvent("onPhntEvent:", x, y, eventTime); + @Override + public void onPhantomUpEvent(final long eventTime) { + if (DEBUG_EVENT) { + printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); + } onUpEventInternal(); mKeyAlreadyProcessed = true; } @@ -664,23 +906,39 @@ public class PointerTracker { private void onUpEventInternal() { mTimerProxy.cancelKeyTimers(); mIsInSlidingKeyInput = false; + mIsDetectingGesture = false; + final Key currentKey = mCurrentKey; + mCurrentKey = null; // Release the last pressed key. - setReleasedKeyGraphics(mCurrentKey); + setReleasedKeyGraphics(currentKey); if (mIsShowingMoreKeysPanel) { mDrawingProxy.dismissMoreKeysPanel(); mIsShowingMoreKeysPanel = false; } - if (mKeyAlreadyProcessed) + + if (sInGesture) { + if (currentKey != null) { + callListenerOnRelease(currentKey, currentKey.mCode, true); + } + mayEndBatchInput(); return; - if (!mIsRepeatableKey) { - detectAndSendKey(mCurrentKey, mKeyX, mKeyY); + } + // 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) { + return; + } + if (currentKey != null && !currentKey.isRepeatable()) { + detectAndSendKey(currentKey, mKeyX, mKeyY); } } - public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { + public void onShowMoreKeysPanel(final int x, final int y, final KeyEventHandler handler) { + abortBatchInput(); onLongPressed(); - onDownEvent(x, y, SystemClock.uptimeMillis(), handler); mIsShowingMoreKeysPanel = true; + onDownEvent(x, y, SystemClock.uptimeMillis(), handler); } public void onLongPressed() { @@ -692,9 +950,10 @@ public class PointerTracker { } } - public void onCancelEvent(int x, int y, long eventTime) { - if (DEBUG_EVENT) + public void onCancelEvent(final int x, final int y, final long eventTime) { + if (DEBUG_EVENT) { printTouchEvent("onCancelEvt:", x, y, eventTime); + } final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { @@ -714,29 +973,25 @@ public class PointerTracker { } } - private void startRepeatKey(Key key) { - if (key != null && key.isRepeatable()) { + private void startRepeatKey(final Key key) { + if (key != null && key.isRepeatable() && !sInGesture) { onRegisterKey(key); mTimerProxy.startKeyRepeatTimer(this); - mIsRepeatableKey = true; - } else { - mIsRepeatableKey = false; } } - public void onRegisterKey(Key key) { + public void onRegisterKey(final Key key) { if (key != null) { detectAndSendKey(key, key.mX, key.mY); - if (!key.altCodeWhileTyping() && !key.isModifier()) { - mTimerProxy.startTypingStateTimer(); - } + mTimerProxy.startTypingStateTimer(key); } } - private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) { - if (mKeyDetector == null) + private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final Key newKey) { + if (mKeyDetector == null) { throw new NullPointerException("keyboard and/or key detector not set"); - Key curKey = mCurrentKey; + } + final Key curKey = mCurrentKey; if (newKey == curKey) { return false; } else if (curKey != null) { @@ -747,31 +1002,28 @@ public class PointerTracker { } } - private void startLongPressTimer(Key key) { - if (key != null && key.isLongPressEnabled()) { + private void startLongPressTimer(final Key key) { + if (key != null && key.isLongPressEnabled() && !sInGesture) { mTimerProxy.startLongPressTimer(this); } } - private void detectAndSendKey(Key key, int x, int y) { + private void detectAndSendKey(final Key key, final int x, final int y) { if (key == null) { callListenerOnCancelInput(); return; } - int code = key.mCode; + final int code = key.mCode; callListenerOnCodeInput(key, code, x, y); callListenerOnRelease(key, code, false); } - private long mPreviousEventTime; - - private void printTouchEvent(String title, int x, int y, long eventTime) { + private void printTouchEvent(final String title, final int x, final int y, + final 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)); } } |