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