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