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