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 
17 package com.android.inputmethod.keyboard;
18 
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.os.SystemClock;
22 import android.util.Log;
23 import android.view.MotionEvent;
24 
25 import com.android.inputmethod.keyboard.internal.BatchInputArbiter;
26 import com.android.inputmethod.keyboard.internal.BatchInputArbiter.BatchInputArbiterListener;
27 import com.android.inputmethod.keyboard.internal.BogusMoveEventDetector;
28 import com.android.inputmethod.keyboard.internal.DrawingProxy;
29 import com.android.inputmethod.keyboard.internal.GestureEnabler;
30 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingParams;
31 import com.android.inputmethod.keyboard.internal.GestureStrokeDrawingPoints;
32 import com.android.inputmethod.keyboard.internal.GestureStrokeRecognitionParams;
33 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
34 import com.android.inputmethod.keyboard.internal.TimerProxy;
35 import com.android.inputmethod.keyboard.internal.TypingTimeRecorder;
36 import com.android.inputmethod.latin.R;
37 import com.android.inputmethod.latin.common.Constants;
38 import com.android.inputmethod.latin.common.CoordinateUtils;
39 import com.android.inputmethod.latin.common.InputPointers;
40 import com.android.inputmethod.latin.define.DebugFlags;
41 import com.android.inputmethod.latin.settings.Settings;
42 import com.android.inputmethod.latin.utils.ResourceUtils;
43 
44 import java.util.ArrayList;
45 
46 import javax.annotation.Nonnull;
47 import javax.annotation.Nullable;
48 
49 public 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 
PointerTrackerParams(final TypedArray mainKeyboardViewAttr)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.
init(final TypedArray mainKeyboardViewAttr, final TimerProxy timerProxy, final DrawingProxy drawingProxy)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.
setMainDictionaryAvailability(final boolean mainDictionaryAvailable)176     public static void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
177         sGestureEnabler.setMainDictionaryAvailability(mainDictionaryAvailable);
178     }
179 
setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser)180     public static void setGestureHandlingEnabledByUser(final boolean gestureHandlingEnabledByUser) {
181         sGestureEnabler.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
182     }
183 
getPointerTracker(final int id)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 
isAnyInDraggingFinger()196     public static boolean isAnyInDraggingFinger() {
197         return sPointerTrackerQueue.isAnyInDraggingFinger();
198     }
199 
cancelAllPointerTrackers()200     public static void cancelAllPointerTrackers() {
201         sPointerTrackerQueue.cancelAllPointerTrackers();
202     }
203 
setKeyboardActionListener(final KeyboardActionListener listener)204     public static void setKeyboardActionListener(final KeyboardActionListener listener) {
205         sListener = listener;
206     }
207 
setKeyDetector(final KeyDetector keyDetector)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 
setReleasedKeyGraphicsToAllKeys()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(), true /* withAnimation */);
226         }
227     }
228 
dismissAllMoreKeysPanels()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 
PointerTracker(final int id)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.
callListenerOnPressAndCheckKeyboardLayoutChange(final Key key, final int repeatCount)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}.
callListenerOnCodeInput(final Key key, final int primaryCode, final int x, final int y, final long eventTime, final boolean isKeyRepeat)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}.
callListenerOnRelease(final Key key, final int primaryCode, final boolean withSliding)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 
callListenerOnFinishSlidingInput()330     private void callListenerOnFinishSlidingInput() {
331         if (DEBUG_LISTENER) {
332             Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId));
333         }
334         sListener.onFinishSlidingInput();
335     }
336 
callListenerOnCancelInput()337     private void callListenerOnCancelInput() {
338         if (DEBUG_LISTENER) {
339             Log.d(TAG, String.format("[%d] onCancelInput", mPointerId));
340         }
341         sListener.onCancelInput();
342     }
343 
setKeyDetectorInner(final KeyDetector keyDetector)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
isInDraggingFinger()367     public boolean isInDraggingFinger() {
368         return mIsInDraggingFinger;
369     }
370 
371     @Nullable
getKey()372     public Key getKey() {
373         return mCurrentKey;
374     }
375 
376     @Override
isModifier()377     public boolean isModifier() {
378         return mCurrentKey != null && mCurrentKey.isModifier();
379     }
380 
getKeyOn(final int x, final int y)381     public Key getKeyOn(final int x, final int y) {
382         return mKeyDetector.detectHitKey(x, y);
383     }
384 
setReleasedKeyGraphics(@ullable final Key key, final boolean withAnimation)385     private void setReleasedKeyGraphics(@Nullable final Key key, final boolean withAnimation) {
386         if (key == null) {
387             return;
388         }
389 
390         sDrawingProxy.onKeyReleased(key, withAnimation);
391 
392         if (key.isShift()) {
393             for (final Key shiftKey : mKeyboard.mShiftKeys) {
394                 if (shiftKey != key) {
395                     sDrawingProxy.onKeyReleased(shiftKey, false /* withAnimation */);
396                 }
397             }
398         }
399 
400         if (key.altCodeWhileTyping()) {
401             final int altCode = key.getAltCode();
402             final Key altKey = mKeyboard.getKey(altCode);
403             if (altKey != null) {
404                 sDrawingProxy.onKeyReleased(altKey, false /* withAnimation */);
405             }
406             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
407                 if (k != key && k.getAltCode() == altCode) {
408                     sDrawingProxy.onKeyReleased(k, false /* withAnimation */);
409                 }
410             }
411         }
412     }
413 
needsToSuppressKeyPreviewPopup(final long eventTime)414     private static boolean needsToSuppressKeyPreviewPopup(final long eventTime) {
415         if (!sGestureEnabler.shouldHandleGesture()) return false;
416         return sTypingTimeRecorder.needsToSuppressKeyPreviewPopup(eventTime);
417     }
418 
setPressedKeyGraphics(@ullable final Key key, final long eventTime)419     private void setPressedKeyGraphics(@Nullable final Key key, final long eventTime) {
420         if (key == null) {
421             return;
422         }
423 
424         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
425         final boolean altersCode = key.altCodeWhileTyping() && sTimerProxy.isTypingState();
426         final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
427         if (!needsToUpdateGraphics) {
428             return;
429         }
430 
431         final boolean noKeyPreview = sInGesture || needsToSuppressKeyPreviewPopup(eventTime);
432         sDrawingProxy.onKeyPressed(key, !noKeyPreview);
433 
434         if (key.isShift()) {
435             for (final Key shiftKey : mKeyboard.mShiftKeys) {
436                 if (shiftKey != key) {
437                     sDrawingProxy.onKeyPressed(shiftKey, false /* withPreview */);
438                 }
439             }
440         }
441 
442         if (altersCode) {
443             final int altCode = key.getAltCode();
444             final Key altKey = mKeyboard.getKey(altCode);
445             if (altKey != null) {
446                 sDrawingProxy.onKeyPressed(altKey, false /* withPreview */);
447             }
448             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
449                 if (k != key && k.getAltCode() == altCode) {
450                     sDrawingProxy.onKeyPressed(k, false /* withPreview */);
451                 }
452             }
453         }
454     }
455 
getGestureStrokeDrawingPoints()456     public GestureStrokeDrawingPoints getGestureStrokeDrawingPoints() {
457         return mGestureStrokeDrawingPoints;
458     }
459 
getLastCoordinates(@onnull final int[] outCoords)460     public void getLastCoordinates(@Nonnull final int[] outCoords) {
461         CoordinateUtils.set(outCoords, mLastX, mLastY);
462     }
463 
getDownTime()464     public long getDownTime() {
465         return mDownTime;
466     }
467 
getDownCoordinates(@onnull final int[] outCoords)468     public void getDownCoordinates(@Nonnull final int[] outCoords) {
469         CoordinateUtils.copy(outCoords, mDownCoordinates);
470     }
471 
onDownKey(final int x, final int y, final long eventTime)472     private Key onDownKey(final int x, final int y, final long eventTime) {
473         mDownTime = eventTime;
474         CoordinateUtils.set(mDownCoordinates, x, y);
475         mBogusMoveEventDetector.onDownKey();
476         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
477     }
478 
getDistance(final int x1, final int y1, final int x2, final int y2)479     private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
480         return (int)Math.hypot(x1 - x2, y1 - y2);
481     }
482 
onMoveKeyInternal(final int x, final int y)483     private Key onMoveKeyInternal(final int x, final int y) {
484         mBogusMoveEventDetector.onMoveKey(getDistance(x, y, mLastX, mLastY));
485         mLastX = x;
486         mLastY = y;
487         return mKeyDetector.detectHitKey(x, y);
488     }
489 
onMoveKey(final int x, final int y)490     private Key onMoveKey(final int x, final int y) {
491         return onMoveKeyInternal(x, y);
492     }
493 
onMoveToNewKey(final Key newKey, final int x, final int y)494     private Key onMoveToNewKey(final Key newKey, final int x, final int y) {
495         mCurrentKey = newKey;
496         mKeyX = x;
497         mKeyY = y;
498         return newKey;
499     }
500 
getActivePointerTrackerCount()501     /* package */ static int getActivePointerTrackerCount() {
502         return sPointerTrackerQueue.size();
503     }
504 
isOldestTrackerInQueue()505     private boolean isOldestTrackerInQueue() {
506         return sPointerTrackerQueue.getOldestElement() == this;
507     }
508 
509     // Implements {@link BatchInputArbiterListener}.
510     @Override
onStartBatchInput()511     public void onStartBatchInput() {
512         if (DEBUG_LISTENER) {
513             Log.d(TAG, String.format("[%d] onStartBatchInput", mPointerId));
514         }
515         sListener.onStartBatchInput();
516         dismissAllMoreKeysPanels();
517         sTimerProxy.cancelLongPressTimersOf(this);
518     }
519 
showGestureTrail()520     private void showGestureTrail() {
521         if (mIsTrackingForActionDisabled) {
522             return;
523         }
524         // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
525         sDrawingProxy.showGestureTrail(
526                 this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
527     }
528 
updateBatchInputByTimer(final long syntheticMoveEventTime)529     public void updateBatchInputByTimer(final long syntheticMoveEventTime) {
530         mBatchInputArbiter.updateBatchInputByTimer(syntheticMoveEventTime, this);
531     }
532 
533     // Implements {@link BatchInputArbiterListener}.
534     @Override
onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime)535     public void onUpdateBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
536         if (DEBUG_LISTENER) {
537             Log.d(TAG, String.format("[%d] onUpdateBatchInput: batchPoints=%d", mPointerId,
538                     aggregatedPointers.getPointerSize()));
539         }
540         sListener.onUpdateBatchInput(aggregatedPointers);
541     }
542 
543     // Implements {@link BatchInputArbiterListener}.
544     @Override
onStartUpdateBatchInputTimer()545     public void onStartUpdateBatchInputTimer() {
546         sTimerProxy.startUpdateBatchInputTimer(this);
547     }
548 
549     // Implements {@link BatchInputArbiterListener}.
550     @Override
onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime)551     public void onEndBatchInput(final InputPointers aggregatedPointers, final long eventTime) {
552         sTypingTimeRecorder.onEndBatchInput(eventTime);
553         sTimerProxy.cancelAllUpdateBatchInputTimers();
554         if (mIsTrackingForActionDisabled) {
555             return;
556         }
557         if (DEBUG_LISTENER) {
558             Log.d(TAG, String.format("[%d] onEndBatchInput   : batchPoints=%d",
559                     mPointerId, aggregatedPointers.getPointerSize()));
560         }
561         sListener.onEndBatchInput(aggregatedPointers);
562     }
563 
cancelBatchInput()564     private void cancelBatchInput() {
565         cancelAllPointerTrackers();
566         mIsDetectingGesture = false;
567         if (!sInGesture) {
568             return;
569         }
570         sInGesture = false;
571         if (DEBUG_LISTENER) {
572             Log.d(TAG, String.format("[%d] onCancelBatchInput", mPointerId));
573         }
574         sListener.onCancelBatchInput();
575     }
576 
processMotionEvent(final MotionEvent me, final KeyDetector keyDetector)577     public void processMotionEvent(final MotionEvent me, final KeyDetector keyDetector) {
578         final int action = me.getActionMasked();
579         final long eventTime = me.getEventTime();
580         if (action == MotionEvent.ACTION_MOVE) {
581             // When this pointer is the only active pointer and is showing a more keys panel,
582             // we should ignore other pointers' motion event.
583             final boolean shouldIgnoreOtherPointers =
584                     isShowingMoreKeysPanel() && getActivePointerTrackerCount() == 1;
585             final int pointerCount = me.getPointerCount();
586             for (int index = 0; index < pointerCount; index++) {
587                 final int id = me.getPointerId(index);
588                 if (shouldIgnoreOtherPointers && id != mPointerId) {
589                     continue;
590                 }
591                 final int x = (int)me.getX(index);
592                 final int y = (int)me.getY(index);
593                 final PointerTracker tracker = getPointerTracker(id);
594                 tracker.onMoveEvent(x, y, eventTime, me);
595             }
596             return;
597         }
598         final int index = me.getActionIndex();
599         final int x = (int)me.getX(index);
600         final int y = (int)me.getY(index);
601         switch (action) {
602         case MotionEvent.ACTION_DOWN:
603         case MotionEvent.ACTION_POINTER_DOWN:
604             onDownEvent(x, y, eventTime, keyDetector);
605             break;
606         case MotionEvent.ACTION_UP:
607         case MotionEvent.ACTION_POINTER_UP:
608             onUpEvent(x, y, eventTime);
609             break;
610         case MotionEvent.ACTION_CANCEL:
611             onCancelEvent(x, y, eventTime);
612             break;
613         }
614     }
615 
onDownEvent(final int x, final int y, final long eventTime, final KeyDetector keyDetector)616     private void onDownEvent(final int x, final int y, final long eventTime,
617             final KeyDetector keyDetector) {
618         setKeyDetectorInner(keyDetector);
619         if (DEBUG_EVENT) {
620             printTouchEvent("onDownEvent:", x, y, eventTime);
621         }
622         // Naive up-to-down noise filter.
623         final long deltaT = eventTime - mUpTime;
624         if (deltaT < sParams.mTouchNoiseThresholdTime) {
625             final int distance = getDistance(x, y, mLastX, mLastY);
626             if (distance < sParams.mTouchNoiseThresholdDistance) {
627                 if (DEBUG_MODE)
628                     Log.w(TAG, String.format("[%d] onDownEvent:"
629                             + " ignore potential noise: time=%d distance=%d",
630                             mPointerId, deltaT, distance));
631                 cancelTrackingForAction();
632                 return;
633             }
634         }
635 
636         final Key key = getKeyOn(x, y);
637         mBogusMoveEventDetector.onActualDownEvent(x, y);
638         if (key != null && key.isModifier()) {
639             // Before processing a down event of modifier key, all pointers already being
640             // tracked should be released.
641             sPointerTrackerQueue.releaseAllPointers(eventTime);
642         }
643         sPointerTrackerQueue.add(this);
644         onDownEventInternal(x, y, eventTime);
645         if (!sGestureEnabler.shouldHandleGesture()) {
646             return;
647         }
648         // A gesture should start only from a non-modifier key. Note that the gesture detection is
649         // disabled when the key is repeating.
650         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
651                 && key != null && !key.isModifier();
652         if (mIsDetectingGesture) {
653             mBatchInputArbiter.addDownEventPoint(x, y, eventTime,
654                     sTypingTimeRecorder.getLastLetterTypingTime(), getActivePointerTrackerCount());
655             mGestureStrokeDrawingPoints.onDownEvent(
656                     x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
657         }
658     }
659 
isShowingMoreKeysPanel()660     /* package */ boolean isShowingMoreKeysPanel() {
661         return (mMoreKeysPanel != null);
662     }
663 
dismissMoreKeysPanel()664     private void dismissMoreKeysPanel() {
665         if (isShowingMoreKeysPanel()) {
666             mMoreKeysPanel.dismissMoreKeysPanel();
667             mMoreKeysPanel = null;
668         }
669     }
670 
onDownEventInternal(final int x, final int y, final long eventTime)671     private void onDownEventInternal(final int x, final int y, final long eventTime) {
672         Key key = onDownKey(x, y, eventTime);
673         // Key selection by dragging finger is allowed when 1) key selection by dragging finger is
674         // enabled by configuration, 2) this pointer starts dragging from modifier key, or 3) this
675         // pointer's KeyDetector always allows key selection by dragging finger, such as
676         // {@link MoreKeysKeyboard}.
677         mIsAllowedDraggingFinger = sParams.mKeySelectionByDraggingFinger
678                 || (key != null && key.isModifier())
679                 || mKeyDetector.alwaysAllowsKeySelectionByDraggingFinger();
680         mKeyboardLayoutHasBeenChanged = false;
681         mIsTrackingForActionDisabled = false;
682         resetKeySelectionByDraggingFinger();
683         if (key != null) {
684             // This onPress call may have changed keyboard layout. Those cases are detected at
685             // {@link #setKeyboard}. In those cases, we should update key according to the new
686             // keyboard layout.
687             if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
688                 key = onDownKey(x, y, eventTime);
689             }
690 
691             startRepeatKey(key);
692             startLongPressTimer(key);
693             setPressedKeyGraphics(key, eventTime);
694         }
695     }
696 
startKeySelectionByDraggingFinger(final Key key)697     private void startKeySelectionByDraggingFinger(final Key key) {
698         if (!mIsInDraggingFinger) {
699             mIsInSlidingKeyInput = key.isModifier();
700         }
701         mIsInDraggingFinger = true;
702     }
703 
resetKeySelectionByDraggingFinger()704     private void resetKeySelectionByDraggingFinger() {
705         mIsInDraggingFinger = false;
706         mIsInSlidingKeyInput = false;
707         sDrawingProxy.showSlidingKeyInputPreview(null /* tracker */);
708     }
709 
onGestureMoveEvent(final int x, final int y, final long eventTime, final boolean isMajorEvent, final Key key)710     private void onGestureMoveEvent(final int x, final int y, final long eventTime,
711             final boolean isMajorEvent, final Key key) {
712         if (!mIsDetectingGesture) {
713             return;
714         }
715         final boolean onValidArea = mBatchInputArbiter.addMoveEventPoint(
716                 x, y, eventTime, isMajorEvent, this);
717         // If the move event goes out from valid batch input area, cancel batch input.
718         if (!onValidArea) {
719             cancelBatchInput();
720             return;
721         }
722         mGestureStrokeDrawingPoints.onMoveEvent(
723                 x, y, mBatchInputArbiter.getElapsedTimeSinceFirstDown(eventTime));
724         // If the MoreKeysPanel is showing then do not attempt to enter gesture mode. However,
725         // the gestured touch points are still being recorded in case the panel is dismissed.
726         if (isShowingMoreKeysPanel()) {
727             return;
728         }
729         if (!sInGesture && key != null && Character.isLetter(key.getCode())
730                 && mBatchInputArbiter.mayStartBatchInput(this)) {
731             sInGesture = true;
732         }
733         if (sInGesture) {
734             if (key != null) {
735                 mBatchInputArbiter.updateBatchInput(eventTime, this);
736             }
737             showGestureTrail();
738         }
739     }
740 
onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me)741     private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
742         if (DEBUG_MOVE_EVENT) {
743             printTouchEvent("onMoveEvent:", x, y, eventTime);
744         }
745         if (mIsTrackingForActionDisabled) {
746             return;
747         }
748 
749         if (sGestureEnabler.shouldHandleGesture() && me != null) {
750             // Add historical points to gesture path.
751             final int pointerIndex = me.findPointerIndex(mPointerId);
752             final int historicalSize = me.getHistorySize();
753             for (int h = 0; h < historicalSize; h++) {
754                 final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
755                 final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
756                 final long historicalTime = me.getHistoricalEventTime(h);
757                 onGestureMoveEvent(historicalX, historicalY, historicalTime,
758                         false /* isMajorEvent */, null);
759             }
760         }
761 
762         if (isShowingMoreKeysPanel()) {
763             final int translatedX = mMoreKeysPanel.translateX(x);
764             final int translatedY = mMoreKeysPanel.translateY(y);
765             mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
766             onMoveKey(x, y);
767             if (mIsInSlidingKeyInput) {
768                 sDrawingProxy.showSlidingKeyInputPreview(this);
769             }
770             return;
771         }
772         onMoveEventInternal(x, y, eventTime);
773     }
774 
processDraggingFingerInToNewKey(final Key newKey, final int x, final int y, final long eventTime)775     private void processDraggingFingerInToNewKey(final Key newKey, final int x, final int y,
776             final long eventTime) {
777         // This onPress call may have changed keyboard layout. Those cases are detected
778         // at {@link #setKeyboard}. In those cases, we should update key according
779         // to the new keyboard layout.
780         Key key = newKey;
781         if (callListenerOnPressAndCheckKeyboardLayoutChange(key, 0 /* repeatCount */)) {
782             key = onMoveKey(x, y);
783         }
784         onMoveToNewKey(key, x, y);
785         if (mIsTrackingForActionDisabled) {
786             return;
787         }
788         startLongPressTimer(key);
789         setPressedKeyGraphics(key, eventTime);
790     }
791 
processPhantomSuddenMoveHack(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)792     private void processPhantomSuddenMoveHack(final Key key, final int x, final int y,
793             final long eventTime, final Key oldKey, final int lastX, final int lastY) {
794         if (DEBUG_MODE) {
795             Log.w(TAG, String.format("[%d] onMoveEvent:"
796                     + " phantom sudden move event (distance=%d) is translated to "
797                     + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId,
798                     getDistance(x, y, lastX, lastY),
799                     lastX, lastY, Constants.printableCode(oldKey.getCode()),
800                     x, y, Constants.printableCode(key.getCode())));
801         }
802         onUpEventInternal(x, y, eventTime);
803         onDownEventInternal(x, y, eventTime);
804     }
805 
processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)806     private void processProximateBogusDownMoveUpEventHack(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             final float keyDiagonal = (float)Math.hypot(
810                     mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
811             final float radiusRatio =
812                     mBogusMoveEventDetector.getDistanceFromDownEvent(x, y)
813                     / keyDiagonal;
814             Log.w(TAG, String.format("[%d] onMoveEvent:"
815                     + " bogus down-move-up event (raidus=%.2f key diagonal) is "
816                     + " translated to up[%d,%d,%s]/down[%d,%d,%s] events",
817                     mPointerId, radiusRatio,
818                     lastX, lastY, Constants.printableCode(oldKey.getCode()),
819                     x, y, Constants.printableCode(key.getCode())));
820         }
821         onUpEventInternal(x, y, eventTime);
822         onDownEventInternal(x, y, eventTime);
823     }
824 
processDraggingFingerOutFromOldKey(final Key oldKey)825     private void processDraggingFingerOutFromOldKey(final Key oldKey) {
826         setReleasedKeyGraphics(oldKey, true /* withAnimation */);
827         callListenerOnRelease(oldKey, oldKey.getCode(), true /* withSliding */);
828         startKeySelectionByDraggingFinger(oldKey);
829         sTimerProxy.cancelKeyTimersOf(this);
830     }
831 
dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y, final long eventTime, final Key oldKey, final int lastX, final int lastY)832     private void dragFingerFromOldKeyToNewKey(final Key key, final int x, final int y,
833             final long eventTime, final Key oldKey, final int lastX, final int lastY) {
834         // The pointer has been slid in to the new key from the previous key, we must call
835         // onRelease() first to notify that the previous key has been released, then call
836         // onPress() to notify that the new key is being pressed.
837         processDraggingFingerOutFromOldKey(oldKey);
838         startRepeatKey(key);
839         if (mIsAllowedDraggingFinger) {
840             processDraggingFingerInToNewKey(key, x, y, eventTime);
841         }
842         // HACK: On some devices, quick successive touches may be reported as a sudden move by
843         // touch panel firmware. This hack detects such cases and translates the move event to
844         // successive up and down events.
845         // TODO: Should find a way to balance gesture detection and this hack.
846         else if (sNeedsPhantomSuddenMoveEventHack
847                 && getDistance(x, y, lastX, lastY) >= mPhantomSuddenMoveThreshold) {
848             processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY);
849         }
850         // HACK: On some devices, quick successive proximate touches may be reported as a bogus
851         // down-move-up event by touch panel firmware. This hack detects such cases and breaks
852         // these events into separate up and down events.
853         else if (sTypingTimeRecorder.isInFastTyping(eventTime)
854                 && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) {
855             processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY);
856         }
857         // HACK: If there are currently multiple touches, register the key even if the finger
858         // slides off the key. This defends against noise from some touch panels when there are
859         // close multiple touches.
860         // Caveat: When in chording input mode with a modifier key, we don't use this hack.
861         else if (getActivePointerTrackerCount() > 1
862                 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
863             if (DEBUG_MODE) {
864                 Log.w(TAG, String.format("[%d] onMoveEvent:"
865                         + " detected sliding finger while multi touching", mPointerId));
866             }
867             onUpEvent(x, y, eventTime);
868             cancelTrackingForAction();
869             setReleasedKeyGraphics(oldKey, true /* withAnimation */);
870         } else {
871             if (!mIsDetectingGesture) {
872                 cancelTrackingForAction();
873             }
874             setReleasedKeyGraphics(oldKey, true /* withAnimation */);
875         }
876     }
877 
dragFingerOutFromOldKey(final Key oldKey, final int x, final int y)878     private void dragFingerOutFromOldKey(final Key oldKey, final int x, final int y) {
879         // The pointer has been slid out from the previous key, we must call onRelease() to
880         // notify that the previous key has been released.
881         processDraggingFingerOutFromOldKey(oldKey);
882         if (mIsAllowedDraggingFinger) {
883             onMoveToNewKey(null, x, y);
884         } else {
885             if (!mIsDetectingGesture) {
886                 cancelTrackingForAction();
887             }
888         }
889     }
890 
onMoveEventInternal(final int x, final int y, final long eventTime)891     private void onMoveEventInternal(final int x, final int y, final long eventTime) {
892         final int lastX = mLastX;
893         final int lastY = mLastY;
894         final Key oldKey = mCurrentKey;
895         final Key newKey = onMoveKey(x, y);
896 
897         if (sGestureEnabler.shouldHandleGesture()) {
898             // Register move event on gesture tracker.
899             onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey);
900             if (sInGesture) {
901                 mCurrentKey = null;
902                 setReleasedKeyGraphics(oldKey, true /* withAnimation */);
903                 return;
904             }
905         }
906 
907         if (newKey != null) {
908             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
909                 dragFingerFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY);
910             } else if (oldKey == null) {
911                 // The pointer has been slid in to the new key, but the finger was not on any keys.
912                 // In this case, we must call onPress() to notify that the new key is being pressed.
913                 processDraggingFingerInToNewKey(newKey, x, y, eventTime);
914             }
915         } else { // newKey == null
916             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) {
917                 dragFingerOutFromOldKey(oldKey, x, y);
918             }
919         }
920         if (mIsInSlidingKeyInput) {
921             sDrawingProxy.showSlidingKeyInputPreview(this);
922         }
923     }
924 
onUpEvent(final int x, final int y, final long eventTime)925     private void onUpEvent(final int x, final int y, final long eventTime) {
926         if (DEBUG_EVENT) {
927             printTouchEvent("onUpEvent  :", x, y, eventTime);
928         }
929 
930         sTimerProxy.cancelUpdateBatchInputTimer(this);
931         if (!sInGesture) {
932             if (mCurrentKey != null && mCurrentKey.isModifier()) {
933                 // Before processing an up event of modifier key, all pointers already being
934                 // tracked should be released.
935                 sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime);
936             } else {
937                 sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime);
938             }
939         }
940         onUpEventInternal(x, y, eventTime);
941         sPointerTrackerQueue.remove(this);
942     }
943 
944     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
945     // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
946     // "virtual" up event.
947     @Override
onPhantomUpEvent(final long eventTime)948     public void onPhantomUpEvent(final long eventTime) {
949         if (DEBUG_EVENT) {
950             printTouchEvent("onPhntEvent:", mLastX, mLastY, eventTime);
951         }
952         onUpEventInternal(mLastX, mLastY, eventTime);
953         cancelTrackingForAction();
954     }
955 
onUpEventInternal(final int x, final int y, final long eventTime)956     private void onUpEventInternal(final int x, final int y, final long eventTime) {
957         sTimerProxy.cancelKeyTimersOf(this);
958         final boolean isInDraggingFinger = mIsInDraggingFinger;
959         final boolean isInSlidingKeyInput = mIsInSlidingKeyInput;
960         resetKeySelectionByDraggingFinger();
961         mIsDetectingGesture = false;
962         final Key currentKey = mCurrentKey;
963         mCurrentKey = null;
964         final int currentRepeatingKeyCode = mCurrentRepeatingKeyCode;
965         mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
966         // Release the last pressed key.
967         setReleasedKeyGraphics(currentKey, true /* withAnimation */);
968 
969         if (isShowingMoreKeysPanel()) {
970             if (!mIsTrackingForActionDisabled) {
971                 final int translatedX = mMoreKeysPanel.translateX(x);
972                 final int translatedY = mMoreKeysPanel.translateY(y);
973                 mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime);
974             }
975             dismissMoreKeysPanel();
976             return;
977         }
978 
979         if (sInGesture) {
980             if (currentKey != null) {
981                 callListenerOnRelease(currentKey, currentKey.getCode(), true /* withSliding */);
982             }
983             if (mBatchInputArbiter.mayEndBatchInput(
984                     eventTime, getActivePointerTrackerCount(), this)) {
985                 sInGesture = false;
986             }
987             showGestureTrail();
988             return;
989         }
990 
991         if (mIsTrackingForActionDisabled) {
992             return;
993         }
994         if (currentKey != null && currentKey.isRepeatable()
995                 && (currentKey.getCode() == currentRepeatingKeyCode) && !isInDraggingFinger) {
996             return;
997         }
998         detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime);
999         if (isInSlidingKeyInput) {
1000             callListenerOnFinishSlidingInput();
1001         }
1002     }
1003 
1004     @Override
cancelTrackingForAction()1005     public void cancelTrackingForAction() {
1006         if (isShowingMoreKeysPanel()) {
1007             return;
1008         }
1009         mIsTrackingForActionDisabled = true;
1010     }
1011 
isInOperation()1012     public boolean isInOperation() {
1013         return !mIsTrackingForActionDisabled;
1014     }
1015 
onLongPressed()1016     public void onLongPressed() {
1017         sTimerProxy.cancelLongPressTimersOf(this);
1018         if (isShowingMoreKeysPanel()) {
1019             return;
1020         }
1021         final Key key = getKey();
1022         if (key == null) {
1023             return;
1024         }
1025         if (key.hasNoPanelAutoMoreKey()) {
1026             cancelKeyTracking();
1027             final int moreKeyCode = key.getMoreKeys()[0].mCode;
1028             sListener.onPressKey(moreKeyCode, 0 /* repeatCont */, true /* isSinglePointer */);
1029             sListener.onCodeInput(moreKeyCode, Constants.NOT_A_COORDINATE,
1030                     Constants.NOT_A_COORDINATE, false /* isKeyRepeat */);
1031             sListener.onReleaseKey(moreKeyCode, false /* withSliding */);
1032             return;
1033         }
1034         final int code = key.getCode();
1035         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
1036             // Long pressing the space key invokes IME switcher dialog.
1037             if (sListener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
1038                 cancelKeyTracking();
1039                 sListener.onReleaseKey(code, false /* withSliding */);
1040                 return;
1041             }
1042         }
1043 
1044         setReleasedKeyGraphics(key, false /* withAnimation */);
1045         final MoreKeysPanel moreKeysPanel = sDrawingProxy.showMoreKeysKeyboard(key, this);
1046         if (moreKeysPanel == null) {
1047             return;
1048         }
1049         final int translatedX = moreKeysPanel.translateX(mLastX);
1050         final int translatedY = moreKeysPanel.translateY(mLastY);
1051         moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis());
1052         mMoreKeysPanel = moreKeysPanel;
1053     }
1054 
cancelKeyTracking()1055     private void cancelKeyTracking() {
1056         resetKeySelectionByDraggingFinger();
1057         cancelTrackingForAction();
1058         setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
1059         sPointerTrackerQueue.remove(this);
1060     }
1061 
onCancelEvent(final int x, final int y, final long eventTime)1062     private void onCancelEvent(final int x, final int y, final long eventTime) {
1063         if (DEBUG_EVENT) {
1064             printTouchEvent("onCancelEvt:", x, y, eventTime);
1065         }
1066 
1067         cancelBatchInput();
1068         cancelAllPointerTrackers();
1069         sPointerTrackerQueue.releaseAllPointers(eventTime);
1070         onCancelEventInternal();
1071     }
1072 
onCancelEventInternal()1073     private void onCancelEventInternal() {
1074         sTimerProxy.cancelKeyTimersOf(this);
1075         setReleasedKeyGraphics(mCurrentKey, true /* withAnimation */);
1076         resetKeySelectionByDraggingFinger();
1077         dismissMoreKeysPanel();
1078     }
1079 
isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, final Key newKey)1080     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
1081             final Key newKey) {
1082         final Key curKey = mCurrentKey;
1083         if (newKey == curKey) {
1084             return false;
1085         }
1086         if (curKey == null /* && newKey != null */) {
1087             return true;
1088         }
1089         // Here curKey points to the different key from newKey.
1090         final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared(
1091                 mIsInSlidingKeyInput);
1092         final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y);
1093         if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) {
1094             if (DEBUG_MODE) {
1095                 final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared)
1096                         / mKeyboard.mMostCommonKeyWidth;
1097                 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
1098                         +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio));
1099             }
1100             return true;
1101         }
1102         if (!mIsAllowedDraggingFinger && sTypingTimeRecorder.isInFastTyping(eventTime)
1103                 && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) {
1104             if (DEBUG_MODE) {
1105                 final float keyDiagonal = (float)Math.hypot(
1106                         mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
1107                 final float lengthFromDownRatio =
1108                         mBogusMoveEventDetector.getAccumulatedDistanceFromDownKey() / keyDiagonal;
1109                 Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:"
1110                         + " %.2f key diagonal from virtual down point",
1111                         mPointerId, lengthFromDownRatio));
1112             }
1113             return true;
1114         }
1115         return false;
1116     }
1117 
startLongPressTimer(final Key key)1118     private void startLongPressTimer(final Key key) {
1119         // Note that we need to cancel all active long press shift key timers if any whenever we
1120         // start a new long press timer for both non-shift and shift keys.
1121         sTimerProxy.cancelLongPressShiftKeyTimer();
1122         if (sInGesture) return;
1123         if (key == null) return;
1124         if (!key.isLongPressEnabled()) return;
1125         // Caveat: Please note that isLongPressEnabled() can be true even if the current key
1126         // doesn't have its more keys. (e.g. spacebar, globe key) If we are in the dragging finger
1127         // mode, we will disable long press timer of such key.
1128         // We always need to start the long press timer if the key has its more keys regardless of
1129         // whether or not we are in the dragging finger mode.
1130         if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
1131 
1132         final int delay = getLongPressTimeout(key.getCode());
1133         if (delay <= 0) return;
1134         sTimerProxy.startLongPressTimerOf(this, delay);
1135     }
1136 
getLongPressTimeout(final int code)1137     private int getLongPressTimeout(final int code) {
1138         if (code == Constants.CODE_SHIFT) {
1139             return sParams.mLongPressShiftLockTimeout;
1140         }
1141         final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
1142         if (mIsInSlidingKeyInput) {
1143             // We use longer timeout for sliding finger input started from the modifier key.
1144             return longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
1145         }
1146         return longpressTimeout;
1147     }
1148 
detectAndSendKey(final Key key, final int x, final int y, final long eventTime)1149     private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
1150         if (key == null) {
1151             callListenerOnCancelInput();
1152             return;
1153         }
1154 
1155         final int code = key.getCode();
1156         callListenerOnCodeInput(key, code, x, y, eventTime, false /* isKeyRepeat */);
1157         callListenerOnRelease(key, code, false /* withSliding */);
1158     }
1159 
startRepeatKey(final Key key)1160     private void startRepeatKey(final Key key) {
1161         if (sInGesture) return;
1162         if (key == null) return;
1163         if (!key.isRepeatable()) return;
1164         // Don't start key repeat when we are in the dragging finger mode.
1165         if (mIsInDraggingFinger) return;
1166         final int startRepeatCount = 1;
1167         startKeyRepeatTimer(startRepeatCount);
1168     }
1169 
onKeyRepeat(final int code, final int repeatCount)1170     public void onKeyRepeat(final int code, final int repeatCount) {
1171         final Key key = getKey();
1172         if (key == null || key.getCode() != code) {
1173             mCurrentRepeatingKeyCode = Constants.NOT_A_CODE;
1174             return;
1175         }
1176         mCurrentRepeatingKeyCode = code;
1177         mIsDetectingGesture = false;
1178         final int nextRepeatCount = repeatCount + 1;
1179         startKeyRepeatTimer(nextRepeatCount);
1180         callListenerOnPressAndCheckKeyboardLayoutChange(key, repeatCount);
1181         callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis(),
1182                 true /* isKeyRepeat */);
1183     }
1184 
startKeyRepeatTimer(final int repeatCount)1185     private void startKeyRepeatTimer(final int repeatCount) {
1186         final int delay =
1187                 (repeatCount == 1) ? sParams.mKeyRepeatStartTimeout : sParams.mKeyRepeatInterval;
1188         sTimerProxy.startKeyRepeatTimerOf(this, repeatCount, delay);
1189     }
1190 
printTouchEvent(final String title, final int x, final int y, final long eventTime)1191     private void printTouchEvent(final String title, final int x, final int y,
1192             final long eventTime) {
1193         final Key key = mKeyDetector.detectHitKey(x, y);
1194         final String code = (key == null ? "none" : Constants.printableCode(key.getCode()));
1195         Log.d(TAG, String.format("[%d]%s%s %4d %4d %5d %s", mPointerId,
1196                 (mIsTrackingForActionDisabled ? "-" : " "), title, x, y, eventTime, code));
1197     }
1198 }
1199