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