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