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