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