• 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.os.SystemClock;
20 import android.util.Log;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.widget.TextView;
24 
25 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
26 import com.android.inputmethod.latin.LatinImeLogger;
27 import com.android.inputmethod.latin.ResearchLogger;
28 import com.android.inputmethod.latin.define.ProductionFlag;
29 
30 import java.util.ArrayList;
31 
32 public class PointerTracker {
33     private static final String TAG = PointerTracker.class.getSimpleName();
34     private static final boolean DEBUG_EVENT = false;
35     private static final boolean DEBUG_MOVE_EVENT = false;
36     private static final boolean DEBUG_LISTENER = false;
37     private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
38 
39     public interface KeyEventHandler {
40         /**
41          * Get KeyDetector object that is used for this PointerTracker.
42          * @return the KeyDetector object that is used for this PointerTracker
43          */
getKeyDetector()44         public KeyDetector getKeyDetector();
45 
46         /**
47          * Get KeyboardActionListener object that is used to register key code and so on.
48          * @return the KeyboardActionListner for this PointerTracker
49          */
getKeyboardActionListener()50         public KeyboardActionListener getKeyboardActionListener();
51 
52         /**
53          * Get DrawingProxy object that is used for this PointerTracker.
54          * @return the DrawingProxy object that is used for this PointerTracker
55          */
getDrawingProxy()56         public DrawingProxy getDrawingProxy();
57 
58         /**
59          * Get TimerProxy object that handles key repeat and long press timer event for this
60          * PointerTracker.
61          * @return the TimerProxy object that handles key repeat and long press timer event.
62          */
getTimerProxy()63         public TimerProxy getTimerProxy();
64     }
65 
66     public interface DrawingProxy extends MoreKeysPanel.Controller {
invalidateKey(Key key)67         public void invalidateKey(Key key);
inflateKeyPreviewText()68         public TextView inflateKeyPreviewText();
showKeyPreview(PointerTracker tracker)69         public void showKeyPreview(PointerTracker tracker);
dismissKeyPreview(PointerTracker tracker)70         public void dismissKeyPreview(PointerTracker tracker);
71     }
72 
73     public interface TimerProxy {
startTypingStateTimer()74         public void startTypingStateTimer();
isTypingState()75         public boolean isTypingState();
startKeyRepeatTimer(PointerTracker tracker)76         public void startKeyRepeatTimer(PointerTracker tracker);
startLongPressTimer(PointerTracker tracker)77         public void startLongPressTimer(PointerTracker tracker);
startLongPressTimer(int code)78         public void startLongPressTimer(int code);
cancelLongPressTimer()79         public void cancelLongPressTimer();
startDoubleTapTimer()80         public void startDoubleTapTimer();
cancelDoubleTapTimer()81         public void cancelDoubleTapTimer();
isInDoubleTapTimeout()82         public boolean isInDoubleTapTimeout();
cancelKeyTimers()83         public void cancelKeyTimers();
84 
85         public static class Adapter implements TimerProxy {
86             @Override
startTypingStateTimer()87             public void startTypingStateTimer() {}
88             @Override
isTypingState()89             public boolean isTypingState() { return false; }
90             @Override
startKeyRepeatTimer(PointerTracker tracker)91             public void startKeyRepeatTimer(PointerTracker tracker) {}
92             @Override
startLongPressTimer(PointerTracker tracker)93             public void startLongPressTimer(PointerTracker tracker) {}
94             @Override
startLongPressTimer(int code)95             public void startLongPressTimer(int code) {}
96             @Override
cancelLongPressTimer()97             public void cancelLongPressTimer() {}
98             @Override
startDoubleTapTimer()99             public void startDoubleTapTimer() {}
100             @Override
cancelDoubleTapTimer()101             public void cancelDoubleTapTimer() {}
102             @Override
isInDoubleTapTimeout()103             public boolean isInDoubleTapTimeout() { return false; }
104             @Override
cancelKeyTimers()105             public void cancelKeyTimers() {}
106         }
107     }
108 
109     // Parameters for pointer handling.
110     private static LatinKeyboardView.PointerTrackerParams sParams;
111     private static int sTouchNoiseThresholdDistanceSquared;
112     private static boolean sNeedsPhantomSuddenMoveEventHack;
113 
114     private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
115     private static PointerTrackerQueue sPointerTrackerQueue;
116 
117     public final int mPointerId;
118 
119     private DrawingProxy mDrawingProxy;
120     private TimerProxy mTimerProxy;
121     private KeyDetector mKeyDetector;
122     private KeyboardActionListener mListener = EMPTY_LISTENER;
123 
124     private Keyboard mKeyboard;
125     private int mKeyQuarterWidthSquared;
126     private final TextView mKeyPreviewText;
127 
128     // The position and time at which first down event occurred.
129     private long mDownTime;
130     private long mUpTime;
131 
132     // The current key where this pointer is.
133     private Key mCurrentKey = null;
134     // The position where the current key was recognized for the first time.
135     private int mKeyX;
136     private int mKeyY;
137 
138     // Last pointer position.
139     private int mLastX;
140     private int mLastY;
141 
142     // true if keyboard layout has been changed.
143     private boolean mKeyboardLayoutHasBeenChanged;
144 
145     // true if event is already translated to a key action.
146     private boolean mKeyAlreadyProcessed;
147 
148     // true if this pointer has been long-pressed and is showing a more keys panel.
149     private boolean mIsShowingMoreKeysPanel;
150 
151     // true if this pointer is repeatable key
152     private boolean mIsRepeatableKey;
153 
154     // true if this pointer is in sliding key input
155     boolean mIsInSlidingKeyInput;
156 
157     // true if sliding key is allowed.
158     private boolean mIsAllowedSlidingKeyInput;
159 
160     // ignore modifier key if true
161     private boolean mIgnoreModifierKey;
162 
163     // Empty {@link KeyboardActionListener}
164     private static final KeyboardActionListener EMPTY_LISTENER =
165             new KeyboardActionListener.Adapter();
166 
init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack)167     public static void init(boolean hasDistinctMultitouch,
168             boolean needsPhantomSuddenMoveEventHack) {
169         if (hasDistinctMultitouch) {
170             sPointerTrackerQueue = new PointerTrackerQueue();
171         } else {
172             sPointerTrackerQueue = null;
173         }
174         sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
175 
176         setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT);
177     }
178 
setParameters(LatinKeyboardView.PointerTrackerParams params)179     public static void setParameters(LatinKeyboardView.PointerTrackerParams params) {
180         sParams = params;
181         sTouchNoiseThresholdDistanceSquared = (int)(
182                 params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
183     }
184 
getPointerTracker(final int id, KeyEventHandler handler)185     public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
186         final ArrayList<PointerTracker> trackers = sTrackers;
187 
188         // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
189         for (int i = trackers.size(); i <= id; i++) {
190             final PointerTracker tracker = new PointerTracker(i, handler);
191             trackers.add(tracker);
192         }
193 
194         return trackers.get(id);
195     }
196 
isAnyInSlidingKeyInput()197     public static boolean isAnyInSlidingKeyInput() {
198         return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
199     }
200 
setKeyboardActionListener(KeyboardActionListener listener)201     public static void setKeyboardActionListener(KeyboardActionListener listener) {
202         for (final PointerTracker tracker : sTrackers) {
203             tracker.mListener = listener;
204         }
205     }
206 
setKeyDetector(KeyDetector keyDetector)207     public static void setKeyDetector(KeyDetector keyDetector) {
208         for (final PointerTracker tracker : sTrackers) {
209             tracker.setKeyDetectorInner(keyDetector);
210             // Mark that keyboard layout has been changed.
211             tracker.mKeyboardLayoutHasBeenChanged = true;
212         }
213     }
214 
dismissAllKeyPreviews()215     public static void dismissAllKeyPreviews() {
216         for (final PointerTracker tracker : sTrackers) {
217             tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
218             tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
219         }
220     }
221 
PointerTracker(int id, KeyEventHandler handler)222     public PointerTracker(int id, KeyEventHandler handler) {
223         if (handler == null)
224             throw new NullPointerException();
225         mPointerId = id;
226         setKeyDetectorInner(handler.getKeyDetector());
227         mListener = handler.getKeyboardActionListener();
228         mDrawingProxy = handler.getDrawingProxy();
229         mTimerProxy = handler.getTimerProxy();
230         mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
231     }
232 
getKeyPreviewText()233     public TextView getKeyPreviewText() {
234         return mKeyPreviewText;
235     }
236 
237     // Returns true if keyboard has been changed by this callback.
callListenerOnPressAndCheckKeyboardLayoutChange(Key key)238     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
239         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
240         if (DEBUG_LISTENER) {
241             Log.d(TAG, "onPress    : " + KeyDetector.printableCode(key)
242                     + " ignoreModifier=" + ignoreModifierKey
243                     + " enabled=" + key.isEnabled());
244         }
245         if (ProductionFlag.IS_EXPERIMENTAL) {
246             ResearchLogger.pointerTracker_callListenerOnPressAndCheckKeyboardLayoutChange(key,
247                     ignoreModifierKey);
248         }
249         if (ignoreModifierKey) {
250             return false;
251         }
252         if (key.isEnabled()) {
253             mListener.onPressKey(key.mCode);
254             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
255             mKeyboardLayoutHasBeenChanged = false;
256             if (!key.altCodeWhileTyping() && !key.isModifier()) {
257                 mTimerProxy.startTypingStateTimer();
258             }
259             return keyboardLayoutHasBeenChanged;
260         }
261         return false;
262     }
263 
264     // Note that we need primaryCode argument because the keyboard may in shifted state and the
265     // primaryCode is different from {@link Key#mCode}.
callListenerOnCodeInput(Key key, int primaryCode, int x, int y)266     private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
267         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
268         final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
269         final int code = altersCode ? key.mAltCode : primaryCode;
270         if (DEBUG_LISTENER) {
271             Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
272                     + " x=" + x + " y=" + y
273                     + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
274                     + " enabled=" + key.isEnabled());
275         }
276         if (ProductionFlag.IS_EXPERIMENTAL) {
277             ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
278                     altersCode, code);
279         }
280         if (ignoreModifierKey) {
281             return;
282         }
283         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
284         if (key.isEnabled() || altersCode) {
285             if (code == Keyboard.CODE_OUTPUT_TEXT) {
286                 mListener.onTextInput(key.mOutputText);
287             } else if (code != Keyboard.CODE_UNSPECIFIED) {
288                 mListener.onCodeInput(code, x, y);
289             }
290         }
291     }
292 
293     // Note that we need primaryCode argument because the keyboard may in shifted state and the
294     // primaryCode is different from {@link Key#mCode}.
callListenerOnRelease(Key key, int primaryCode, boolean withSliding)295     private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
296         final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
297         if (DEBUG_LISTENER) {
298             Log.d(TAG, "onRelease  : " + Keyboard.printableCode(primaryCode)
299                     + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
300                     + " enabled="+ key.isEnabled());
301         }
302         if (ProductionFlag.IS_EXPERIMENTAL) {
303             ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
304                     ignoreModifierKey);
305         }
306         if (ignoreModifierKey) {
307             return;
308         }
309         if (key.isEnabled()) {
310             mListener.onReleaseKey(primaryCode, withSliding);
311         }
312     }
313 
callListenerOnCancelInput()314     private void callListenerOnCancelInput() {
315         if (DEBUG_LISTENER)
316             Log.d(TAG, "onCancelInput");
317         if (ProductionFlag.IS_EXPERIMENTAL) {
318             ResearchLogger.pointerTracker_callListenerOnCancelInput();
319         }
320         mListener.onCancelInput();
321     }
322 
setKeyDetectorInner(KeyDetector keyDetector)323     private void setKeyDetectorInner(KeyDetector keyDetector) {
324         mKeyDetector = keyDetector;
325         mKeyboard = keyDetector.getKeyboard();
326         final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
327         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
328     }
329 
isInSlidingKeyInput()330     public boolean isInSlidingKeyInput() {
331         return mIsInSlidingKeyInput;
332     }
333 
getKey()334     public Key getKey() {
335         return mCurrentKey;
336     }
337 
isModifier()338     public boolean isModifier() {
339         return mCurrentKey != null && mCurrentKey.isModifier();
340     }
341 
getKeyOn(int x, int y)342     public Key getKeyOn(int x, int y) {
343         return mKeyDetector.detectHitKey(x, y);
344     }
345 
setReleasedKeyGraphics(Key key)346     private void setReleasedKeyGraphics(Key key) {
347         mDrawingProxy.dismissKeyPreview(this);
348         if (key == null) {
349             return;
350         }
351 
352         // Even if the key is disabled, update the key release graphics just in case.
353         updateReleaseKeyGraphics(key);
354 
355         if (key.isShift()) {
356             for (final Key shiftKey : mKeyboard.mShiftKeys) {
357                 if (shiftKey != key) {
358                     updateReleaseKeyGraphics(shiftKey);
359                 }
360             }
361         }
362 
363         if (key.altCodeWhileTyping()) {
364             final int altCode = key.mAltCode;
365             final Key altKey = mKeyboard.getKey(altCode);
366             if (altKey != null) {
367                 updateReleaseKeyGraphics(altKey);
368             }
369             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
370                 if (k != key && k.mAltCode == altCode) {
371                     updateReleaseKeyGraphics(k);
372                 }
373             }
374         }
375     }
376 
setPressedKeyGraphics(Key key)377     private void setPressedKeyGraphics(Key key) {
378         if (key == null) {
379             return;
380         }
381 
382         // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
383         final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
384         final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
385         if (!needsToUpdateGraphics) {
386             return;
387         }
388 
389         if (!key.noKeyPreview()) {
390             mDrawingProxy.showKeyPreview(this);
391         }
392         updatePressKeyGraphics(key);
393 
394         if (key.isShift()) {
395             for (final Key shiftKey : mKeyboard.mShiftKeys) {
396                 if (shiftKey != key) {
397                     updatePressKeyGraphics(shiftKey);
398                 }
399             }
400         }
401 
402         if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
403             final int altCode = key.mAltCode;
404             final Key altKey = mKeyboard.getKey(altCode);
405             if (altKey != null) {
406                 updatePressKeyGraphics(altKey);
407             }
408             for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
409                 if (k != key && k.mAltCode == altCode) {
410                     updatePressKeyGraphics(k);
411                 }
412             }
413         }
414     }
415 
updateReleaseKeyGraphics(Key key)416     private void updateReleaseKeyGraphics(Key key) {
417         key.onReleased();
418         mDrawingProxy.invalidateKey(key);
419     }
420 
updatePressKeyGraphics(Key key)421     private void updatePressKeyGraphics(Key key) {
422         key.onPressed();
423         mDrawingProxy.invalidateKey(key);
424     }
425 
getLastX()426     public int getLastX() {
427         return mLastX;
428     }
429 
getLastY()430     public int getLastY() {
431         return mLastY;
432     }
433 
getDownTime()434     public long getDownTime() {
435         return mDownTime;
436     }
437 
onDownKey(int x, int y, long eventTime)438     private Key onDownKey(int x, int y, long eventTime) {
439         mDownTime = eventTime;
440         return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
441     }
442 
onMoveKeyInternal(int x, int y)443     private Key onMoveKeyInternal(int x, int y) {
444         mLastX = x;
445         mLastY = y;
446         return mKeyDetector.detectHitKey(x, y);
447     }
448 
onMoveKey(int x, int y)449     private Key onMoveKey(int x, int y) {
450         return onMoveKeyInternal(x, y);
451     }
452 
onMoveToNewKey(Key newKey, int x, int y)453     private Key onMoveToNewKey(Key newKey, int x, int y) {
454         mCurrentKey = newKey;
455         mKeyX = x;
456         mKeyY = y;
457         return newKey;
458     }
459 
processMotionEvent(int action, int x, int y, long eventTime, KeyEventHandler handler)460     public void processMotionEvent(int action, int x, int y, long eventTime,
461             KeyEventHandler handler) {
462         switch (action) {
463         case MotionEvent.ACTION_DOWN:
464         case MotionEvent.ACTION_POINTER_DOWN:
465             onDownEvent(x, y, eventTime, handler);
466             break;
467         case MotionEvent.ACTION_UP:
468         case MotionEvent.ACTION_POINTER_UP:
469             onUpEvent(x, y, eventTime);
470             break;
471         case MotionEvent.ACTION_MOVE:
472             onMoveEvent(x, y, eventTime);
473             break;
474         case MotionEvent.ACTION_CANCEL:
475             onCancelEvent(x, y, eventTime);
476             break;
477         }
478     }
479 
onDownEvent(int x, int y, long eventTime, KeyEventHandler handler)480     public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
481         if (DEBUG_EVENT)
482             printTouchEvent("onDownEvent:", x, y, eventTime);
483 
484         mDrawingProxy = handler.getDrawingProxy();
485         mTimerProxy = handler.getTimerProxy();
486         setKeyboardActionListener(handler.getKeyboardActionListener());
487         setKeyDetectorInner(handler.getKeyDetector());
488         // Naive up-to-down noise filter.
489         final long deltaT = eventTime - mUpTime;
490         if (deltaT < sParams.mTouchNoiseThresholdTime) {
491             final int dx = x - mLastX;
492             final int dy = y - mLastY;
493             final int distanceSquared = (dx * dx + dy * dy);
494             if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
495                 if (DEBUG_MODE)
496                     Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
497                             + " distance=" + distanceSquared);
498                 if (ProductionFlag.IS_EXPERIMENTAL) {
499                     ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared);
500                 }
501                 mKeyAlreadyProcessed = true;
502                 return;
503             }
504         }
505 
506         final PointerTrackerQueue queue = sPointerTrackerQueue;
507         if (queue != null) {
508             final Key key = getKeyOn(x, y);
509             if (key != null && key.isModifier()) {
510                 // Before processing a down event of modifier key, all pointers already being
511                 // tracked should be released.
512                 queue.releaseAllPointers(eventTime);
513             }
514             queue.add(this);
515         }
516         onDownEventInternal(x, y, eventTime);
517     }
518 
onDownEventInternal(int x, int y, long eventTime)519     private void onDownEventInternal(int x, int y, long eventTime) {
520         Key key = onDownKey(x, y, eventTime);
521         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
522         // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
523         mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
524                 || (key != null && key.isModifier())
525                 || mKeyDetector.alwaysAllowsSlidingInput();
526         mKeyboardLayoutHasBeenChanged = false;
527         mKeyAlreadyProcessed = false;
528         mIsRepeatableKey = false;
529         mIsInSlidingKeyInput = false;
530         mIgnoreModifierKey = false;
531         if (key != null) {
532             // This onPress call may have changed keyboard layout. Those cases are detected at
533             // {@link #setKeyboard}. In those cases, we should update key according to the new
534             // keyboard layout.
535             if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
536                 key = onDownKey(x, y, eventTime);
537             }
538 
539             startRepeatKey(key);
540             startLongPressTimer(key);
541             setPressedKeyGraphics(key);
542         }
543     }
544 
startSlidingKeyInput(Key key)545     private void startSlidingKeyInput(Key key) {
546         if (!mIsInSlidingKeyInput) {
547             mIgnoreModifierKey = key.isModifier();
548         }
549         mIsInSlidingKeyInput = true;
550     }
551 
onMoveEvent(int x, int y, long eventTime)552     public void onMoveEvent(int x, int y, long eventTime) {
553         if (DEBUG_MOVE_EVENT)
554             printTouchEvent("onMoveEvent:", x, y, eventTime);
555         if (mKeyAlreadyProcessed)
556             return;
557 
558         final int lastX = mLastX;
559         final int lastY = mLastY;
560         final Key oldKey = mCurrentKey;
561         Key key = onMoveKey(x, y);
562         if (key != null) {
563             if (oldKey == null) {
564                 // The pointer has been slid in to the new key, but the finger was not on any keys.
565                 // In this case, we must call onPress() to notify that the new key is being pressed.
566                 // This onPress call may have changed keyboard layout. Those cases are detected at
567                 // {@link #setKeyboard}. In those cases, we should update key according to the
568                 // new keyboard layout.
569                 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
570                     key = onMoveKey(x, y);
571                 }
572                 onMoveToNewKey(key, x, y);
573                 startLongPressTimer(key);
574                 setPressedKeyGraphics(key);
575             } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
576                 // The pointer has been slid in to the new key from the previous key, we must call
577                 // onRelease() first to notify that the previous key has been released, then call
578                 // onPress() to notify that the new key is being pressed.
579                 setReleasedKeyGraphics(oldKey);
580                 callListenerOnRelease(oldKey, oldKey.mCode, true);
581                 startSlidingKeyInput(oldKey);
582                 mTimerProxy.cancelKeyTimers();
583                 startRepeatKey(key);
584                 if (mIsAllowedSlidingKeyInput) {
585                     // This onPress call may have changed keyboard layout. Those cases are detected
586                     // at {@link #setKeyboard}. In those cases, we should update key according
587                     // to the new keyboard layout.
588                     if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
589                         key = onMoveKey(x, y);
590                     }
591                     onMoveToNewKey(key, x, y);
592                     startLongPressTimer(key);
593                     setPressedKeyGraphics(key);
594                 } else {
595                     // HACK: On some devices, quick successive touches may be translated to sudden
596                     // move by touch panel firmware. This hack detects the case and translates the
597                     // move event to successive up and down events.
598                     final int dx = x - lastX;
599                     final int dy = y - lastY;
600                     final int lastMoveSquared = dx * dx + dy * dy;
601                     if (sNeedsPhantomSuddenMoveEventHack
602                             && lastMoveSquared >= mKeyQuarterWidthSquared) {
603                         if (DEBUG_MODE) {
604                             Log.w(TAG, String.format("onMoveEvent:"
605                                     + " phantom sudden move event is translated to "
606                                     + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
607                         }
608                         if (ProductionFlag.IS_EXPERIMENTAL) {
609                             ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
610                         }
611                         onUpEventInternal();
612                         onDownEventInternal(x, y, eventTime);
613                     } else {
614                         mKeyAlreadyProcessed = true;
615                         setReleasedKeyGraphics(oldKey);
616                     }
617                 }
618             }
619         } else {
620             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
621                 // The pointer has been slid out from the previous key, we must call onRelease() to
622                 // notify that the previous key has been released.
623                 setReleasedKeyGraphics(oldKey);
624                 callListenerOnRelease(oldKey, oldKey.mCode, true);
625                 startSlidingKeyInput(oldKey);
626                 mTimerProxy.cancelLongPressTimer();
627                 if (mIsAllowedSlidingKeyInput) {
628                     onMoveToNewKey(key, x, y);
629                 } else {
630                     mKeyAlreadyProcessed = true;
631                 }
632             }
633         }
634     }
635 
onUpEvent(int x, int y, long eventTime)636     public void onUpEvent(int x, int y, long eventTime) {
637         if (DEBUG_EVENT)
638             printTouchEvent("onUpEvent  :", x, y, eventTime);
639 
640         final PointerTrackerQueue queue = sPointerTrackerQueue;
641         if (queue != null) {
642             if (mCurrentKey != null && mCurrentKey.isModifier()) {
643                 // Before processing an up event of modifier key, all pointers already being
644                 // tracked should be released.
645                 queue.releaseAllPointersExcept(this, eventTime);
646             } else {
647                 queue.releaseAllPointersOlderThan(this, eventTime);
648             }
649             queue.remove(this);
650         }
651         onUpEventInternal();
652     }
653 
654     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
655     // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
656     // "virtual" up event.
onPhantomUpEvent(int x, int y, long eventTime)657     public void onPhantomUpEvent(int x, int y, long eventTime) {
658         if (DEBUG_EVENT)
659             printTouchEvent("onPhntEvent:", x, y, eventTime);
660         onUpEventInternal();
661         mKeyAlreadyProcessed = true;
662     }
663 
onUpEventInternal()664     private void onUpEventInternal() {
665         mTimerProxy.cancelKeyTimers();
666         mIsInSlidingKeyInput = false;
667         // Release the last pressed key.
668         setReleasedKeyGraphics(mCurrentKey);
669         if (mIsShowingMoreKeysPanel) {
670             mDrawingProxy.dismissMoreKeysPanel();
671             mIsShowingMoreKeysPanel = false;
672         }
673         if (mKeyAlreadyProcessed)
674             return;
675         if (!mIsRepeatableKey) {
676             detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
677         }
678     }
679 
onShowMoreKeysPanel(int x, int y, KeyEventHandler handler)680     public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
681         onLongPressed();
682         onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
683         mIsShowingMoreKeysPanel = true;
684     }
685 
onLongPressed()686     public void onLongPressed() {
687         mKeyAlreadyProcessed = true;
688         setReleasedKeyGraphics(mCurrentKey);
689         final PointerTrackerQueue queue = sPointerTrackerQueue;
690         if (queue != null) {
691             queue.remove(this);
692         }
693     }
694 
onCancelEvent(int x, int y, long eventTime)695     public void onCancelEvent(int x, int y, long eventTime) {
696         if (DEBUG_EVENT)
697             printTouchEvent("onCancelEvt:", x, y, eventTime);
698 
699         final PointerTrackerQueue queue = sPointerTrackerQueue;
700         if (queue != null) {
701             queue.releaseAllPointersExcept(this, eventTime);
702             queue.remove(this);
703         }
704         onCancelEventInternal();
705     }
706 
onCancelEventInternal()707     private void onCancelEventInternal() {
708         mTimerProxy.cancelKeyTimers();
709         setReleasedKeyGraphics(mCurrentKey);
710         mIsInSlidingKeyInput = false;
711         if (mIsShowingMoreKeysPanel) {
712             mDrawingProxy.dismissMoreKeysPanel();
713             mIsShowingMoreKeysPanel = false;
714         }
715     }
716 
startRepeatKey(Key key)717     private void startRepeatKey(Key key) {
718         if (key != null && key.isRepeatable()) {
719             onRegisterKey(key);
720             mTimerProxy.startKeyRepeatTimer(this);
721             mIsRepeatableKey = true;
722         } else {
723             mIsRepeatableKey = false;
724         }
725     }
726 
onRegisterKey(Key key)727     public void onRegisterKey(Key key) {
728         if (key != null) {
729             detectAndSendKey(key, key.mX, key.mY);
730             if (!key.altCodeWhileTyping() && !key.isModifier()) {
731                 mTimerProxy.startTypingStateTimer();
732             }
733         }
734     }
735 
isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey)736     private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
737         if (mKeyDetector == null)
738             throw new NullPointerException("keyboard and/or key detector not set");
739         Key curKey = mCurrentKey;
740         if (newKey == curKey) {
741             return false;
742         } else if (curKey != null) {
743             return curKey.squaredDistanceToEdge(x, y)
744                     >= mKeyDetector.getKeyHysteresisDistanceSquared();
745         } else {
746             return true;
747         }
748     }
749 
startLongPressTimer(Key key)750     private void startLongPressTimer(Key key) {
751         if (key != null && key.isLongPressEnabled()) {
752             mTimerProxy.startLongPressTimer(this);
753         }
754     }
755 
detectAndSendKey(Key key, int x, int y)756     private void detectAndSendKey(Key key, int x, int y) {
757         if (key == null) {
758             callListenerOnCancelInput();
759             return;
760         }
761 
762         int code = key.mCode;
763         callListenerOnCodeInput(key, code, x, y);
764         callListenerOnRelease(key, code, false);
765     }
766 
767     private long mPreviousEventTime;
768 
printTouchEvent(String title, int x, int y, long eventTime)769     private void printTouchEvent(String title, int x, int y, long eventTime) {
770         final Key key = mKeyDetector.detectHitKey(x, y);
771         final String code = KeyDetector.printableCode(key);
772         final long delta = eventTime - mPreviousEventTime;
773         Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
774                 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, code));
775         mPreviousEventTime = eventTime;
776     }
777 }
778