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