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