PointerTracker.java revision 8632bff2d5a8e1160989008dea6eff4b94b065dd
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 com.android.inputmethod.keyboard.KeyboardView.UIHandler;
20import com.android.inputmethod.latin.LatinImeLogger;
21import com.android.inputmethod.latin.R;
22import com.android.inputmethod.latin.SubtypeSwitcher;
23
24import android.content.res.Resources;
25import android.util.Log;
26import android.view.MotionEvent;
27
28import java.util.Arrays;
29import java.util.List;
30
31public class PointerTracker {
32    private static final String TAG = PointerTracker.class.getSimpleName();
33    private static final boolean ENABLE_ASSERTION = false;
34    private static final boolean DEBUG_EVENT = false;
35    private static final boolean DEBUG_MOVE_EVENT = false;
36    private static final boolean DEBUG_LISTENER = false;
37    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
38
39    public interface UIProxy {
40        public void invalidateKey(Key key);
41        public void showKeyPreview(int keyIndex, PointerTracker tracker);
42        public void dismissKeyPreview(PointerTracker tracker);
43        public boolean hasDistinctMultitouch();
44    }
45
46    public final int mPointerId;
47
48    // Timing constants
49    private final int mDelayBeforeKeyRepeatStart;
50    private final int mLongPressKeyTimeout;
51    private final int mLongPressShiftKeyTimeout;
52
53    private final KeyboardView mKeyboardView;
54    private final UIProxy mProxy;
55    private final UIHandler mHandler;
56    private final KeyDetector mKeyDetector;
57    private KeyboardActionListener mListener = EMPTY_LISTENER;
58    private final KeyboardSwitcher mKeyboardSwitcher;
59    private final boolean mHasDistinctMultitouch;
60    private final boolean mConfigSlidingKeyInputEnabled;
61
62    private final int mTouchNoiseThresholdMillis;
63    private final int mTouchNoiseThresholdDistanceSquared;
64
65    private Keyboard mKeyboard;
66    private List<Key> mKeys;
67    private int mKeyHysteresisDistanceSquared = -1;
68    private int mKeyQuarterWidthSquared;
69
70    private final PointerTrackerKeyState mKeyState;
71
72    // true if keyboard layout has been changed.
73    private boolean mKeyboardLayoutHasBeenChanged;
74
75    // true if event is already translated to a key action (long press or mini-keyboard)
76    private boolean mKeyAlreadyProcessed;
77
78    // true if this pointer is repeatable key
79    private boolean mIsRepeatableKey;
80
81    // true if this pointer is in sliding key input
82    private boolean mIsInSlidingKeyInput;
83
84    // true if sliding key is allowed.
85    private boolean mIsAllowedSlidingKeyInput;
86
87    // ignore modifier key if true
88    private boolean mIgnoreModifierKey;
89
90    // TODO: Remove these hacking variables
91    // true if this pointer is in sliding language switch
92    private boolean mIsInSlidingLanguageSwitch;
93    private int mSpaceKeyIndex;
94    private final SubtypeSwitcher mSubtypeSwitcher;
95
96    // Empty {@link KeyboardActionListener}
97    private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
98        @Override
99        public void onPress(int primaryCode, boolean withSliding) {}
100        @Override
101        public void onRelease(int primaryCode, boolean withSliding) {}
102        @Override
103        public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {}
104        @Override
105        public void onTextInput(CharSequence text) {}
106        @Override
107        public void onCancelInput() {}
108        @Override
109        public void onSwipeDown() {}
110    };
111
112    public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler,
113            KeyDetector keyDetector, UIProxy proxy) {
114        if (proxy == null || handler == null || keyDetector == null)
115            throw new NullPointerException();
116        mPointerId = id;
117        mKeyboardView = keyboardView;
118        mProxy = proxy;
119        mHandler = handler;
120        mKeyDetector = keyDetector;
121        mKeyboardSwitcher = KeyboardSwitcher.getInstance();
122        mKeyState = new PointerTrackerKeyState(keyDetector);
123        mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
124        final Resources res = mKeyboardView.getResources();
125        mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
126        mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
127        mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout);
128        mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout);
129        mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis);
130        final float touchNoiseThresholdDistance = res.getDimension(
131                R.dimen.config_touch_noise_threshold_distance);
132        mTouchNoiseThresholdDistanceSquared = (int)(
133                touchNoiseThresholdDistance * touchNoiseThresholdDistance);
134        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
135    }
136
137    public void setOnKeyboardActionListener(KeyboardActionListener listener) {
138        mListener = listener;
139    }
140
141    // Returns true if keyboard has been changed by this callback.
142    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
143        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
144        if (DEBUG_LISTENER)
145            Log.d(TAG, "onPress    : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding
146                    + " ignoreModifier=" + ignoreModifierKey);
147        if (ignoreModifierKey)
148            return false;
149        if (key.mEnabled) {
150            mListener.onPress(key.mCode, withSliding);
151            final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
152            mKeyboardLayoutHasBeenChanged = false;
153            return keyboardLayoutHasBeenChanged;
154        }
155        return false;
156    }
157
158    // Note that we need primaryCode argument because the keyboard may in shifted state and the
159    // primaryCode is different from {@link Key#mCode}.
160    private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
161        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
162        if (DEBUG_LISTENER)
163            Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
164                    + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y
165                    + " ignoreModifier=" + ignoreModifierKey);
166        if (ignoreModifierKey)
167            return;
168        if (key.mEnabled)
169            mListener.onCodeInput(primaryCode, keyCodes, x, y);
170    }
171
172    private void callListenerOnTextInput(Key key) {
173        if (DEBUG_LISTENER)
174            Log.d(TAG, "onTextInput: text=" + key.mOutputText);
175        if (key.mEnabled)
176            mListener.onTextInput(key.mOutputText);
177    }
178
179    // Note that we need primaryCode argument because the keyboard may in shifted state and the
180    // primaryCode is different from {@link Key#mCode}.
181    private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
182        final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode);
183        if (DEBUG_LISTENER)
184            Log.d(TAG, "onRelease  : " + keyCodePrintable(primaryCode) + " sliding="
185                    + withSliding + " ignoreModifier=" + ignoreModifierKey);
186        if (ignoreModifierKey)
187            return;
188        if (key.mEnabled)
189            mListener.onRelease(primaryCode, withSliding);
190    }
191
192    private void callListenerOnCancelInput() {
193        if (DEBUG_LISTENER)
194            Log.d(TAG, "onCancelInput");
195        mListener.onCancelInput();
196    }
197
198    public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) {
199        if (keyboard == null || keyHysteresisDistance < 0)
200            throw new IllegalArgumentException();
201        mKeyboard = keyboard;
202        mKeys = keyboard.getKeys();
203        mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
204        final int keyQuarterWidth = keyboard.getKeyWidth() / 4;
205        mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
206        // Mark that keyboard layout has been changed.
207        mKeyboardLayoutHasBeenChanged = true;
208    }
209
210    public boolean isInSlidingKeyInput() {
211        return mIsInSlidingKeyInput;
212    }
213
214    private boolean isValidKeyIndex(int keyIndex) {
215        return keyIndex >= 0 && keyIndex < mKeys.size();
216    }
217
218    public Key getKey(int keyIndex) {
219        return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null;
220    }
221
222    private static boolean isModifierCode(int primaryCode) {
223        return primaryCode == Keyboard.CODE_SHIFT
224                || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL;
225    }
226
227    private boolean isModifierInternal(int keyIndex) {
228        final Key key = getKey(keyIndex);
229        return key == null ? false : isModifierCode(key.mCode);
230    }
231
232    public boolean isModifier() {
233        return isModifierInternal(mKeyState.getKeyIndex());
234    }
235
236    private boolean isOnModifierKey(int x, int y) {
237        return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
238    }
239
240    public boolean isOnShiftKey(int x, int y) {
241        final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
242        return key != null && key.mCode == Keyboard.CODE_SHIFT;
243    }
244
245    public int getKeyIndexOn(int x, int y) {
246        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
247    }
248
249    public boolean isSpaceKey(int keyIndex) {
250        Key key = getKey(keyIndex);
251        return key != null && key.mCode == Keyboard.CODE_SPACE;
252    }
253
254    public void setReleasedKeyGraphics() {
255        setReleasedKeyGraphics(mKeyState.getKeyIndex());
256    }
257
258    private void setReleasedKeyGraphics(int keyIndex) {
259        final Key key = getKey(keyIndex);
260        if (key != null) {
261            key.onReleased();
262            mProxy.invalidateKey(key);
263        }
264    }
265
266    private void setPressedKeyGraphics(int keyIndex) {
267        final Key key = getKey(keyIndex);
268        if (key != null && key.mEnabled) {
269            key.onPressed();
270            mProxy.invalidateKey(key);
271        }
272    }
273
274    private void checkAssertion(PointerTrackerQueue queue) {
275        if (mHasDistinctMultitouch && queue == null)
276            throw new RuntimeException(
277                    "PointerTrackerQueue must be passed on distinct multi touch device");
278        if (!mHasDistinctMultitouch && queue != null)
279            throw new RuntimeException(
280                    "PointerTrackerQueue must be null on non-distinct multi touch device");
281    }
282
283    public void onTouchEvent(int action, int x, int y, long eventTime, PointerTrackerQueue queue) {
284        switch (action) {
285        case MotionEvent.ACTION_MOVE:
286            onMoveEvent(x, y, eventTime, queue);
287            break;
288        case MotionEvent.ACTION_DOWN:
289        case MotionEvent.ACTION_POINTER_DOWN:
290            onDownEvent(x, y, eventTime, queue);
291            break;
292        case MotionEvent.ACTION_UP:
293        case MotionEvent.ACTION_POINTER_UP:
294            onUpEvent(x, y, eventTime, queue);
295            break;
296        case MotionEvent.ACTION_CANCEL:
297            onCancelEvent(x, y, eventTime, queue);
298            break;
299        }
300    }
301
302    public void onDownEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
303        if (ENABLE_ASSERTION) checkAssertion(queue);
304        if (DEBUG_EVENT)
305            printTouchEvent("onDownEvent:", x, y, eventTime);
306
307        // Naive up-to-down noise filter.
308        final long deltaT = eventTime - mKeyState.getUpTime();
309        if (deltaT < mTouchNoiseThresholdMillis) {
310            final int dx = x - mKeyState.getLastX();
311            final int dy = y - mKeyState.getLastY();
312            final int distanceSquared = (dx * dx + dy * dy);
313            if (distanceSquared < mTouchNoiseThresholdDistanceSquared) {
314                if (DEBUG_MODE)
315                    Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
316                            + " distance=" + distanceSquared);
317                mKeyAlreadyProcessed = true;
318                return;
319            }
320        }
321
322        if (queue != null) {
323            if (isOnModifierKey(x, y)) {
324                // Before processing a down event of modifier key, all pointers already being
325                // tracked should be released.
326                queue.releaseAllPointers(eventTime);
327            }
328            queue.add(this);
329        }
330        onDownEventInternal(x, y, eventTime);
331    }
332
333    private void onDownEventInternal(int x, int y, long eventTime) {
334        int keyIndex = mKeyState.onDownKey(x, y, eventTime);
335        // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
336        // from modifier key, or 3) this pointer is on mini-keyboard.
337        mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
338                || mKeyDetector instanceof MiniKeyboardKeyDetector;
339        mKeyboardLayoutHasBeenChanged = false;
340        mKeyAlreadyProcessed = false;
341        mIsRepeatableKey = false;
342        mIsInSlidingKeyInput = false;
343        mIsInSlidingLanguageSwitch = false;
344        mIgnoreModifierKey = false;
345        if (isValidKeyIndex(keyIndex)) {
346            // This onPress call may have changed keyboard layout. Those cases are detected at
347            // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
348            // keyboard layout.
349            if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
350                keyIndex = mKeyState.onDownKey(x, y, eventTime);
351
352            startRepeatKey(keyIndex);
353            startLongPressTimer(keyIndex);
354            showKeyPreview(keyIndex);
355            setPressedKeyGraphics(keyIndex);
356        }
357    }
358
359    private void startSlidingKeyInput(Key key) {
360        if (!mIsInSlidingKeyInput)
361            mIgnoreModifierKey = isModifierCode(key.mCode);
362        mIsInSlidingKeyInput = true;
363    }
364
365    public void onMoveEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
366        if (ENABLE_ASSERTION) checkAssertion(queue);
367        if (DEBUG_MOVE_EVENT)
368            printTouchEvent("onMoveEvent:", x, y, eventTime);
369        if (mKeyAlreadyProcessed)
370            return;
371        final PointerTrackerKeyState keyState = mKeyState;
372
373        // TODO: Remove this hacking code
374        if (mIsInSlidingLanguageSwitch) {
375            ((LatinKeyboard)mKeyboard).updateSpacebarPreviewIcon(x - keyState.getKeyX());
376            showKeyPreview(mSpaceKeyIndex);
377            return;
378        }
379        final int lastX = keyState.getLastX();
380        final int lastY = keyState.getLastY();
381        final int oldKeyIndex = keyState.getKeyIndex();
382        final Key oldKey = getKey(oldKeyIndex);
383        int keyIndex = keyState.onMoveKey(x, y);
384        if (isValidKeyIndex(keyIndex)) {
385            if (oldKey == null) {
386                // The pointer has been slid in to the new key, but the finger was not on any keys.
387                // In this case, we must call onPress() to notify that the new key is being pressed.
388                // This onPress call may have changed keyboard layout. Those cases are detected at
389                // {@link #setKeyboard}. In those cases, we should update keyIndex according to the
390                // new keyboard layout.
391                if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
392                    keyIndex = keyState.onMoveKey(x, y);
393                keyState.onMoveToNewKey(keyIndex, x, y);
394                startLongPressTimer(keyIndex);
395                showKeyPreview(keyIndex);
396                setPressedKeyGraphics(keyIndex);
397            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
398                // The pointer has been slid in to the new key from the previous key, we must call
399                // onRelease() first to notify that the previous key has been released, then call
400                // onPress() to notify that the new key is being pressed.
401                setReleasedKeyGraphics(oldKeyIndex);
402                callListenerOnRelease(oldKey, oldKey.mCode, true);
403                startSlidingKeyInput(oldKey);
404                mHandler.cancelKeyTimers();
405                startRepeatKey(keyIndex);
406                if (mIsAllowedSlidingKeyInput) {
407                    // This onPress call may have changed keyboard layout. Those cases are detected
408                    // at {@link #setKeyboard}. In those cases, we should update keyIndex according
409                    // to the new keyboard layout.
410                    if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
411                        keyIndex = keyState.onMoveKey(x, y);
412                    keyState.onMoveToNewKey(keyIndex, x, y);
413                    startLongPressTimer(keyIndex);
414                    setPressedKeyGraphics(keyIndex);
415                    showKeyPreview(keyIndex);
416                } else {
417                    // HACK: On some devices, quick successive touches may be translated to sudden
418                    // move by touch panel firmware. This hack detects the case and translates the
419                    // move event to successive up and down events.
420                    final int dx = x - lastX;
421                    final int dy = y - lastY;
422                    final int lastMoveSquared = dx * dx + dy * dy;
423                    if (lastMoveSquared >= mKeyQuarterWidthSquared) {
424                        if (DEBUG_MODE)
425                            Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
426                                    + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
427                        onUpEventInternal(lastX, lastY, eventTime, true);
428                        onDownEventInternal(x, y, eventTime);
429                    } else {
430                        mKeyAlreadyProcessed = true;
431                        dismissKeyPreview();
432                        setReleasedKeyGraphics(oldKeyIndex);
433                    }
434                }
435            }
436            // TODO: Remove this hack code
437            else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch
438                    && mKeyboard instanceof LatinKeyboard) {
439                final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard);
440                if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()
441                        && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
442                    final int diff = x - keyState.getKeyX();
443                    if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) {
444                        // Detect start sliding language switch.
445                        mIsInSlidingLanguageSwitch = true;
446                        mSpaceKeyIndex = keyIndex;
447                        keyboard.updateSpacebarPreviewIcon(diff);
448                        // Display spacebar slide language switcher.
449                        showKeyPreview(keyIndex);
450                        if (queue != null)
451                            queue.releaseAllPointersExcept(this, eventTime, true);
452                    }
453                }
454            }
455        } else {
456            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
457                // The pointer has been slid out from the previous key, we must call onRelease() to
458                // notify that the previous key has been released.
459                setReleasedKeyGraphics(oldKeyIndex);
460                callListenerOnRelease(oldKey, oldKey.mCode, true);
461                startSlidingKeyInput(oldKey);
462                mHandler.cancelLongPressTimers();
463                if (mIsAllowedSlidingKeyInput) {
464                    keyState.onMoveToNewKey(keyIndex, x, y);
465                } else {
466                    mKeyAlreadyProcessed = true;
467                    dismissKeyPreview();
468                }
469            }
470        }
471    }
472
473    public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
474        if (ENABLE_ASSERTION) checkAssertion(queue);
475        if (DEBUG_EVENT)
476            printTouchEvent("onUpEvent  :", x, y, eventTime);
477
478        if (queue != null) {
479            if (isModifier()) {
480                // Before processing an up event of modifier key, all pointers already being
481                // tracked should be released.
482                queue.releaseAllPointersExcept(this, eventTime, true);
483            } else {
484                queue.releaseAllPointersOlderThan(this, eventTime);
485            }
486            queue.remove(this);
487        }
488        onUpEventInternal(x, y, eventTime, true);
489    }
490
491    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
492    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
493    // "virtual" up event.
494    public void onPhantomUpEvent(int x, int y, long eventTime, boolean updateReleasedKeyGraphics) {
495        if (DEBUG_EVENT)
496            printTouchEvent("onPhntEvent:", x, y, eventTime);
497        onUpEventInternal(x, y, eventTime, updateReleasedKeyGraphics);
498        mKeyAlreadyProcessed = true;
499    }
500
501    private void onUpEventInternal(int x, int y, long eventTime,
502            boolean updateReleasedKeyGraphics) {
503        mHandler.cancelKeyTimers();
504        mHandler.cancelShowKeyPreview(this);
505        mIsInSlidingKeyInput = false;
506        final PointerTrackerKeyState keyState = mKeyState;
507        final int keyX, keyY;
508        if (isMajorEnoughMoveToBeOnNewKey(x, y, keyState.onMoveKey(x, y))) {
509            keyX = x;
510            keyY = y;
511        } else {
512            // Use previous fixed key coordinates.
513            keyX = keyState.getKeyX();
514            keyY = keyState.getKeyY();
515        }
516        final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime);
517        dismissKeyPreview();
518        if (updateReleasedKeyGraphics)
519            setReleasedKeyGraphics(keyIndex);
520        if (mKeyAlreadyProcessed)
521            return;
522        // TODO: Remove this hacking code
523        if (mIsInSlidingLanguageSwitch) {
524            setReleasedKeyGraphics(mSpaceKeyIndex);
525            final int languageDir = ((LatinKeyboard)mKeyboard).getLanguageChangeDirection();
526            if (languageDir != 0) {
527                final int code = (languageDir == 1)
528                        ? LatinKeyboard.CODE_NEXT_LANGUAGE : LatinKeyboard.CODE_PREV_LANGUAGE;
529                // This will change keyboard layout.
530                mListener.onCodeInput(code, new int[] {code}, keyX, keyY);
531            }
532            mIsInSlidingLanguageSwitch = false;
533            ((LatinKeyboard)mKeyboard).setSpacebarSlidingLanguageSwitchDiff(0);
534            return;
535        }
536        if (!mIsRepeatableKey) {
537            detectAndSendKey(keyIndex, keyX, keyY);
538        }
539    }
540
541    public void onLongPressed(PointerTrackerQueue queue) {
542        mKeyAlreadyProcessed = true;
543        if (queue != null)
544            queue.remove(this);
545    }
546
547    public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
548        if (ENABLE_ASSERTION) checkAssertion(queue);
549        if (DEBUG_EVENT)
550            printTouchEvent("onCancelEvt:", x, y, eventTime);
551
552        if (queue != null) {
553            queue.releaseAllPointersExcept(this, eventTime, true);
554            queue.remove(this);
555        }
556        onCancelEventInternal();
557    }
558
559    private void onCancelEventInternal() {
560        mHandler.cancelKeyTimers();
561        mHandler.cancelShowKeyPreview(this);
562        dismissKeyPreview();
563        setReleasedKeyGraphics(mKeyState.getKeyIndex());
564        mIsInSlidingKeyInput = false;
565    }
566
567    private void startRepeatKey(int keyIndex) {
568        final Key key = getKey(keyIndex);
569        if (key != null && key.mRepeatable) {
570            dismissKeyPreview();
571            onRepeatKey(keyIndex);
572            mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
573            mIsRepeatableKey = true;
574        } else {
575            mIsRepeatableKey = false;
576        }
577    }
578
579    public void onRepeatKey(int keyIndex) {
580        Key key = getKey(keyIndex);
581        if (key != null) {
582            detectAndSendKey(keyIndex, key.mX, key.mY);
583        }
584    }
585
586    public int getLastX() {
587        return mKeyState.getLastX();
588    }
589
590    public int getLastY() {
591        return mKeyState.getLastY();
592    }
593
594    public long getDownTime() {
595        return mKeyState.getDownTime();
596    }
597
598    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) {
599        if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
600            throw new IllegalStateException("keyboard and/or hysteresis not set");
601        int curKey = mKeyState.getKeyIndex();
602        if (newKey == curKey) {
603            return false;
604        } else if (isValidKeyIndex(curKey)) {
605            return mKeys.get(curKey).squaredDistanceToEdge(x, y) >= mKeyHysteresisDistanceSquared;
606        } else {
607            return true;
608        }
609    }
610
611    // The modifier key, such as shift key, should not show its key preview.
612    private boolean isKeyPreviewNotRequired(int keyIndex) {
613        final Key key = getKey(keyIndex);
614        if (!key.mEnabled)
615            return true;
616        // Such as spacebar sliding language switch.
617        if (mKeyboard.needSpacebarPreview(keyIndex))
618            return false;
619        final int code = key.mCode;
620        return isModifierCode(code) || code == Keyboard.CODE_DELETE
621                || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE;
622    }
623
624    private void showKeyPreview(int keyIndex) {
625        if (isKeyPreviewNotRequired(keyIndex))
626            return;
627        mProxy.showKeyPreview(keyIndex, this);
628    }
629
630    private void dismissKeyPreview() {
631        mProxy.dismissKeyPreview(this);
632    }
633
634    private void startLongPressTimer(int keyIndex) {
635        Key key = getKey(keyIndex);
636        if (!key.mEnabled)
637            return;
638        if (key.mCode == Keyboard.CODE_SHIFT) {
639            mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
640        } else if (key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY
641                && mKeyboard.isManualTemporaryUpperCase()) {
642            // We need not start long press timer on the key which has manual temporary upper case
643            // code defined and the keyboard is in manual temporary upper case mode.
644            return;
645        } else if (mKeyboardSwitcher.isInMomentarySwitchState()) {
646            // We use longer timeout for sliding finger input started from the symbols mode key.
647            mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this);
648        } else {
649            mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this);
650        }
651    }
652
653    private void detectAndSendKey(int index, int x, int y) {
654        final Key key = getKey(index);
655        if (key == null) {
656            callListenerOnCancelInput();
657            return;
658        }
659        if (key.mOutputText != null) {
660            callListenerOnTextInput(key);
661            callListenerOnRelease(key, key.mCode, false);
662        } else {
663            int code = key.mCode;
664            final int[] codes = mKeyDetector.newCodeArray();
665            mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
666
667            // If keyboard is in manual temporary upper case state and key has manual temporary
668            // shift code, alternate character code should be sent.
669            if (mKeyboard.isManualTemporaryUpperCase()
670                    && key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY) {
671                code = key.mManualTemporaryUpperCaseCode;
672                codes[0] = code;
673            }
674
675            // Swap the first and second values in the codes array if the primary code is not the
676            // first value but the second value in the array. This happens when key debouncing is
677            // in effect.
678            if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
679                codes[1] = codes[0];
680                codes[0] = code;
681            }
682            callListenerOnCodeInput(key, code, codes, x, y);
683            callListenerOnRelease(key, code, false);
684        }
685    }
686
687    public CharSequence getPreviewText(Key key) {
688        return key.mLabel;
689    }
690
691    private long mPreviousEventTime;
692
693    private void printTouchEvent(String title, int x, int y, long eventTime) {
694        final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
695        final Key key = getKey(keyIndex);
696        final String code = (key == null) ? "----" : keyCodePrintable(key.mCode);
697        final long delta = eventTime - mPreviousEventTime;
698        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title,
699                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code));
700        mPreviousEventTime = eventTime;
701    }
702
703    private static String keyCodePrintable(int primaryCode) {
704        final String modifier = isModifierCode(primaryCode) ? " modifier" : "";
705        return  String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier;
706    }
707}
708