diff options
Diffstat (limited to 'java/src/com/android/inputmethod/keyboard/PointerTracker.java')
-rw-r--r-- | java/src/com/android/inputmethod/keyboard/PointerTracker.java | 874 |
1 files changed, 469 insertions, 405 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 8b03360bf..34e428e82 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -16,73 +16,143 @@ package com.android.inputmethod.keyboard; -import android.content.res.Resources; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; -import com.android.inputmethod.keyboard.KeyboardView.UIHandler; -import com.android.inputmethod.keyboard.internal.PointerTrackerKeyState; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.SubtypeSwitcher; +import com.android.inputmethod.latin.ResearchLogger; +import com.android.inputmethod.latin.define.ProductionFlag; -import java.util.Arrays; -import java.util.List; +import java.util.ArrayList; public class PointerTracker { private static final String TAG = PointerTracker.class.getSimpleName(); - private static final boolean ENABLE_ASSERTION = false; 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; - public interface UIProxy { + public interface KeyEventHandler { + /** + * Get KeyDetector object that is used for this PointerTracker. + * @return the KeyDetector object that is used for this PointerTracker + */ + public KeyDetector getKeyDetector(); + + /** + * Get KeyboardActionListener object that is used to register key code and so on. + * @return the KeyboardActionListner for this PointerTracker + */ + public KeyboardActionListener getKeyboardActionListener(); + + /** + * Get DrawingProxy object that is used for this PointerTracker. + * @return the DrawingProxy object that is used for this PointerTracker + */ + public DrawingProxy getDrawingProxy(); + + /** + * Get TimerProxy object that handles key repeat and long press timer event for this + * PointerTracker. + * @return the TimerProxy object that handles key repeat and long press timer event. + */ + public TimerProxy getTimerProxy(); + } + + public interface DrawingProxy extends MoreKeysPanel.Controller { public void invalidateKey(Key key); - public void showKeyPreview(int keyIndex, PointerTracker tracker); + public TextView inflateKeyPreviewText(); + public void showKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker); - public boolean hasDistinctMultitouch(); } - public final int mPointerId; + public interface TimerProxy { + public void startTypingStateTimer(); + public boolean isTypingState(); + public void startKeyRepeatTimer(PointerTracker tracker); + public void startLongPressTimer(PointerTracker tracker); + public void startLongPressTimer(int code); + public void cancelLongPressTimer(); + public void startDoubleTapTimer(); + public void cancelDoubleTapTimer(); + public boolean isInDoubleTapTimeout(); + public void cancelKeyTimers(); + + public static class Adapter implements TimerProxy { + @Override + public void startTypingStateTimer() {} + @Override + public boolean isTypingState() { return false; } + @Override + public void startKeyRepeatTimer(PointerTracker tracker) {} + @Override + public void startLongPressTimer(PointerTracker tracker) {} + @Override + public void startLongPressTimer(int code) {} + @Override + public void cancelLongPressTimer() {} + @Override + public void startDoubleTapTimer() {} + @Override + public void cancelDoubleTapTimer() {} + @Override + public boolean isInDoubleTapTimeout() { return false; } + @Override + public void cancelKeyTimers() {} + } + } - // Timing constants - private final int mDelayBeforeKeyRepeatStart; - private final int mLongPressKeyTimeout; - private final int mLongPressShiftKeyTimeout; + // Parameters for pointer handling. + private static LatinKeyboardView.PointerTrackerParams sParams; + private static int sTouchNoiseThresholdDistanceSquared; + private static boolean sNeedsPhantomSuddenMoveEventHack; - private final KeyboardView mKeyboardView; - private final UIProxy mProxy; - private final UIHandler mHandler; - private final KeyDetector mKeyDetector; - private KeyboardActionListener mListener = EMPTY_LISTENER; - private final KeyboardSwitcher mKeyboardSwitcher; - private final boolean mHasDistinctMultitouch; - private final boolean mConfigSlidingKeyInputEnabled; + private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); + private static PointerTrackerQueue sPointerTrackerQueue; + + public final int mPointerId; - private final int mTouchNoiseThresholdMillis; - private final int mTouchNoiseThresholdDistanceSquared; + private DrawingProxy mDrawingProxy; + private TimerProxy mTimerProxy; + private KeyDetector mKeyDetector; + private KeyboardActionListener mListener = EMPTY_LISTENER; private Keyboard mKeyboard; - private List<Key> mKeys; - private int mKeyHysteresisDistanceSquared = -1; private int mKeyQuarterWidthSquared; + private final TextView mKeyPreviewText; + + // The position and time at which first down event occurred. + private long mDownTime; + private long mUpTime; - private final PointerTrackerKeyState mKeyState; + // The current key where this pointer is. + private Key mCurrentKey = null; + // The position where the current key was recognized for the first time. + private int mKeyX; + private int mKeyY; + + // Last pointer position. + private int mLastX; + private int mLastY; // true if keyboard layout has been changed. private boolean mKeyboardLayoutHasBeenChanged; - // true if event is already translated to a key action (long press or mini-keyboard) + // true if event is already translated to a key action. private boolean mKeyAlreadyProcessed; + // 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 - private boolean mIsInSlidingKeyInput; + boolean mIsInSlidingKeyInput; // true if sliding key is allowed. private boolean mIsAllowedSlidingKeyInput; @@ -90,69 +160,98 @@ public class PointerTracker { // ignore modifier key if true private boolean mIgnoreModifierKey; - // TODO: Remove these hacking variables - // true if this pointer is in sliding language switch - private boolean mIsInSlidingLanguageSwitch; - private int mSpaceKeyIndex; - private final SubtypeSwitcher mSubtypeSwitcher; - // Empty {@link KeyboardActionListener} - private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() { - @Override - public void onPress(int primaryCode, boolean withSliding) {} - @Override - public void onRelease(int primaryCode, boolean withSliding) {} - @Override - public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {} - @Override - public void onTextInput(CharSequence text) {} - @Override - public void onCancelInput() {} - @Override - public void onSwipeDown() {} - }; - - public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler, - KeyDetector keyDetector, UIProxy proxy) { - if (proxy == null || handler == null || keyDetector == null) + private static final KeyboardActionListener EMPTY_LISTENER = + new KeyboardActionListener.Adapter(); + + public static void init(boolean hasDistinctMultitouch, + boolean needsPhantomSuddenMoveEventHack) { + if (hasDistinctMultitouch) { + sPointerTrackerQueue = new PointerTrackerQueue(); + } else { + sPointerTrackerQueue = null; + } + sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; + + setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT); + } + + public static void setParameters(LatinKeyboardView.PointerTrackerParams params) { + sParams = params; + sTouchNoiseThresholdDistanceSquared = (int)( + params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); + } + + public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { + final ArrayList<PointerTracker> trackers = sTrackers; + + // Create pointer trackers until we can get 'id+1'-th tracker, if needed. + for (int i = trackers.size(); i <= id; i++) { + final PointerTracker tracker = new PointerTracker(i, handler); + trackers.add(tracker); + } + + return trackers.get(id); + } + + public static boolean isAnyInSlidingKeyInput() { + return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; + } + + public static void setKeyboardActionListener(KeyboardActionListener listener) { + for (final PointerTracker tracker : sTrackers) { + tracker.mListener = listener; + } + } + + public static void setKeyDetector(KeyDetector keyDetector) { + for (final PointerTracker tracker : sTrackers) { + tracker.setKeyDetectorInner(keyDetector); + // Mark that keyboard layout has been changed. + tracker.mKeyboardLayoutHasBeenChanged = true; + } + } + + public static void dismissAllKeyPreviews() { + for (final PointerTracker tracker : sTrackers) { + tracker.getKeyPreviewText().setVisibility(View.INVISIBLE); + tracker.setReleasedKeyGraphics(tracker.mCurrentKey); + } + } + + public PointerTracker(int id, KeyEventHandler handler) { + if (handler == null) throw new NullPointerException(); mPointerId = id; - mKeyboardView = keyboardView; - mProxy = proxy; - mHandler = handler; - mKeyDetector = keyDetector; - mKeyboardSwitcher = KeyboardSwitcher.getInstance(); - mKeyState = new PointerTrackerKeyState(keyDetector); - mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); - final Resources res = mKeyboardView.getResources(); - mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); - mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); - mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); - mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); - mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis); - final float touchNoiseThresholdDistance = res.getDimension( - R.dimen.config_touch_noise_threshold_distance); - mTouchNoiseThresholdDistanceSquared = (int)( - touchNoiseThresholdDistance * touchNoiseThresholdDistance); - mSubtypeSwitcher = SubtypeSwitcher.getInstance(); - } - - public void setOnKeyboardActionListener(KeyboardActionListener listener) { - mListener = listener; + 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, boolean withSliding) { - final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); - if (DEBUG_LISTENER) - Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding - + " ignoreModifier=" + ignoreModifierKey); - if (ignoreModifierKey) + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { + final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); + if (DEBUG_LISTENER) { + Log.d(TAG, "onPress : " + KeyDetector.printableCode(key) + + " ignoreModifier=" + ignoreModifierKey + + " enabled=" + key.isEnabled()); + } + if (ignoreModifierKey) { return false; + } if (key.isEnabled()) { - mListener.onPress(key.mCode, withSliding); + mListener.onPressKey(key.mCode); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; + if (!key.altCodeWhileTyping() && !key.isModifier()) { + mTimerProxy.startTypingStateTimer(); + } return keyboardLayoutHasBeenChanged; } return false; @@ -160,170 +259,250 @@ 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[] keyCodes, int x, int y) { - final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); - if (DEBUG_LISTENER) - Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode) - + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y - + " ignoreModifier=" + ignoreModifierKey); - if (ignoreModifierKey) + private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) { + final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); + final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); + final int code = altersCode ? key.mAltCode : primaryCode; + if (DEBUG_LISTENER) { + Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText + + " x=" + x + " y=" + y + + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode + + " enabled=" + key.isEnabled()); + } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, + altersCode, code); + } + if (ignoreModifierKey) { return; - if (key.isEnabled()) - mListener.onCodeInput(primaryCode, keyCodes, x, y); - } - - private void callListenerOnTextInput(Key key) { - if (DEBUG_LISTENER) - Log.d(TAG, "onTextInput: text=" + key.mOutputText); - if (key.isEnabled()) - mListener.onTextInput(key.mOutputText); + } + // 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); + } else if (code != Keyboard.CODE_UNSPECIFIED) { + mListener.onCodeInput(code, x, y); + } + } } // 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) { - final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); - if (DEBUG_LISTENER) - Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding=" - + withSliding + " ignoreModifier=" + ignoreModifierKey); - if (ignoreModifierKey) + final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); + if (DEBUG_LISTENER) { + Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) + + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey + + " enabled="+ key.isEnabled()); + } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, + ignoreModifierKey); + } + if (ignoreModifierKey) { return; - if (key.isEnabled()) - mListener.onRelease(primaryCode, withSliding); + } + if (key.isEnabled()) { + mListener.onReleaseKey(primaryCode, withSliding); + } } private void callListenerOnCancelInput() { if (DEBUG_LISTENER) Log.d(TAG, "onCancelInput"); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_callListenerOnCancelInput(); + } mListener.onCancelInput(); } - public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) { - if (keyboard == null || keyHysteresisDistance < 0) - throw new IllegalArgumentException(); - mKeyboard = keyboard; - mKeys = keyboard.getKeys(); - mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); - final int keyQuarterWidth = keyboard.getKeyWidth() / 4; + private void setKeyDetectorInner(KeyDetector keyDetector) { + mKeyDetector = keyDetector; + mKeyboard = keyDetector.getKeyboard(); + final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; - // Mark that keyboard layout has been changed. - mKeyboardLayoutHasBeenChanged = true; } public boolean isInSlidingKeyInput() { return mIsInSlidingKeyInput; } - private boolean isValidKeyIndex(int keyIndex) { - return keyIndex >= 0 && keyIndex < mKeys.size(); + public Key getKey() { + return mCurrentKey; } - public Key getKey(int keyIndex) { - return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null; + public boolean isModifier() { + return mCurrentKey != null && mCurrentKey.isModifier(); } - private static boolean isModifierCode(int primaryCode) { - return primaryCode == Keyboard.CODE_SHIFT - || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL; + public Key getKeyOn(int x, int y) { + return mKeyDetector.detectHitKey(x, y); } - private boolean isModifierInternal(int keyIndex) { - final Key key = getKey(keyIndex); - return key == null ? false : isModifierCode(key.mCode); + private void setReleasedKeyGraphics(Key key) { + mDrawingProxy.dismissKeyPreview(this); + if (key == null) { + return; + } + + // Even if the key is disabled, update the key release graphics just in case. + updateReleaseKeyGraphics(key); + + if (key.isShift()) { + for (final Key shiftKey : mKeyboard.mShiftKeys) { + if (shiftKey != key) { + updateReleaseKeyGraphics(shiftKey); + } + } + } + + if (key.altCodeWhileTyping()) { + final int altCode = key.mAltCode; + final Key altKey = mKeyboard.getKey(altCode); + if (altKey != null) { + updateReleaseKeyGraphics(altKey); + } + for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { + if (k != key && k.mAltCode == altCode) { + updateReleaseKeyGraphics(k); + } + } + } } - public boolean isModifier() { - return isModifierInternal(mKeyState.getKeyIndex()); + private void setPressedKeyGraphics(Key key) { + if (key == null) { + return; + } + + // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. + final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); + final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; + if (!needsToUpdateGraphics) { + return; + } + + if (!key.noKeyPreview()) { + mDrawingProxy.showKeyPreview(this); + } + updatePressKeyGraphics(key); + + if (key.isShift()) { + for (final Key shiftKey : mKeyboard.mShiftKeys) { + if (shiftKey != key) { + updatePressKeyGraphics(shiftKey); + } + } + } + + if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { + final int altCode = key.mAltCode; + final Key altKey = mKeyboard.getKey(altCode); + if (altKey != null) { + updatePressKeyGraphics(altKey); + } + for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { + if (k != key && k.mAltCode == altCode) { + updatePressKeyGraphics(k); + } + } + } } - private boolean isOnModifierKey(int x, int y) { - return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); + private void updateReleaseKeyGraphics(Key key) { + key.onReleased(); + mDrawingProxy.invalidateKey(key); } - public boolean isOnShiftKey(int x, int y) { - final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); - return key != null && key.mCode == Keyboard.CODE_SHIFT; + private void updatePressKeyGraphics(Key key) { + key.onPressed(); + mDrawingProxy.invalidateKey(key); } - public int getKeyIndexOn(int x, int y) { - return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); + public int getLastX() { + return mLastX; } - public boolean isSpaceKey(int keyIndex) { - Key key = getKey(keyIndex); - return key != null && key.mCode == Keyboard.CODE_SPACE; + public int getLastY() { + return mLastY; } - public void setReleasedKeyGraphics() { - setReleasedKeyGraphics(mKeyState.getKeyIndex()); + public long getDownTime() { + return mDownTime; } - private void setReleasedKeyGraphics(int keyIndex) { - final Key key = getKey(keyIndex); - if (key != null) { - key.onReleased(); - mProxy.invalidateKey(key); - } + private Key onDownKey(int x, int y, long eventTime) { + mDownTime = eventTime; + return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); } - private void setPressedKeyGraphics(int keyIndex) { - final Key key = getKey(keyIndex); - if (key != null && key.isEnabled()) { - key.onPressed(); - mProxy.invalidateKey(key); - } + private Key onMoveKeyInternal(int x, int y) { + mLastX = x; + mLastY = y; + return mKeyDetector.detectHitKey(x, y); } - private void checkAssertion(PointerTrackerQueue queue) { - if (mHasDistinctMultitouch && queue == null) - throw new RuntimeException( - "PointerTrackerQueue must be passed on distinct multi touch device"); - if (!mHasDistinctMultitouch && queue != null) - throw new RuntimeException( - "PointerTrackerQueue must be null on non-distinct multi touch device"); + private Key onMoveKey(int x, int y) { + return onMoveKeyInternal(x, y); } - public void onTouchEvent(int action, int x, int y, long eventTime, PointerTrackerQueue queue) { + private Key onMoveToNewKey(Key newKey, int x, int y) { + mCurrentKey = newKey; + mKeyX = x; + mKeyY = y; + return newKey; + } + + public void processMotionEvent(int action, int x, int y, long eventTime, + KeyEventHandler handler) { switch (action) { - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime, queue); - break; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, eventTime, queue); + onDownEvent(x, y, eventTime, handler); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, eventTime, queue); + onUpEvent(x, y, eventTime); + break; + case MotionEvent.ACTION_MOVE: + onMoveEvent(x, y, eventTime); break; case MotionEvent.ACTION_CANCEL: - onCancelEvent(x, y, eventTime, queue); + onCancelEvent(x, y, eventTime); break; } } - public void onDownEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { - if (ENABLE_ASSERTION) checkAssertion(queue); + public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) { if (DEBUG_EVENT) printTouchEvent("onDownEvent:", x, y, eventTime); + mDrawingProxy = handler.getDrawingProxy(); + mTimerProxy = handler.getTimerProxy(); + setKeyboardActionListener(handler.getKeyboardActionListener()); + setKeyDetectorInner(handler.getKeyDetector()); // Naive up-to-down noise filter. - final long deltaT = eventTime - mKeyState.getUpTime(); - if (deltaT < mTouchNoiseThresholdMillis) { - final int dx = x - mKeyState.getLastX(); - final int dy = y - mKeyState.getLastY(); + final long deltaT = eventTime - mUpTime; + if (deltaT < sParams.mTouchNoiseThresholdTime) { + final int dx = x - mLastX; + final int dy = y - mLastY; final int distanceSquared = (dx * dx + dy * dy); - if (distanceSquared < mTouchNoiseThresholdDistanceSquared) { + if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { if (DEBUG_MODE) Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT + " distance=" + distanceSquared); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared); + } mKeyAlreadyProcessed = true; return; } } + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - if (isOnModifierKey(x, y)) { + 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. queue.releaseAllPointers(eventTime); @@ -334,88 +513,80 @@ public class PointerTracker { } private void onDownEventInternal(int x, int y, long eventTime) { - int keyIndex = mKeyState.onDownKey(x, y, 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 is on mini-keyboard. - mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) - || mKeyDetector instanceof MiniKeyboardKeyDetector; + // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. + mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled + || (key != null && key.isModifier()) + || mKeyDetector.alwaysAllowsSlidingInput(); mKeyboardLayoutHasBeenChanged = false; mKeyAlreadyProcessed = false; mIsRepeatableKey = false; mIsInSlidingKeyInput = false; - mIsInSlidingLanguageSwitch = false; mIgnoreModifierKey = false; - if (isValidKeyIndex(keyIndex)) { + if (key != null) { // This onPress call may have changed keyboard layout. Those cases are detected at - // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new + // {@link #setKeyboard}. In those cases, we should update key according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false)) - keyIndex = mKeyState.onDownKey(x, y, eventTime); + if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + key = onDownKey(x, y, eventTime); + } - startRepeatKey(keyIndex); - startLongPressTimer(keyIndex); - showKeyPreview(keyIndex); - setPressedKeyGraphics(keyIndex); + startRepeatKey(key); + startLongPressTimer(key); + setPressedKeyGraphics(key); } } private void startSlidingKeyInput(Key key) { - if (!mIsInSlidingKeyInput) - mIgnoreModifierKey = isModifierCode(key.mCode); + if (!mIsInSlidingKeyInput) { + mIgnoreModifierKey = key.isModifier(); + } mIsInSlidingKeyInput = true; } - public void onMoveEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { - if (ENABLE_ASSERTION) checkAssertion(queue); + public void onMoveEvent(int x, int y, long eventTime) { if (DEBUG_MOVE_EVENT) printTouchEvent("onMoveEvent:", x, y, eventTime); if (mKeyAlreadyProcessed) return; - final PointerTrackerKeyState keyState = mKeyState; - // TODO: Remove this hacking code - if (mIsInSlidingLanguageSwitch) { - ((LatinKeyboard)mKeyboard).updateSpacebarPreviewIcon(x - keyState.getKeyX()); - showKeyPreview(mSpaceKeyIndex); - return; - } - final int lastX = keyState.getLastX(); - final int lastY = keyState.getLastY(); - final int oldKeyIndex = keyState.getKeyIndex(); - final Key oldKey = getKey(oldKeyIndex); - int keyIndex = keyState.onMoveKey(x, y); - if (isValidKeyIndex(keyIndex)) { + final int lastX = mLastX; + final int lastY = mLastY; + final Key oldKey = mCurrentKey; + Key key = onMoveKey(x, y); + if (key != null) { if (oldKey == null) { // The pointer has been slid in to the new key, but the finger was not on any keys. // In this case, we must call onPress() to notify that the new key is being pressed. // This onPress call may have changed keyboard layout. Those cases are detected at - // {@link #setKeyboard}. In those cases, we should update keyIndex according to the + // {@link #setKeyboard}. In those cases, we should update key according to the // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) - keyIndex = keyState.onMoveKey(x, y); - keyState.onMoveToNewKey(keyIndex, x, y); - startLongPressTimer(keyIndex); - showKeyPreview(keyIndex); - setPressedKeyGraphics(keyIndex); - } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { + if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + key = onMoveKey(x, y); + } + onMoveToNewKey(key, x, y); + startLongPressTimer(key); + setPressedKeyGraphics(key); + } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) { // The pointer has been slid in to the new key from the previous key, we must call // onRelease() first to notify that the previous key has been released, then call // onPress() to notify that the new key is being pressed. - setReleasedKeyGraphics(oldKeyIndex); + setReleasedKeyGraphics(oldKey); callListenerOnRelease(oldKey, oldKey.mCode, true); startSlidingKeyInput(oldKey); - mHandler.cancelKeyTimers(); - startRepeatKey(keyIndex); + mTimerProxy.cancelKeyTimers(); + startRepeatKey(key); if (mIsAllowedSlidingKeyInput) { // This onPress call may have changed keyboard layout. Those cases are detected - // at {@link #setKeyboard}. In those cases, we should update keyIndex according + // at {@link #setKeyboard}. In those cases, we should update key according // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) - keyIndex = keyState.onMoveKey(x, y); - keyState.onMoveToNewKey(keyIndex, x, y); - startLongPressTimer(keyIndex); - setPressedKeyGraphics(keyIndex); - showKeyPreview(keyIndex); + if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + key = onMoveKey(x, y); + } + onMoveToNewKey(key, x, y); + startLongPressTimer(key); + setPressedKeyGraphics(key); } else { // HACK: On some devices, quick successive touches may be translated to sudden // move by touch panel firmware. This hack detects the case and translates the @@ -423,287 +594,180 @@ public class PointerTracker { final int dx = x - lastX; final int dy = y - lastY; final int lastMoveSquared = dx * dx + dy * dy; - if (lastMoveSquared >= mKeyQuarterWidthSquared) { - if (DEBUG_MODE) - Log.w(TAG, String.format("onMoveEvent: sudden move is translated to " + if (sNeedsPhantomSuddenMoveEventHack + && lastMoveSquared >= mKeyQuarterWidthSquared) { + 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)); - onUpEventInternal(lastX, lastY, eventTime, true); + } + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); + } + onUpEventInternal(); onDownEventInternal(x, y, eventTime); } else { mKeyAlreadyProcessed = true; - dismissKeyPreview(); - setReleasedKeyGraphics(oldKeyIndex); - } - } - } - // TODO: Remove this hack code - else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch - && mKeyboard instanceof LatinKeyboard) { - final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard); - if (mSubtypeSwitcher.useSpacebarLanguageSwitcher() - && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { - final int diff = x - keyState.getKeyX(); - if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) { - // Detect start sliding language switch. - mIsInSlidingLanguageSwitch = true; - mSpaceKeyIndex = keyIndex; - keyboard.updateSpacebarPreviewIcon(diff); - // Display spacebar slide language switcher. - showKeyPreview(keyIndex); - if (queue != null) - queue.releaseAllPointersExcept(this, eventTime, true); + setReleasedKeyGraphics(oldKey); } } } } else { - if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { + if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) { // The pointer has been slid out from the previous key, we must call onRelease() to // notify that the previous key has been released. - setReleasedKeyGraphics(oldKeyIndex); + setReleasedKeyGraphics(oldKey); callListenerOnRelease(oldKey, oldKey.mCode, true); startSlidingKeyInput(oldKey); - mHandler.cancelLongPressTimers(); + mTimerProxy.cancelLongPressTimer(); if (mIsAllowedSlidingKeyInput) { - keyState.onMoveToNewKey(keyIndex, x, y); + onMoveToNewKey(key, x, y); } else { mKeyAlreadyProcessed = true; - dismissKeyPreview(); } } } } - public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { - if (ENABLE_ASSERTION) checkAssertion(queue); + public void onUpEvent(int x, int y, long eventTime) { if (DEBUG_EVENT) printTouchEvent("onUpEvent :", x, y, eventTime); + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - if (isModifier()) { + 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, true); + queue.releaseAllPointersExcept(this, eventTime); } else { queue.releaseAllPointersOlderThan(this, eventTime); } queue.remove(this); } - onUpEventInternal(x, y, eventTime, true); + onUpEventInternal(); } // 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, boolean updateReleasedKeyGraphics) { + public void onPhantomUpEvent(int x, int y, long eventTime) { if (DEBUG_EVENT) printTouchEvent("onPhntEvent:", x, y, eventTime); - onUpEventInternal(x, y, eventTime, updateReleasedKeyGraphics); + onUpEventInternal(); mKeyAlreadyProcessed = true; } - private void onUpEventInternal(int x, int y, long eventTime, - boolean updateReleasedKeyGraphics) { - mHandler.cancelKeyTimers(); - mHandler.cancelShowKeyPreview(this); + private void onUpEventInternal() { + mTimerProxy.cancelKeyTimers(); mIsInSlidingKeyInput = false; - final PointerTrackerKeyState keyState = mKeyState; - final int keyX, keyY; - if (isMajorEnoughMoveToBeOnNewKey(x, y, keyState.onMoveKey(x, y))) { - keyX = x; - keyY = y; - } else { - // Use previous fixed key coordinates. - keyX = keyState.getKeyX(); - keyY = keyState.getKeyY(); - } - final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime); - dismissKeyPreview(); - if (updateReleasedKeyGraphics) - setReleasedKeyGraphics(keyIndex); + // Release the last pressed key. + setReleasedKeyGraphics(mCurrentKey); + if (mIsShowingMoreKeysPanel) { + mDrawingProxy.dismissMoreKeysPanel(); + mIsShowingMoreKeysPanel = false; + } if (mKeyAlreadyProcessed) return; - // TODO: Remove this hacking code - if (mIsInSlidingLanguageSwitch) { - setReleasedKeyGraphics(mSpaceKeyIndex); - final int languageDir = ((LatinKeyboard)mKeyboard).getLanguageChangeDirection(); - if (languageDir != 0) { - final int code = (languageDir == 1) - ? LatinKeyboard.CODE_NEXT_LANGUAGE : LatinKeyboard.CODE_PREV_LANGUAGE; - // This will change keyboard layout. - mListener.onCodeInput(code, new int[] {code}, keyX, keyY); - } - mIsInSlidingLanguageSwitch = false; - ((LatinKeyboard)mKeyboard).setSpacebarSlidingLanguageSwitchDiff(0); - return; - } if (!mIsRepeatableKey) { - detectAndSendKey(keyIndex, keyX, keyY); + detectAndSendKey(mCurrentKey, mKeyX, mKeyY); } } - public void onLongPressed(PointerTrackerQueue queue) { + public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { + onLongPressed(); + onDownEvent(x, y, SystemClock.uptimeMillis(), handler); + mIsShowingMoreKeysPanel = true; + } + + public void onLongPressed() { mKeyAlreadyProcessed = true; + setReleasedKeyGraphics(mCurrentKey); + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - // TODO: Support chording + long-press input. - queue.releaseAllPointersExcept(this, SystemClock.uptimeMillis(), true); queue.remove(this); } } - public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { - if (ENABLE_ASSERTION) checkAssertion(queue); + public void onCancelEvent(int x, int y, long eventTime) { if (DEBUG_EVENT) printTouchEvent("onCancelEvt:", x, y, eventTime); + final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - queue.releaseAllPointersExcept(this, eventTime, true); + queue.releaseAllPointersExcept(this, eventTime); queue.remove(this); } onCancelEventInternal(); } private void onCancelEventInternal() { - mHandler.cancelKeyTimers(); - mHandler.cancelShowKeyPreview(this); - dismissKeyPreview(); - setReleasedKeyGraphics(mKeyState.getKeyIndex()); + mTimerProxy.cancelKeyTimers(); + setReleasedKeyGraphics(mCurrentKey); mIsInSlidingKeyInput = false; + if (mIsShowingMoreKeysPanel) { + mDrawingProxy.dismissMoreKeysPanel(); + mIsShowingMoreKeysPanel = false; + } } - private void startRepeatKey(int keyIndex) { - final Key key = getKey(keyIndex); - if (key != null && key.mRepeatable) { - dismissKeyPreview(); - onRepeatKey(keyIndex); - mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); + private void startRepeatKey(Key key) { + if (key != null && key.isRepeatable()) { + onRegisterKey(key); + mTimerProxy.startKeyRepeatTimer(this); mIsRepeatableKey = true; } else { mIsRepeatableKey = false; } } - public void onRepeatKey(int keyIndex) { - Key key = getKey(keyIndex); + public void onRegisterKey(Key key) { if (key != null) { - detectAndSendKey(keyIndex, key.mX, key.mY); + detectAndSendKey(key, key.mX, key.mY); + if (!key.altCodeWhileTyping() && !key.isModifier()) { + mTimerProxy.startTypingStateTimer(); + } } } - public int getLastX() { - return mKeyState.getLastX(); - } - - public int getLastY() { - return mKeyState.getLastY(); - } - - public long getDownTime() { - return mKeyState.getDownTime(); - } - - private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) { - if (mKeys == null || mKeyHysteresisDistanceSquared < 0) - throw new IllegalStateException("keyboard and/or hysteresis not set"); - int curKey = mKeyState.getKeyIndex(); + private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) { + if (mKeyDetector == null) + throw new NullPointerException("keyboard and/or key detector not set"); + Key curKey = mCurrentKey; if (newKey == curKey) { return false; - } else if (isValidKeyIndex(curKey)) { - return mKeys.get(curKey).squaredDistanceToEdge(x, y) >= mKeyHysteresisDistanceSquared; + } else if (curKey != null) { + return curKey.squaredDistanceToEdge(x, y) + >= mKeyDetector.getKeyHysteresisDistanceSquared(); } else { return true; } } - // The modifier key, such as shift key, should not show its key preview. - private boolean isKeyPreviewNotRequired(int keyIndex) { - final Key key = getKey(keyIndex); - if (key == null || !key.isEnabled()) - return true; - // Such as spacebar sliding language switch. - if (mKeyboard.needSpacebarPreview(keyIndex)) - return false; - final int code = key.mCode; - return isModifierCode(code) || code == Keyboard.CODE_DELETE - || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE; - } - - private void showKeyPreview(int keyIndex) { - if (isKeyPreviewNotRequired(keyIndex)) - return; - mProxy.showKeyPreview(keyIndex, this); - } - - private void dismissKeyPreview() { - mProxy.dismissKeyPreview(this); - } - - private void startLongPressTimer(int keyIndex) { - Key key = getKey(keyIndex); - if (key.mCode == Keyboard.CODE_SHIFT) { - mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this); - } else if (key.hasUppercaseLetter() && mKeyboard.isManualTemporaryUpperCase()) { - // We need not start long press timer on the key which has manual temporary upper case - // code defined and the keyboard is in manual temporary upper case mode. - return; - } else if (mKeyboardSwitcher.isInMomentarySwitchState()) { - // We use longer timeout for sliding finger input started from the symbols mode key. - mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this); - } else { - mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); + private void startLongPressTimer(Key key) { + if (key != null && key.isLongPressEnabled()) { + mTimerProxy.startLongPressTimer(this); } } - private void detectAndSendKey(int index, int x, int y) { - final Key key = getKey(index); + private void detectAndSendKey(Key key, int x, int y) { if (key == null) { callListenerOnCancelInput(); return; } - if (key.mOutputText != null) { - callListenerOnTextInput(key); - callListenerOnRelease(key, key.mCode, false); - } else { - int code = key.mCode; - final int[] codes = mKeyDetector.newCodeArray(); - mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); - - // If keyboard is in manual temporary upper case state and key has manual temporary - // uppercase letter as key hint letter, alternate character code should be sent. - if (mKeyboard.isManualTemporaryUpperCase() && key.hasUppercaseLetter()) { - code = key.mHintLabel.charAt(0); - codes[0] = code; - } - // Swap the first and second values in the codes array if the primary code is not the - // first value but the second value in the array. This happens when key debouncing is - // in effect. - if (codes.length >= 2 && codes[0] != code && codes[1] == code) { - codes[1] = codes[0]; - codes[0] = code; - } - callListenerOnCodeInput(key, code, codes, x, y); - callListenerOnRelease(key, code, false); - } - } - - public CharSequence getPreviewText(Key key) { - return key.mLabel; + 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) { - final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - final Key key = getKey(keyIndex); - final String code = (key == null) ? "----" : keyCodePrintable(key.mCode); + 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 %3d(%s)", title, - (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code)); + Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, + (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code)); mPreviousEventTime = eventTime; } - - private static String keyCodePrintable(int primaryCode) { - final String modifier = isModifierCode(primaryCode) ? " modifier" : ""; - return String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier; - } } |