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