Home | History | Annotate | Download | only in keyboard
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.content.res.Resources;
     20 import android.content.res.TypedArray;
     21 import android.os.SystemClock;
     22 import android.util.Log;
     23 import android.view.MotionEvent;
     24 
     25 import com.android.inputmethod.keyboard.internal.BatchInputArbiter;
     26 import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener;
     27 import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector;
     28 import com.android.inputmethod.keyboard.internal.GestureEnabler;
     29 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams;
     30 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints;
     31 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams;
     32 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
     33 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
     34 import com.android.inputmethod.latin.Constants;
     35 import com.android.inputmethod.latin.InputPointers;
     36 import com.android.inputmethod.latin.R;
     37 import com.android.inputmethod.latin.define.DebugFlags;
     38 import com.android.inputmethod.latin.settings.Settings;
     39 import com.android.inputmethod.latin.utils.CoordinateUtils;
     40 import com.android.inputmethod.latin.utils.ResourceUtils;
     41 
     42 import java.util.ArrayList;
     43 
     44 public final class PointerTracker implements PointerTrackerQueue.Element,
     45         BatchInputArbiterListener {
     46     private static final String TAG = PointerTracker.class.getSimpleName();
     47     private static final boolean DEBUG_EVENT = false;
     48     private static final boolean DEBUG_MOVE_EVENT = false;
     49     private static final boolean DEBUG_LISTENER = false;
     50     private static boolean DEBUG_MODE = DebugFlags.DEBUG_ENABLED || DEBUG_EVENT;
     51 
     52     public interface DrawingProxy {
     53         public void invalidateKey(Key key);
     54         public void showKeyPreview(Key key);
     55         public void dismissKeyPreview(Key key);
     56         public void showSlidingKeyInputPreview(PointerTracker tracker);
     57         public void dismissSlidingKeyInputPreview();
     58         public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
     59     }
     60 
     61     public interface TimerProxy {
     62         public void startTypingStateTimer(Key typedKey);
     63         public boolean isTypingState();
     64         public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay);
     65         public void startLongPressTimerOf(PointerTracker tracker, int delay);
     66         public void cancelLongPressTimerOf(PointerTracker tracker);
     67         public void cancelLongPressShiftKeyTimers();
     68         public void cancelKeyTimersOf(PointerTracker tracker);
     69         public void startDoubleTapShiftKeyTimer();
     70         public void cancelDoubleTapShiftKeyTimer();
     71         public boolean isInDoubleTapShiftKeyTimeout();
     72         public void startUpdateBatchInputTimer(PointerTracker tracker);
     73         public void cancelUpdateBatchInputTimer(PointerTracker tracker);
     74         public void cancelAllUpdateBatchInputTimers();
     75 
     76         public static class Adapter implements TimerProxy {
     77             @Override
     78             public void startTypingStateTimer(Key typedKey) {}
     79             @Override
     80             public boolean isTypingState() { return false; }
     81             @Override
     82             public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay) {}
     83             @Override
     84             public void startLongPressTimerOf(PointerTracker tracker, int delay) {}
     85             @Override
     86             public void cancelLongPressTimerOf(PointerTracker tracker) {}
     87             @Override
     88             public void cancelLongPressShiftKeyTimers() {}
     89             @Override
     90             public void cancelKeyTimersOf(PointerTracker tracker) {}
     91             @Override
     92             public void startDoubleTapShiftKeyTimer() {}
     93             @Override
     94             public void cancelDoubleTapShiftKeyTimer() {}
     95             @Override
     96             public boolean isInDoubleTapShiftKeyTimeout() { return false; }
     97             @Override
     98             public void startUpdateBatchInputTimer(PointerTracker tracker) {}
     99             @Override
    100             public void cancelUpdateBatchInputTimer(PointerTracker tracker) {}
    101             @Override
    102             public void cancelAllUpdateBatchInputTimers() {}
    103         }
    104     }
    105 
    106     static final class PointerTrackerParams {
    107         public final boolean mKeySelectionByDraggingFinger;
    108         public final int mTouchNoiseThresholdTime;
    109         public final int mTouchNoiseThresholdDistance;
    110         public final int mSuppressKeyPreviewAfterBatchInputDuration;
    111         public final int mKeyRepeatStartTimeout;
    112         public final int mKeyRepeatInterval;
    113         public final int mLongPressShiftLockTimeout;
    114 
    115         public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
    116             mKeySelectionByDraggingFinger = mainKeyboardViewAttr.getBoolean(
    117                     R.styleable.MainKeyboardView_keySelectionByDraggingFinger, false);
    118             mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt(
    119                     R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0);
    120             mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimensionPixelSize(
    121                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
    122             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
    123                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
    124             mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
    125                     R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
    126             mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
    127                     R.styleable.MainKeyboardView_keyRepeatInterval, 0);
    128             mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
    129                     R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
    130         }
    131     }
    132 
    133     private static GestureEnabler sGestureEnabler = new GestureEnabler();
    134 
    135     // Parameters for pointer handling.
    136     private static PointerTrackerParams sParams;
    137     private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
    138     private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
    139     private static boolean sNeedsPhantomSuddenMoveEventHack;
    140     // Move this threshold to resource.
    141     // TODO: Device specific parameter would be better for device specific hack?
    142     private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
    143 
    144     private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>();
    145     private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
    146 
    147     public final int mPointerId;
    148 
    149     private static DrawingProxy sDrawingProxy;
    150     private static TimerProxy sTimerProxy;
    151     private static KeyboardActionListener sListener = KeyboardActionListener.EMPTY_LISTENER;
    152 
    153     // The {@link KeyDetector} is set whenever the down event is processed. Also this is updated
    154     // when new {@link Keyboard} is set by {@link #setKeyDetector(KeyDetector)}.
    155     private KeyDetector mKeyDetector = new KeyDetector();
    156     private Keyboard mKeyboard;
    157     private int mPhantomSuddenMoveThreshold;
    158     private final BogusMoveEventDetector mBogusMoveEventDetector = new BogusMoveEventDetector();
    159 
    160     private boolean mIsDetectingGesture = false; // per PointerTracker.
    161     private static boolean sInGesture = false;
    162     private static TypingTimeRecorder sTypingTimeRecorder;
    163 
    164     // The position and time at which first down event occurred.
    165     private long mDownTime;
    166     private int[] mDownCoordinates = CoordinateUtils.newInstance();
    167     private long mUpTime;
    168 
    169     // The current key where this pointer is.
    170     private Key mCurrentKey = null;
    171     // The position where the current key was recognized for the first time.
    172     private int mKeyX;
    173     private int mKeyY;
    174 
    175     // Last pointer position.
    176     private int mLastX;
    177     private int mLastY;
    178 
    179     // true if keyboard layout has been changed.
    180     private boolean mKeyboardLayoutHasBeenChanged;
    181 
    182     // true if this pointer is no longer triggering any action because it has been canceled.
    183     private boolean mIsTrackingForActionDisabled;
    184 
    185     // the more keys panel currently being shown. equals null if no panel is active.
    186     private MoreKeysPanel mMoreKeysPanel;
    187 
    188     private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
    189     // true if this pointer is in the dragging finger mode.
    190     boolean mIsInDraggingFinger;
    191     // true if this pointer is sliding from a modifier key and in the sliding key input mode,
    192     // so that further modifier keys should be ignored.
    193     boolean mIsInSlidingKeyInput;
    194     // if not a NOT_A_CODE, the key of this code is repeating
    195     private int mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
    196 
    197     // true if dragging finger is allowed.
    198     private boolean mIsAllowedDraggingFinger;
    199 
    200     private final BatchInputArbiter mBatchInputArbiter;
    201     private final GestureStrokeDrawingPoints mGestureStrokeDrawingPoints;
    202 
    203     // TODO: Add PointerTrackerFactory singleton and move some class static methods into it.
    204     public static void init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy,
    205             final DrawingProxy drawingProxy) {
    206         sParams = new PointerTrackerParams(mainKeyboardViewAttr);
    207         sGestureStrokeRecognitionParams = new GestureStrokeRecognitionParams(mainKeyboardViewAttr);
    208         sGestureStrokeDrawingParams = new GestureStrokeDrawingParams(mainKeyboardViewAttr);
    209         sTypingTimeRecorder = new TypingTimeRecorder(
    210                 sGestureStrokeRecognitionParams.mStaticTimeThresholdAfterFastTyping,
    211                 sParams.mSuppressKeyPreviewAfterBatchInputDuration);
    212 
    213         final Resources res = mainKeyboardViewAttr.getResources();
    214         sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
    215                 ResourceUtils.getDeviceOverrideValue(res,
    216                         R.array.phantom_sudden_move_event_device_list, Boolean.FALSE.toString()));
    217         BogusMoveEventDetector.init(res);
    218 
    219         sTimerProxy = timerProxy;
    220         sDrawingProxy = drawingProxy;
    221     }
    222 
    223     // Note that this method is called from a non-UI thread.
    224     public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
    225         sGestureEnabler.setMainDictionaryAvailability(mainDictionaryAvailable);
    226     }
    227 
    228     public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
    229         sGestureEnabler.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
    230     }
    231 
    232     public static PointerTracker getPointerTracker(final int id) {
    233         final ArrayList<PointerTracker> trackers = sTrackers;
    234 
    235         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
    236         for (int i = trackers.size(); i <= id; i++) {
    237             final PointerTracker tracker = new PointerTracker(i);
    238             trackers.add(tracker);
    239         }
    240 
    241         return trackers.get(id);
    242     }
    243 
    244     public static boolean isAnyInDraggingFinger() {
    245         return sPointerTrackerQueue.isAnyInDraggingFinger();
    246     }
    247 
    248     public static void cancelAllPointerTrackers() {
    249         sPointerTrackerQueue.cancelAllPointerTrackers();
    250     }
    251 
    252     public static void setKeyboardActionListener(final KeyboardActionListener listener) {
    253         sListener = listener;
    254     }
    255 
    256     public static void setKeyDetector(final KeyDetector keyDetector) {
    257         final Keyboard keyboard = keyDetector.getKeyboard();
    258         if (keyboard == null) {
    259             return;
    260         }
    261         final int trackersSize = sTrackers.size();
    262         for (int i = 0; i < trackersSize; ++i) {
    263             final PointerTracker tracker = sTrackers.get(i);
    264             tracker.setKeyDetectorInner(keyDetector);
    265         }
    266         sGestureEnabler.setPasswordMode(keyboard.mId.passwordInput());
    267     }
    268 
    269     public static void setReleasedKeyGraphicsToAllKeys() {
    270         final int trackersSize = sTrackers.size();
    271         for (int i = 0; i < trackersSize; ++i) {
    272             final PointerTracker tracker = sTrackers.get(i);
    273             tracker.setReleasedKeyGraphics(tracker.getKey());
    274         }
    275     }
    276 
    277     public static void dismissAllMoreKeysPanels() {
    278         final int trackersSize = sTrackers.size();
    279         for (int i = 0; i < trackersSize; ++i) {
    280             final PointerTracker tracker = sTrackers.get(i);
    281             tracker.dismissMoreKeysPanel();
    282         }
    283     }
    284 
    285     private PointerTracker(final int id) {
    286         mPointerId = id;
    287         mBatchInputArbiter = new BatchInputArbiter(id, sGestureStrokeRecognitionParams);
    288         mGestureStrokeDrawingPoints = new GestureStrokeDrawingPoints(sGestureStrokeDrawingParams);
    289     }
    290 
    291     // Returns true if keyboard has been changed by this callback.
    292     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
    293             final int repeatCount) {
    294         // While gesture input is going on, this method should be a no-operation. But when gesture
    295         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
    296         // are set to false. To keep this method is a no-operation,
    297         // <code>mIsTrackingForActionDisabled</code> should also be taken account of.
    298         if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
    299             return false;
    300         }
    301         final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
    302         if (DEBUG_LISTENER) {
    303             Log.d(TAG, String.format("[%d] onPress    : %s%s%s%s", mPointerId,
    304                     (key == null ? "none" : Constants.printableCode(key.getCode())),
    305                     ignoreModifierKey ? " ignoreModifier" : "",
    306                     key.isEnabled() ? "" : " disabled",
    307                     repeatCount > 0 ? " repeatCount=" + repeatCount : ""));
    308         }
    309         if (ignoreModifierKey) {
    310             return false;
    311         }
    312         if (key.isEnabled()) {
    313             sListener.onPressKey(key.getCode(), repeatCount, getActivePointerTrackerCount() == 1);
    314             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
    315             mKeyboardLayoutHasBeenChanged = false;
    316             sTimerProxy.startTypingStateTimer(key);
    317             return keyboardLayoutHasBeenChanged;
    318         }
    319         return false;
    320     }
    321 
    322     // Note that we need primaryCode argument because the keyboard may in shifted state and the
    323     // primaryCode is different from {@link Key#mKeyCode}.
    324     private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
    325             final int y, final long eventTime, final boolean isKeyRepeat) {
    326         final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
    327         final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
    328         final int code = altersCode ? key.getAltCode() : primaryCode;
    329         if (DEBUG_LISTENER) {
    330             final String output = code == Constants.CODE_OUTPUT_TEXT
    331                     ? key.getOutputText() : Constants.printableCode(code);
    332             Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y,
    333                     output, ignoreModifierKey ? " ignoreModifier" : "",
    334                     altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled"));
    335         }
    336         if (ignoreModifierKey) {
    337             return;
    338         }
    339         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
    340         if (key.isEnabled() || altersCode) {
    341             sTypingTimeRecorder.onCodeInput(code, eventTime);
    342             if (code == Constants.CODE_OUTPUT_TEXT) {
    343                 sListener.onTextInput(key.getOutputText());
    344             } else if (code != Constants.CODE_UNSPECIFIED) {
    345                 if (mKeyboard.hasProximityCharsCorrection(code)) {
    346                     sListener.onCodeInput(code, x, y, isKeyRepeat);
    347                 } else {
    348                     sListener.onCodeInput(code,
    349                             Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, isKeyRepeat);
    350                 }
    351             }
    352         }
    353     }
    354 
    355     // Note that we need primaryCode argument because the keyboard may be in shifted state and the
    356     // primaryCode is different from {@link Key#mKeyCode}.
    357     private void callListenerOnRelease(final Key key, final int primaryCode,
    358             final boolean withSliding) {
    359         // See the comment at {@link #callListenerOnPressAndCheckKeyboardLayoutChange(Key}}.
    360         if (sInGesture || mIsDetectingGesture || mIsTrackingForActionDisabled) {
    361             return;
    362         }
    363         final boolean ignoreModifierKey = mIsInDraggingFinger && key.isModifier();
    364         if (DEBUG_LISTENER) {
    365             Log.d(TAG, String.format("[%d] onRelease  : %s%s%s%s", mPointerId,
    366                     Constants.printableCode(primaryCode),
    367                     withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "",
    368                     key.isEnabled() ?  "": " disabled"));
    369         }
    370         if (ignoreModifierKey) {
    371             return;
    372         }
    373         if (key.isEnabled()) {
    374             sListener.onReleaseKey(primaryCode, withSliding);
    375         }
    376     }
    377 
    378     private void callListenerOnFinishSlidingInput() {
    379         if (DEBUG_LISTENER) {
    380             Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId));
    381         }
    382         sListener.onFinishSlidingInput();
    383     }
    384 
    385     private void callListenerOnCancelInput() {
    386         if (DEBUG_LISTENER) {
    387             Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
    388         }
    389         sListener.onCancelInput();
    390     }
    391 
    392     private void setKeyDetectorInner(final KeyDetector keyDetector) {
    393         final Keyboard keyboard = keyDetector.getKeyboard();
    394         if (keyboard == null) {
    395             return;
    396         }
    397         if (keyDetector == mKeyDetector && keyboard == mKeyboard) {
    398             return;
    399         }
    400         mKeyDetector = keyDetector;
    401         mKeyboard = keyboard;
    402         // Mark that keyboard layout has been changed.
    403         mKeyboardLayoutHasBeenChanged = true;
    404         final int keyWidth = mKeyboard.mMostCommonKeyWidth;
    405         final int keyHeight = mKeyboard.mMostCommonKeyHeight;
    406         mBatchInputArbiter.setKeyboardGeometry(keyWidth, mKeyboard.mOccupiedHeight);
    407         // Keep {@link #mCurrentKey} that comes from previous keyboard. The key preview of
    408         // {@link #mCurrentKey} will be dismissed by {@setReleasedKeyGraphics(Key)} via
    409         // {@link onMoveEventInternal(int,int,long)} or {@link #onUpEventInternal(int,int,long)}.
    410         mPhantomSuddenMoveThreshold = (int)(keyWidth * PHANTOM_SUDDEN_MOVE_THRESHOLD);
    411         mBogusMoveEventDetector.setKeyboardGeometry(keyWidth, keyHeight);
    412     }
    413 
    414     @Override
    415     public boolean isInDraggingFinger() {
    416         return mIsInDraggingFinger;
    417     }
    418 
    419     public Key getKey() {
    420         return mCurrentKey;
    421     }
    422 
    423     @Override
    424     public boolean isModifier() {
    425         return mCurrentKey != null && mCurrentKey.isModifier();
    426     }
    427 
    428     public Key getKeyOn(final int x, final int y) {
    429         return mKeyDetector.detectHitKey(x, y);
    430     }
    431 
    432     private void setReleasedKeyGraphics(final Key key) {
    433         sDrawingProxy.dismissKeyPreview(key);
    434         if (key == null) {
    435             return;
    436         }
    437 
    438         // Even if the key is disabled, update the key release graphics just in case.
    439         updateReleaseKeyGraphics(key);
    440 
    441         if (key.isShift()) {
    442             for (final Key shiftKey : mKeyboard.mShiftKeys) {
    443                 if (shiftKey != key) {
    444                     updateReleaseKeyGraphics(shiftKey);
    445                 }
    446             }
    447         }
    448 
    449         if (key.altCodeWhileTyping()) {
    450             final int altCode = key.getAltCode();
    451             final Key altKey = mKeyboard.getKey(altCode);
    452             if (altKey != null) {
    453                 updateReleaseKeyGraphics(altKey);
    454             }
    455             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
    456                 if (k != key && k.getAltCode() == altCode) {
    457                     updateReleaseKeyGraphics(k);
    458                 }
    459             }
    460         }
    461     }
    462 
    463     private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
    464         if (!sGestureEnabler.shouldHandleGesture()) return false;
    465         return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
    466     }
    467 
    468     private void setPressedKeyGraphics(final Key key, final long eventTime) {
    469         if (key == null) {
    470             return;
    471         }
    472 
    473         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
    474         final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
    475         final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
    476         if (!needsToUpdateGraphics) {
    477             return;
    478         }
    479 
    480         if (!key.noKeyPreview() && !sInGesture && !needsToSuppressKeyPreviewPopup(eventTime)) {
    481             sDrawingProxy.showKeyPreview(key);
    482         }
    483         updatePressKeyGraphics(key);
    484 
    485         if (key.isShift()) {
    486             for (final Key shiftKey : mKeyboard.mShiftKeys) {
    487                 if (shiftKey != key) {
    488                     updatePressKeyGraphics(shiftKey);
    489                 }
    490             }
    491         }
    492 
    493         if (altersCode) {
    494             final int altCode = key.getAltCode();
    495             final Key altKey = mKeyboard.getKey(altCode);
    496             if (altKey != null) {
    497                 updatePressKeyGraphics(altKey);
    498             }
    499             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
    500                 if (k != key && k.getAltCode() == altCode) {
    501                     updatePressKeyGraphics(k);
    502                 }
    503             }
    504         }
    505     }
    506 
    507     private static void updateReleaseKeyGraphics(final Key key) {
    508         key.onReleased();
    509         sDrawingProxy.invalidateKey(key);
    510     }
    511 
    512     private static void updatePressKeyGraphics(final Key key) {
    513         key.onPressed();
    514         sDrawingProxy.invalidateKey(key);
    515     }
    516 
    517     public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() {
    518         return mGestureStrokeDrawingPoints;
    519     }
    520 
    521     public void getLastCoordinates(final int[] outCoords) {
    522         CoordinateUtils.set(outCoords, mLastX, mLastY);
    523     }
    524 
    525     public long getDownTime() {
    526         return mDownTime;
    527     }
    528 
    529     public void getDownCoordinates(final int[] outCoords) {
    530         CoordinateUtils.copy(outCoords, mDownCoordinates);
    531     }
    532 
    533     private Key onDownKey(final int x, final int y, final long eventTime) {
    534         mDownTime = eventTime;
    535         CoordinateUtils.set(mDownCoordinates, x, y);
    536         mBogusMoveEventDetector.onDownKey();
    537         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
    538     }
    539 
    540     private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
    541         return (int)Math.hypot(x1 - x2, y1 - y2);
    542     }
    543 
    544     private Key onMoveKeyInternal(final int x, final int y) {
    545         mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
    546         mLastX = x;
    547         mLastY = y;
    548         return mKeyDetector.detectHitKey(x, y);
    549     }
    550 
    551     private Key onMoveKey(final int x, final int y) {
    552         return onMoveKeyInternal(x, y);
    553     }
    554 
    555     private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
    556         mCurrentKey = newKey;
    557         mKeyX = x;
    558         mKeyY = y;
    559         return newKey;
    560     }
    561 
    562     /* package */ static int getActivePointerTrackerCount() {
    563         return sPointerTrackerQueue.size();
    564     }
    565 
    566     private boolean isOldestTrackerInQueue() {
    567         return sPointerTrackerQueue.getOldestElement() == this;
    568     }
    569 
    570     // Implements {@link BatchInputArbiterListener}.
    571     @Override
    572     public void onStartBatchInput() {
    573         if (DEBUG_LISTENER) {
    574             Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
    575         }
    576         sListener.onStartBatchInput();
    577         dismissAllMoreKeysPanels();
    578         sTimerProxy.cancelLongPressTimerOf(this);
    579     }
    580 
    581     private void showGestureTrail() {
    582         if (mIsTrackingForActionDisabled) {
    583             return;
    584         }
    585         // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
    586         sDrawingProxy.showGestureTrail(
    587                 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
    588     }
    589 
    590     public void updateBatchInputByTimer(final long syntheticMoveEventTime) {
    591         mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this);
    592     }
    593 
    594     // Implements {@link BatchInputArbiterListener}.
    595     @Override
    596     public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
    597         if (DEBUG_LISTENER) {
    598             Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
    599                     aggregatedPointers.getPointerSize()));
    600         }
    601         sListener.onUpdateBatchInput(aggregatedPointers);
    602     }
    603 
    604     // Implements {@link BatchInputArbiterListener}.
    605     @Override
    606     public void onStartUpdateBatchInputTimer() {
    607         sTimerProxy.startUpdateBatchInputTimer(this);
    608     }
    609 
    610     // Implements {@link BatchInputArbiterListener}.
    611     @Override
    612     public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
    613         sTypingTimeRecorder.onEndBatchInput(eventTime);
    614         sTimerProxy.cancelAllUpdateBatchInputTimers();
    615         if (mIsTrackingForActionDisabled) {
    616             return;
    617         }
    618         if (DEBUG_LISTENER) {
    619             Log.d(TAG, String.format("[%d] onEndBatchInput   : batchPoints=%d",
    620                     mPointerId, aggregatedPointers.getPointerSize()));
    621         }
    622         sListener.onEndBatchInput(aggregatedPointers);
    623     }
    624 
    625     private void cancelBatchInput() {
    626         cancelAllPointerTrackers();
    627         mIsDetectingGesture = false;
    628         if (!sInGesture) {
    629             return;
    630         }
    631         sInGesture = false;
    632         if (DEBUG_LISTENER) {
    633             Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId));
    634         }
    635         sListener.onCancelBatchInput();
    636     }
    637 
    638     public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
    639         final int action = me.getActionMasked();
    640         final long eventTime = me.getEventTime();
    641         if (action == MotionEvent.ACTION_MOVE) {
    642             // When this pointer is the only active pointer and is showing a more keys panel,
    643             // we should ignore other pointers' motion event.
    644             final boolean shouldIgnoreOtherPointers =
    645                     isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1;
    646             final int pointerCount = me.getPointerCount();
    647             for (int index = 0; index < pointerCount; index++) {
    648                 final int id = me.getPointerId(index);
    649                 if (shouldIgnoreOtherPointers && id != mPointerId) {
    650                     continue;
    651                 }
    652                 final int x = (int)me.getX(index);
    653                 final int y = (int)me.getY(index);
    654                 final PointerTracker tracker = getPointerTracker(id);
    655                 tracker.onMoveEvent(x, y, eventTime, me);
    656             }
    657             return;
    658         }
    659         final int index = me.getActionIndex();
    660         final int x = (int)me.getX(index);
    661         final int y = (int)me.getY(index);
    662         switch (action) {
    663         case MotionEvent.ACTION_DOWN:
    664         case MotionEvent.ACTION_POINTER_DOWN:
    665             onDownEvent(x, y, eventTime, keyDetector);
    666             break;
    667         case MotionEvent.ACTION_UP:
    668         case MotionEvent.ACTION_POINTER_UP:
    669             onUpEvent(x, y, eventTime);
    670             break;
    671         case MotionEvent.ACTION_CANCEL:
    672             onCancelEvent(x, y, eventTime);
    673             break;
    674         }
    675     }
    676 
    677     private void onDownEvent(final int x, final int y, final long eventTime,
    678             final KeyDetector keyDetector) {
    679         setKeyDetectorInner(keyDetector);
    680         if (DEBUG_EVENT) {
    681             printTouchEvent("onDownEvent:", x, y, eventTime);
    682         }
    683         // Naive up-to-down noise filter.
    684         final long deltaT = eventTime - mUpTime;
    685         if (deltaT < sParams.mTouchNoiseThresholdTime) {
    686             final int distance = getDistance(x, y, mLastX, mLastY);
    687             if (distance < sParams.mTouchNoiseThresholdDistance) {
    688                 if (DEBUG_MODE)
    689                     Log.w(TAG, String.format("[%d] onDownEvent:"
    690                             + " ignore potential noise: time=%d distance=%d",
    691                             mPointerId, deltaT, distance));
    692                 cancelTrackingForAction();
    693                 return;
    694             }
    695         }
    696 
    697         final Key key = getKeyOn(x, y);
    698         mBogusMoveEventDetector.onActualDownEvent(x, y);
    699         if (key != null && key.isModifier()) {
    700             // Before processing a down event of modifier key, all pointers already being
    701             // tracked should be released.
    702             sPointerTrackerQueue.releaseAllPointers(eventTime);
    703         }
    704         sPointerTrackerQueue.add(this);
    705         onDownEventInternal(x, y, eventTime);
    706         if (!sGestureEnabler.shouldHandleGesture()) {
    707             return;
    708         }
    709         // A gesture should start only from a non-modifier key. Note that the gesture detection is
    710         // disabled when the key is repeating.
    711         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
    712                 && key != null && !key.isModifier();
    713         if (mIsDetectingGesture) {
    714             mBatchInputArbiter.addDownEventPoint(x, y, eventTime,
    715                     sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount());
    716             mGestureStrokeDrawingPoints.onDownEvent(
    717                     x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
    718         }
    719     }
    720 
    721     /* package */ boolean isShowingMoreKeysPanel() {
    722         return (mMoreKeysPanel != null);
    723     }
    724 
    725     private void dismissMoreKeysPanel() {
    726         if (isShowingMoreKeysPanel()) {
    727             mMoreKeysPanel.dismissMoreKeysPanel();
    728             mMoreKeysPanel = null;
    729         }
    730     }
    731 
    732     private void onDownEventInternal(final int x, final int y, final long eventTime) {
    733         Key key = onDownKey(x, y, eventTime);
    734         // Key selection by dragging finger is allowed when 1) key selection by dragging finger is
    735         // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this
    736         // pointer's KeyDetector always allows key selection by dragging finger, such as
    737         // {@link MoreKeysKeyboard}.
    738         mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger
    739                 || (key != null && key.isModifier())
    740                 || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger();
    741         mKeyboardLayoutHasBeenChanged = false;
    742         mIsTrackingForActionDisabled = false;
    743         resetKeySelectionByDraggingFinger();
    744         if (key != null) {
    745             // This onPress call may have changed keyboard layout. Those cases are detected at
    746             // {@link #setKeyboard}. In those cases, we should update key according to the new
    747             // keyboard layout.
    748             if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
    749                 key = onDownKey(x, y, eventTime);
    750             }
    751 
    752             startRepeatKey(key);
    753             startLongPressTimer(key);
    754             setPressedKeyGraphics(key, eventTime);
    755         }
    756     }
    757 
    758     private void startKeySelectionByDraggingFinger(final Key key) {
    759         if (!mIsInDraggingFinger) {
    760             mIsInSlidingKeyInput = key.isModifier();
    761         }
    762         mIsInDraggingFinger = true;
    763     }
    764 
    765     private void resetKeySelectionByDraggingFinger() {
    766         mIsInDraggingFinger = false;
    767         mIsInSlidingKeyInput = false;
    768         sDrawingProxy.dismissSlidingKeyInputPreview();
    769     }
    770 
    771     private void onGestureMoveEvent(final int x, final int y, final long eventTime,
    772             final boolean isMajorEvent, final Key key) {
    773         if (!mIsDetectingGesture) {
    774             return;
    775         }
    776         final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint(
    777                 x, y, eventTime, isMajorEvent, this);
    778         // If the move event goes out from valid batch input area, cancel batch input.
    779         if (!onValidArea) {
    780             cancelBatchInput();
    781             return;
    782         }
    783         mGestureStrokeDrawingPoints.onMoveEvent(
    784                 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
    785         // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However,
    786         // the gestured touch points are still being recorded in case the panel is dismissed.
    787         if (isShowingMoreKeysPanel()) {
    788             return;
    789         }
    790         if (!sInGesture && key != null && Character.isLetter(key.getCode())
    791                 && mBatchInputArbiter.mayStartBatchInput(this)) {
    792             sInGesture = true;
    793         }
    794         if (sInGesture) {
    795             if (key != null) {
    796                 mBatchInputArbiter.updateBatchInput(eventTime, this);
    797             }
    798             showGestureTrail();
    799         }
    800     }
    801 
    802     private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
    803         if (DEBUG_MOVE_EVENT) {
    804             printTouchEvent("onMoveEvent:", x, y, eventTime);
    805         }
    806         if (mIsTrackingForActionDisabled) {
    807             return;
    808         }
    809 
    810         if (sGestureEnabler.shouldHandleGesture() && me != null) {
    811             // Add historical points to gesture path.
    812             final int pointerIndex = me.findPointerIndex(mPointerId);
    813             final int historicalSize = me.getHistorySize();
    814             for (int h = 0; h < historicalSize; h++) {
    815                 final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
    816                 final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
    817                 final long historicalTime = me.getHistoricalEventTime(h);
    818                 onGestureMoveEvent(historicalX, historicalY, historicalTime,
    819                         false /* isMajorEvent */, null);
    820             }
    821         }
    822 
    823         if (isShowingMoreKeysPanel()) {
    824             final int translatedX = mMoreKeysPanel.translateX(x);
    825             final int translatedY = mMoreKeysPanel.translateY(y);
    826             mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
    827             onMoveKey(x, y);
    828             if (mIsInSlidingKeyInput) {
    829                 sDrawingProxy.showSlidingKeyInputPreview(this);
    830             }
    831             return;
    832         }
    833         onMoveEventInternal(x, y, eventTime);
    834     }
    835 
    836     private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y,
    837             final long eventTime) {
    838         // This onPress call may have changed keyboard layout. Those cases are detected
    839         // at {@link #setKeyboard}. In those cases, we should update key according
    840         // to the new keyboard layout.
    841         Key key = newKey;
    842         if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
    843             key = onMoveKey(x, y);
    844         }
    845         onMoveToNewKey(key, x, y);
    846         if (mIsTrackingForActionDisabled) {
    847             return;
    848         }
    849         startLongPressTimer(key);
    850         setPressedKeyGraphics(key, eventTime);
    851     }
    852 
    853     private void processPhantomSuddenMoveHack(final Key key, final int x, final int y,
    854             final long eventTime, final Key oldKey, final int lastX, final int lastY) {
    855         if (DEBUG_MODE) {
    856             Log.w(TAG, String.format("[%d] onMoveEvent:"
    857                     + " phantom sudden move event (distance=%d) is translated to "
    858                     + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId,
    859                     getDistance(x, y, lastX, lastY),
    860                     lastX, lastY, Constants.printableCode(oldKey.getCode()),
    861                     x, y, Constants.printableCode(key.getCode())));
    862         }
    863         onUpEventInternal(x, y, eventTime);
    864         onDownEventInternal(x, y, eventTime);
    865     }
    866 
    867     private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y,
    868             final long eventTime, final Key oldKey, final int lastX, final int lastY) {
    869         if (DEBUG_MODE) {
    870             final float keyDiagonal = (float)Math.hypot(
    871                     mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
    872             final float radiusRatio =
    873                     mBogusMoveEventDetector.getDistanceFromDownEvent(x, y)
    874                     / keyDiagonal;
    875             Log.w(TAG, String.format("[%d] onMoveEvent:"
    876                     + " bogus down-move-up event (raidus=%.2f key diagonal) is "
    877                     + " translated to up[%d,%d,%s]/down[%d,%d,%s] events",
    878                     mPointerId, radiusRatio,
    879                     lastX, lastY, Constants.printableCode(oldKey.getCode()),
    880                     x, y, Constants.printableCode(key.getCode())));
    881         }
    882         onUpEventInternal(x, y, eventTime);
    883         onDownEventInternal(x, y, eventTime);
    884     }
    885 
    886     private void processDraggingFingerOutFromOldKey(final Key oldKey) {
    887         setReleasedKeyGraphics(oldKey);
    888         callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
    889         startKeySelectionByDraggingFinger(oldKey);
    890         sTimerProxy.cancelKeyTimersOf(this);
    891     }
    892 
    893     private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y,
    894             final long eventTime, final Key oldKey, final int lastX, final int lastY) {
    895         // The pointer has been slid in to the new key from the previous key, we must call
    896         // onRelease() first to notify that the previous key has been released, then call
    897         // onPress() to notify that the new key is being pressed.
    898         processDraggingFingerOutFromOldKey(oldKey);
    899         startRepeatKey(key);
    900         if (mIsAllowedDraggingFinger) {
    901             processDraggingFingerInToNewKey(key, x, y, eventTime);
    902         }
    903         // HACK: On some devices, quick successive touches may be reported as a sudden move by
    904         // touch panel firmware. This hack detects such cases and translates the move event to
    905         // successive up and down events.
    906         // TODO: Should find a way to balance gesture detection and this hack.
    907         else if (sNeedsPhantomSuddenMoveEventHack
    908                 && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) {
    909             processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY);
    910         }
    911         // HACK: On some devices, quick successive proximate touches may be reported as a bogus
    912         // down-move-up event by touch panel firmware. This hack detects such cases and breaks
    913         // these events into separate up and down events.
    914         else if (sTypingTimeRecorder.isInFastTyping(eventTime)
    915                 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) {
    916             processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY);
    917         }
    918         // HACK: If there are currently multiple touches, register the key even if the finger
    919         // slides off the key. This defends against noise from some touch panels when there are
    920         // close multiple touches.
    921         // Caveat: When in chording input mode with a modifier key, we don't use this hack.
    922         else if (getActivePointerTrackerCount() > 1
    923                 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
    924             if (DEBUG_MODE) {
    925                 Log.w(TAG, String.format("[%d] onMoveEvent:"
    926                         + " detected sliding finger while multi touching", mPointerId));
    927             }
    928             onUpEvent(x, y, eventTime);
    929             cancelTrackingForAction();
    930             setReleasedKeyGraphics(oldKey);
    931         } else {
    932             if (!mIsDetectingGesture) {
    933                 cancelTrackingForAction();
    934             }
    935             setReleasedKeyGraphics(oldKey);
    936         }
    937     }
    938 
    939     private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) {
    940         // The pointer has been slid out from the previous key, we must call onRelease() to
    941         // notify that the previous key has been released.
    942         processDraggingFingerOutFromOldKey(oldKey);
    943         if (mIsAllowedDraggingFinger) {
    944             onMoveToNewKey(null, x, y);
    945         } else {
    946             if (!mIsDetectingGesture) {
    947                 cancelTrackingForAction();
    948             }
    949         }
    950     }
    951 
    952     private void onMoveEventInternal(final int x, final int y, final long eventTime) {
    953         final int lastX = mLastX;
    954         final int lastY = mLastY;
    955         final Key oldKey = mCurrentKey;
    956         final Key newKey = onMoveKey(x, y);
    957 
    958         if (sGestureEnabler.shouldHandleGesture()) {
    959             // Register move event on gesture tracker.
    960             onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey);
    961             if (sInGesture) {
    962                 mCurrentKey = null;
    963                 setReleasedKeyGraphics(oldKey);
    964                 return;
    965             }
    966         }
    967 
    968         if (newKey != null) {
    969             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
    970                 dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
    971             } else if (oldKey == null) {
    972                 // The pointer has been slid in to the new key, but the finger was not on any keys.
    973                 // In this case, we must call onPress() to notify that the new key is being pressed.
    974                 processDraggingFingerInToNewKey(newKey, x, y, eventTime);
    975             }
    976         } else { // newKey == null
    977             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
    978                 dragFingerOutFromOldKey(oldKey, x, y);
    979             }
    980         }
    981         if (mIsInSlidingKeyInput) {
    982             sDrawingProxy.showSlidingKeyInputPreview(this);
    983         }
    984     }
    985 
    986     private void onUpEvent(final int x, final int y, final long eventTime) {
    987         if (DEBUG_EVENT) {
    988             printTouchEvent("onUpEvent  :", x, y, eventTime);
    989         }
    990 
    991         sTimerProxy.cancelUpdateBatchInputTimer(this);
    992         if (!sInGesture) {
    993             if (mCurrentKey != null && mCurrentKey.isModifier()) {
    994                 // Before processing an up event of modifier key, all pointers already being
    995                 // tracked should be released.
    996                 sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime);
    997             } else {
    998                 sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime);
    999             }
   1000         }
   1001         onUpEventInternal(x, y, eventTime);
   1002         sPointerTrackerQueue.remove(this);
   1003     }
   1004 
   1005     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
   1006     // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
   1007     // "virtual" up event.
   1008     @Override
   1009     public void onPhantomUpEvent(final long eventTime) {
   1010         if (DEBUG_EVENT) {
   1011             printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime);
   1012         }
   1013         onUpEventInternal(mLastX, mLastY, eventTime);
   1014         cancelTrackingForAction();
   1015     }
   1016 
   1017     private void onUpEventInternal(final int x, final int y, final long eventTime) {
   1018         sTimerProxy.cancelKeyTimersOf(this);
   1019         final boolean isInDraggingFinger = mIsInDraggingFinger;
   1020         final boolean isInSlidingKeyInput = mIsInSlidingKeyInput;
   1021         resetKeySelectionByDraggingFinger();
   1022         mIsDetectingGesture = false;
   1023         final Key currentKey = mCurrentKey;
   1024         mCurrentKey = null;
   1025         final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode;
   1026         mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
   1027         // Release the last pressed key.
   1028         setReleasedKeyGraphics(currentKey);
   1029 
   1030         if (isShowingMoreKeysPanel()) {
   1031             if (!mIsTrackingForActionDisabled) {
   1032                 final int translatedX = mMoreKeysPanel.translateX(x);
   1033                 final int translatedY = mMoreKeysPanel.translateY(y);
   1034                 mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime);
   1035             }
   1036             dismissMoreKeysPanel();
   1037             return;
   1038         }
   1039 
   1040         if (sInGesture) {
   1041             if (currentKey != null) {
   1042                 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */);
   1043             }
   1044             if (mBatchInputArbiter.mayEndBatchInput(
   1045                     eventTime, getActivePointerTrackerCount(), this)) {
   1046                 sInGesture = false;
   1047             }
   1048             showGestureTrail();
   1049             return;
   1050         }
   1051 
   1052         if (mIsTrackingForActionDisabled) {
   1053             return;
   1054         }
   1055         if (currentKey != null && currentKey.isRepeatable()
   1056                 && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) {
   1057             return;
   1058         }
   1059         detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
   1060         if (isInSlidingKeyInput) {
   1061             callListenerOnFinishSlidingInput();
   1062         }
   1063     }
   1064 
   1065     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
   1066         setReleasedKeyGraphics(mCurrentKey);
   1067         final int translatedX = panel.translateX(mLastX);
   1068         final int translatedY = panel.translateY(mLastY);
   1069         panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis());
   1070         mMoreKeysPanel = panel;
   1071     }
   1072 
   1073     @Override
   1074     public void cancelTrackingForAction() {
   1075         if (isShowingMoreKeysPanel()) {
   1076             return;
   1077         }
   1078         mIsTrackingForActionDisabled = true;
   1079     }
   1080 
   1081     public boolean isInOperation() {
   1082         return !mIsTrackingForActionDisabled;
   1083     }
   1084 
   1085     public void cancelLongPressTimer() {
   1086         sTimerProxy.cancelLongPressTimerOf(this);
   1087     }
   1088 
   1089     public void onLongPressed() {
   1090         resetKeySelectionByDraggingFinger();
   1091         cancelTrackingForAction();
   1092         setReleasedKeyGraphics(mCurrentKey);
   1093         sPointerTrackerQueue.remove(this);
   1094     }
   1095 
   1096     private void onCancelEvent(final int x, final int y, final long eventTime) {
   1097         if (DEBUG_EVENT) {
   1098             printTouchEvent("onCancelEvt:", x, y, eventTime);
   1099         }
   1100 
   1101         cancelBatchInput();
   1102         cancelAllPointerTrackers();
   1103         sPointerTrackerQueue.releaseAllPointers(eventTime);
   1104         onCancelEventInternal();
   1105     }
   1106 
   1107     private void onCancelEventInternal() {
   1108         sTimerProxy.cancelKeyTimersOf(this);
   1109         setReleasedKeyGraphics(mCurrentKey);
   1110         resetKeySelectionByDraggingFinger();
   1111         dismissMoreKeysPanel();
   1112     }
   1113 
   1114     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
   1115             final Key newKey) {
   1116         final Key curKey = mCurrentKey;
   1117         if (newKey == curKey) {
   1118             return false;
   1119         }
   1120         if (curKey == null /* && newKey != null */) {
   1121             return true;
   1122         }
   1123         // Here curKey points to the different key from newKey.
   1124         final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
   1125                 mIsInSlidingKeyInput);
   1126         final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
   1127         if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
   1128             if (DEBUG_MODE) {
   1129                 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared)
   1130                         / mKeyboard.mMostCommonKeyWidth;
   1131                 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
   1132                         +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio));
   1133             }
   1134             return true;
   1135         }
   1136         if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime)
   1137                 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) {
   1138             if (DEBUG_MODE) {
   1139                 final float keyDiagonal = (float)Math.hypot(
   1140                         mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
   1141                 final float lengthFromDownRatio =
   1142                         mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal;
   1143                 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
   1144                         + " %.2f key diagonal from virtual down point",
   1145                         mPointerId, lengthFromDownRatio));
   1146             }
   1147             return true;
   1148         }
   1149         return false;
   1150     }
   1151 
   1152     private void startLongPressTimer(final Key key) {
   1153         // Note that we need to cancel all active long press shift key timers if any whenever we
   1154         // start a new long press timer for both non-shift and shift keys.
   1155         sTimerProxy.cancelLongPressShiftKeyTimers();
   1156         if (sInGesture) return;
   1157         if (key == null) return;
   1158         if (!key.isLongPressEnabled()) return;
   1159         // Caveat: Please note that isLongPressEnabled() can be true even if the current key
   1160         // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger
   1161         // mode, we will disable long press timer of such key.
   1162         // We always need to start the long press timer if the key has its more keys regardless of
   1163         // whether or not we are in the dragging finger mode.
   1164         if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
   1165 
   1166         final int delay = getLongPressTimeout(key.getCode());
   1167         if (delay <= 0) return;
   1168         sTimerProxy.startLongPressTimerOf(this, delay);
   1169     }
   1170 
   1171     private int getLongPressTimeout(final int code) {
   1172         if (code == Constants.CODE_SHIFT) {
   1173             return sParams.mLongPressShiftLockTimeout;
   1174         }
   1175         final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
   1176         if (mIsInSlidingKeyInput) {
   1177             // We use longer timeout for sliding finger input started from the modifier key.
   1178             return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
   1179         }
   1180         return longpressTimeout;
   1181     }
   1182 
   1183     private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
   1184         if (key == null) {
   1185             callListenerOnCancelInput();
   1186             return;
   1187         }
   1188 
   1189         final int code = key.getCode();
   1190         callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */);
   1191         callListenerOnRelease(key, code, false /* withSliding */);
   1192     }
   1193 
   1194     private void startRepeatKey(final Key key) {
   1195         if (sInGesture) return;
   1196         if (key == null) return;
   1197         if (!key.isRepeatable()) return;
   1198         // Don't start key repeat when we are in the dragging finger mode.
   1199         if (mIsInDraggingFinger) return;
   1200         final int startRepeatCount = 1;
   1201         startKeyRepeatTimer(startRepeatCount);
   1202     }
   1203 
   1204     public void onKeyRepeat(final int code, final int repeatCount) {
   1205         final Key key = getKey();
   1206         if (key == null || key.getCode() != code) {
   1207             mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
   1208             return;
   1209         }
   1210         mCurrentRepeatingKeyCode = code;
   1211         mIsDetectingGesture = false;
   1212         final int nextRepeatCount = repeatCount + 1;
   1213         startKeyRepeatTimer(nextRepeatCount);
   1214         callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
   1215         callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(),
   1216                 true /* isKeyRepeat */);
   1217     }
   1218 
   1219     private void startKeyRepeatTimer(final int repeatCount) {
   1220         final int delay =
   1221                 (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval;
   1222         sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay);
   1223     }
   1224 
   1225     private void printTouchEvent(final String title, final int x, final int y,
   1226             final long eventTime) {
   1227         final Key key = mKeyDetector.detectHitKey(x, y);
   1228         final String code = (key == null ? "none" : Constants.printableCode(key.getCode()));
   1229         Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
   1230                 (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code));
   1231     }
   1232 }
   1233