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