diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/PointerTracker.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/PointerTracker.java | 511 |
1 files changed, 0 insertions, 511 deletions
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java deleted file mode 100644 index cb717cbe7..000000000 --- a/java/src/com/android/inputmethod/latin/PointerTracker.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin; - -import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener; -import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler; - -import android.inputmethodservice.Keyboard; -import android.inputmethodservice.Keyboard.Key; -import android.util.Log; -import android.view.MotionEvent; -import android.view.ViewConfiguration; - -public class PointerTracker { - private static final String TAG = "PointerTracker"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_MOVE = false; - - public interface UIProxy { - public void invalidateKey(Key key); - public void showPreview(int keyIndex, PointerTracker tracker); - } - - public final int mPointerId; - - // Timing constants - private static final int REPEAT_START_DELAY = 400; - /* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second - private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); - private static final int MULTITAP_INTERVAL = 800; // milliseconds - private static final int KEY_DEBOUNCE_TIME = 70; - - // Miscellaneous constants - private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY; - private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE }; - - private final UIProxy mProxy; - private final UIHandler mHandler; - private final KeyDetector mKeyDetector; - private OnKeyboardActionListener mListener; - private final boolean mHasDistinctMultitouch; - - private Key[] mKeys; - private int mKeyHysteresisDistanceSquared = -1; - - private int mCurrentKey = NOT_A_KEY; - private int mStartX; - private int mStartY; - private long mDownTime; - - // true if event is already translated to a key action (long press or mini-keyboard) - private boolean mKeyAlreadyProcessed; - - // true if this pointer is repeatable key - private boolean mIsRepeatableKey; - - // for move de-bouncing - private int mLastCodeX; - private int mLastCodeY; - private int mLastX; - private int mLastY; - - // for time de-bouncing - private int mLastKey; - private long mLastKeyTime; - private long mLastMoveTime; - private long mCurrentKeyTime; - - // For multi-tap - private int mLastSentIndex; - private int mTapCount; - private long mLastTapTime; - private boolean mInMultiTap; - private final StringBuilder mPreviewLabel = new StringBuilder(1); - - // pressed key - private int mPreviousKey = NOT_A_KEY; - - public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy, - boolean hasDistinctMultitouch) { - if (proxy == null || handler == null || keyDetector == null) - throw new NullPointerException(); - mPointerId = id; - mProxy = proxy; - mHandler = handler; - mKeyDetector = keyDetector; - mHasDistinctMultitouch = hasDistinctMultitouch; - resetMultiTap(); - } - - public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { - mListener = listener; - } - - public void setKeyboard(Key[] keys, float keyHysteresisDistance) { - if (keys == null || keyHysteresisDistance < 0) - throw new IllegalArgumentException(); - mKeys = keys; - mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); - // Update current key index because keyboard layout has been changed. - mCurrentKey = mKeyDetector.getKeyIndexAndNearbyCodes(mStartX, mStartY, null); - } - - private boolean isValidKeyIndex(int keyIndex) { - return keyIndex >= 0 && keyIndex < mKeys.length; - } - - public Key getKey(int keyIndex) { - return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null; - } - - private boolean isModifierInternal(int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return false; - int primaryCode = key.codes[0]; - return primaryCode == Keyboard.KEYCODE_SHIFT - || primaryCode == Keyboard.KEYCODE_MODE_CHANGE; - } - - public boolean isModifier() { - return isModifierInternal(mCurrentKey); - } - - public boolean isOnModifierKey(int x, int y) { - return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); - } - - public boolean isSpaceKey(int keyIndex) { - Key key = getKey(keyIndex); - return key != null && key.codes[0] == LatinIME.KEYCODE_SPACE; - } - - public void updateKey(int keyIndex) { - if (mKeyAlreadyProcessed) - return; - int oldKeyIndex = mPreviousKey; - mPreviousKey = keyIndex; - if (keyIndex != oldKeyIndex) { - if (isValidKeyIndex(oldKeyIndex)) { - // if new key index is not a key, old key was just released inside of the key. - final boolean inside = (keyIndex == NOT_A_KEY); - mKeys[oldKeyIndex].onReleased(inside); - mProxy.invalidateKey(mKeys[oldKeyIndex]); - } - if (isValidKeyIndex(keyIndex)) { - mKeys[keyIndex].onPressed(); - mProxy.invalidateKey(mKeys[keyIndex]); - } - } - } - - public void setAlreadyProcessed() { - mKeyAlreadyProcessed = true; - } - - public void onTouchEvent(int action, int x, int y, long eventTime) { - switch (action) { - case MotionEvent.ACTION_MOVE: - onMoveEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - onDownEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - onUpEvent(x, y, eventTime); - break; - case MotionEvent.ACTION_CANCEL: - onCancelEvent(x, y, eventTime); - break; - } - } - - public void onDownEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onDownEvent:", x, y); - int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - mCurrentKey = keyIndex; - mStartX = x; - mStartY = y; - mDownTime = eventTime; - mKeyAlreadyProcessed = false; - mIsRepeatableKey = false; - startMoveDebouncing(x, y); - startTimeDebouncing(eventTime); - checkMultiTap(eventTime, keyIndex); - if (mListener != null) { - int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0; - mListener.onPress(primaryCode); - // This onPress call may have changed keyboard layout and have updated mCurrentKey - keyIndex = mCurrentKey; - } - if (isValidKeyIndex(keyIndex)) { - if (mKeys[keyIndex].repeatable) { - repeatKey(keyIndex); - mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this); - mIsRepeatableKey = true; - } - mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); - } - showKeyPreviewAndUpdateKey(keyIndex); - updateMoveDebouncing(x, y); - } - - public void onMoveEvent(int x, int y, long eventTime) { - if (DEBUG_MOVE) - debugLog("onMoveEvent:", x, y); - if (mKeyAlreadyProcessed) - return; - int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - if (isValidKeyIndex(keyIndex)) { - if (mCurrentKey == NOT_A_KEY) { - updateTimeDebouncing(eventTime); - mCurrentKey = keyIndex; - mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); - } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) { - updateTimeDebouncing(eventTime); - } else { - resetMultiTap(); - resetTimeDebouncing(eventTime, mCurrentKey); - resetMoveDebouncing(); - mCurrentKey = keyIndex; - mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this); - } - } else { - if (mCurrentKey != NOT_A_KEY) { - updateTimeDebouncing(eventTime); - mCurrentKey = keyIndex; - mHandler.cancelLongPressTimer(); - } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) { - updateTimeDebouncing(eventTime); - } else { - resetMultiTap(); - resetTimeDebouncing(eventTime, mCurrentKey); - resetMoveDebouncing(); - mCurrentKey = keyIndex; - mHandler.cancelLongPressTimer(); - } - } - /* - * While time debouncing is in effect, mCurrentKey holds the new key and this tracker - * holds the last key. At ACTION_UP event if time debouncing will be in effect - * eventually, the last key should be sent as the result. In such case mCurrentKey - * should not be showed as popup preview. - */ - showKeyPreviewAndUpdateKey(isMinorTimeBounce() ? mLastKey : mCurrentKey); - updateMoveDebouncing(x, y); - } - - public void onUpEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onUpEvent :", x, y); - if (mKeyAlreadyProcessed) - return; - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); - if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) { - updateTimeDebouncing(eventTime); - } else { - resetMultiTap(); - resetTimeDebouncing(eventTime, mCurrentKey); - mCurrentKey = keyIndex; - } - if (isMinorTimeBounce()) { - mCurrentKey = mLastKey; - x = mLastCodeX; - y = mLastCodeY; - } - showKeyPreviewAndUpdateKey(NOT_A_KEY); - if (!mIsRepeatableKey) { - detectAndSendKey(mCurrentKey, x, y, eventTime); - } - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void onCancelEvent(int x, int y, long eventTime) { - if (DEBUG) - debugLog("onCancelEvt:", x, y); - mHandler.cancelKeyTimers(); - mHandler.cancelPopupPreview(); - showKeyPreviewAndUpdateKey(NOT_A_KEY); - int keyIndex = mCurrentKey; - if (isValidKeyIndex(keyIndex)) - mProxy.invalidateKey(mKeys[keyIndex]); - } - - public void repeatKey(int keyIndex) { - Key key = getKey(keyIndex); - if (key != null) { - // While key is repeating, because there is no need to handle multi-tap key, we can - // pass -1 as eventTime argument. - detectAndSendKey(keyIndex, key.x, key.y, -1); - } - } - - public int getLastX() { - return mLastX; - } - - public int getLastY() { - return mLastY; - } - - public long getDownTime() { - return mDownTime; - } - - // These package scope methods are only for debugging purpose. - /* package */ int getStartX() { - return mStartX; - } - - /* package */ int getStartY() { - return mStartY; - } - - private void startMoveDebouncing(int x, int y) { - mLastCodeX = x; - mLastCodeY = y; - } - - private void updateMoveDebouncing(int x, int y) { - mLastX = x; - mLastY = y; - } - - private void resetMoveDebouncing() { - mLastCodeX = mLastX; - mLastCodeY = mLastY; - } - - private boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) { - if (mKeys == null || mKeyHysteresisDistanceSquared < 0) - throw new IllegalStateException("keyboard and/or hysteresis not set"); - if (newKey == curKey) { - return true; - } else if (isValidKeyIndex(curKey)) { - return getSquareDistanceToKeyEdge(x, y, mKeys[curKey]) < mKeyHysteresisDistanceSquared; - } else { - return false; - } - } - - private static int getSquareDistanceToKeyEdge(int x, int y, Key key) { - final int left = key.x; - final int right = key.x + key.width; - final int top = key.y; - final int bottom = key.y + key.height; - final int edgeX = x < left ? left : (x > right ? right : x); - final int edgeY = y < top ? top : (y > bottom ? bottom : y); - final int dx = x - edgeX; - final int dy = y - edgeY; - return dx * dx + dy * dy; - } - - private void startTimeDebouncing(long eventTime) { - mLastKey = NOT_A_KEY; - mLastKeyTime = 0; - mCurrentKeyTime = 0; - mLastMoveTime = eventTime; - } - - private void updateTimeDebouncing(long eventTime) { - mCurrentKeyTime += eventTime - mLastMoveTime; - mLastMoveTime = eventTime; - } - - private void resetTimeDebouncing(long eventTime, int currentKey) { - mLastKey = currentKey; - mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime; - mCurrentKeyTime = 0; - mLastMoveTime = eventTime; - } - - private boolean isMinorTimeBounce() { - return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME - && mLastKey != NOT_A_KEY; - } - - private void showKeyPreviewAndUpdateKey(int keyIndex) { - updateKey(keyIndex); - // The modifier key, such as shift key, should not be shown as preview when multi-touch is - // supported. On thge other hand, if multi-touch is not supported, the modifier key should - // be shown as preview. - if (mHasDistinctMultitouch && isModifier()) { - mProxy.showPreview(NOT_A_KEY, this); - } else { - mProxy.showPreview(keyIndex, this); - } - } - - private void detectAndSendKey(int index, int x, int y, long eventTime) { - final OnKeyboardActionListener listener = mListener; - final Key key = getKey(index); - - if (key == null) { - if (listener != null) - listener.onCancel(); - } else { - if (key.text != null) { - if (listener != null) { - listener.onText(key.text); - listener.onRelease(NOT_A_KEY); - } - } else { - int code = key.codes[0]; - //TextEntryState.keyPressedAt(key, x, y); - int[] codes = mKeyDetector.newCodeArray(); - mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); - // Multi-tap - if (mInMultiTap) { - if (mTapCount != -1) { - mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y); - } else { - mTapCount = 0; - } - code = key.codes[mTapCount]; - } - /* - * 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; - } - if (listener != null) { - listener.onKey(code, codes, x, y); - listener.onRelease(code); - } - } - mLastSentIndex = index; - mLastTapTime = eventTime; - } - } - - /** - * Handle multi-tap keys by producing the key label for the current multi-tap state. - */ - public CharSequence getPreviewText(Key key) { - if (mInMultiTap) { - // Multi-tap - mPreviewLabel.setLength(0); - mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]); - return mPreviewLabel; - } else { - return key.label; - } - } - - private void resetMultiTap() { - mLastSentIndex = NOT_A_KEY; - mTapCount = 0; - mLastTapTime = -1; - mInMultiTap = false; - } - - private void checkMultiTap(long eventTime, int keyIndex) { - Key key = getKey(keyIndex); - if (key == null) - return; - - final boolean isMultiTap = - (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex); - if (key.codes.length > 1) { - mInMultiTap = true; - if (isMultiTap) { - mTapCount = (mTapCount + 1) % key.codes.length; - return; - } else { - mTapCount = -1; - return; - } - } - if (!isMultiTap) { - resetMultiTap(); - } - } - - private void debugLog(String title, int x, int y) { - Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); - final String code; - if (key == null) { - code = "----"; - } else { - int primaryCode = key.codes[0]; - code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode); - } - Log.d(TAG, String.format("%s [%d] %3d,%3d %s %s", title, mPointerId, x, y, code, - isModifier() ? "modifier" : "")); - } -}
\ No newline at end of file |