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