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