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