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