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