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