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