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