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