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