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