PointerTracker.java revision f39fccbd0fd63647c52e8eabcb60df69f97492b5
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.os.SystemClock;
20import android.util.Log;
21import android.view.MotionEvent;
22import android.view.View;
23import android.widget.TextView;
24
25import com.android.inputmethod.keyboard.internal.GestureStroke;
26import com.android.inputmethod.keyboard.internal.GestureTracker;
27import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
28import com.android.inputmethod.latin.InputPointers;
29import com.android.inputmethod.latin.LatinImeLogger;
30import com.android.inputmethod.latin.ResearchLogger;
31import com.android.inputmethod.latin.define.ProductionFlag;
32
33import java.util.ArrayList;
34
35public class PointerTracker {
36    private static final String TAG = PointerTracker.class.getSimpleName();
37    private static final boolean DEBUG_EVENT = false;
38    private static final boolean DEBUG_MOVE_EVENT = false;
39    private static final boolean DEBUG_LISTENER = false;
40    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
41
42    public interface KeyEventHandler {
43        /**
44         * Get KeyDetector object that is used for this PointerTracker.
45         * @return the KeyDetector object that is used for this PointerTracker
46         */
47        public KeyDetector getKeyDetector();
48
49        /**
50         * Get KeyboardActionListener object that is used to register key code and so on.
51         * @return the KeyboardActionListner for this PointerTracker
52         */
53        public KeyboardActionListener getKeyboardActionListener();
54
55        /**
56         * Get DrawingProxy object that is used for this PointerTracker.
57         * @return the DrawingProxy object that is used for this PointerTracker
58         */
59        public DrawingProxy getDrawingProxy();
60
61        /**
62         * Get TimerProxy object that handles key repeat and long press timer event for this
63         * PointerTracker.
64         * @return the TimerProxy object that handles key repeat and long press timer event.
65         */
66        public TimerProxy getTimerProxy();
67    }
68
69    public interface DrawingProxy extends MoreKeysPanel.Controller {
70        public void invalidateKey(Key key);
71        public TextView inflateKeyPreviewText();
72        public void showKeyPreview(PointerTracker tracker);
73        public void dismissKeyPreview(PointerTracker tracker);
74    }
75
76    public interface TimerProxy {
77        public void startTypingStateTimer();
78        public boolean isTypingState();
79        public void startKeyRepeatTimer(PointerTracker tracker);
80        public void startLongPressTimer(PointerTracker tracker);
81        public void startLongPressTimer(int code);
82        public void cancelLongPressTimer();
83        public void startDoubleTapTimer();
84        public void cancelDoubleTapTimer();
85        public boolean isInDoubleTapTimeout();
86        public void cancelKeyTimers();
87
88        public static class Adapter implements TimerProxy {
89            @Override
90            public void startTypingStateTimer() {}
91            @Override
92            public boolean isTypingState() { return false; }
93            @Override
94            public void startKeyRepeatTimer(PointerTracker tracker) {}
95            @Override
96            public void startLongPressTimer(PointerTracker tracker) {}
97            @Override
98            public void startLongPressTimer(int code) {}
99            @Override
100            public void cancelLongPressTimer() {}
101            @Override
102            public void startDoubleTapTimer() {}
103            @Override
104            public void cancelDoubleTapTimer() {}
105            @Override
106            public boolean isInDoubleTapTimeout() { return false; }
107            @Override
108            public void cancelKeyTimers() {}
109        }
110    }
111
112    // Parameters for pointer handling.
113    private static LatinKeyboardView.PointerTrackerParams sParams;
114    private static int sTouchNoiseThresholdDistanceSquared;
115    private static boolean sNeedsPhantomSuddenMoveEventHack;
116
117    private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>();
118    private static PointerTrackerQueue sPointerTrackerQueue;
119
120    public final int mPointerId;
121
122    private DrawingProxy mDrawingProxy;
123    private TimerProxy mTimerProxy;
124    private KeyDetector mKeyDetector;
125    private KeyboardActionListener mListener = EMPTY_LISTENER;
126
127    private Keyboard mKeyboard;
128    private int mKeyQuarterWidthSquared;
129    private final TextView mKeyPreviewText;
130
131    // The position and time at which first down event occurred.
132    private long mDownTime;
133    private long mUpTime;
134
135    // The current key where this pointer is.
136    private Key mCurrentKey = null;
137    // The position where the current key was recognized for the first time.
138    private int mKeyX;
139    private int mKeyY;
140
141    // Last pointer position.
142    private int mLastX;
143    private int mLastY;
144
145    // true if keyboard layout has been changed.
146    private boolean mKeyboardLayoutHasBeenChanged;
147
148    // true if event is already translated to a key action.
149    private boolean mKeyAlreadyProcessed;
150
151    // true if this pointer has been long-pressed and is showing a more keys panel.
152    private boolean mIsShowingMoreKeysPanel;
153
154    // true if this pointer is in sliding key input
155    boolean mIsInSlidingKeyInput;
156
157    // true if sliding key is allowed.
158    private boolean mIsAllowedSlidingKeyInput;
159
160    // ignore modifier key if true
161    private boolean mIgnoreModifierKey;
162
163    // Empty {@link KeyboardActionListener}
164    private static final KeyboardActionListener EMPTY_LISTENER =
165            new KeyboardActionListener.Adapter();
166
167    // Gesture tracker singleton instance
168    private static final GestureTracker sGestureTracker = GestureTracker.getInstance();
169
170    private final GestureStroke mGestureStroke;
171
172    public static void init(boolean hasDistinctMultitouch,
173            boolean needsPhantomSuddenMoveEventHack) {
174        if (hasDistinctMultitouch) {
175            sPointerTrackerQueue = new PointerTrackerQueue();
176        } else {
177            sPointerTrackerQueue = null;
178        }
179        sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
180
181        setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT);
182    }
183
184    public static void setParameters(LatinKeyboardView.PointerTrackerParams params) {
185        sParams = params;
186        sTouchNoiseThresholdDistanceSquared = (int)(
187                params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
188    }
189
190    public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
191        final ArrayList<PointerTracker> trackers = sTrackers;
192
193        // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
194        for (int i = trackers.size(); i <= id; i++) {
195            final PointerTracker tracker = new PointerTracker(i, handler);
196            trackers.add(tracker);
197        }
198
199        return trackers.get(id);
200    }
201
202    public static boolean isAnyInSlidingKeyInput() {
203        return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false;
204    }
205
206    public static void setKeyboardActionListener(KeyboardActionListener listener) {
207        for (final PointerTracker tracker : sTrackers) {
208            tracker.mListener = listener;
209        }
210        GestureTracker.init(listener);
211    }
212
213    public static void setKeyDetector(KeyDetector keyDetector) {
214        for (final PointerTracker tracker : sTrackers) {
215            tracker.setKeyDetectorInner(keyDetector);
216            // Mark that keyboard layout has been changed.
217            tracker.mKeyboardLayoutHasBeenChanged = true;
218        }
219        sGestureTracker.setKeyboard(keyDetector.getKeyboard());
220    }
221
222    public static void dismissAllKeyPreviews() {
223        for (final PointerTracker tracker : sTrackers) {
224            tracker.getKeyPreviewText().setVisibility(View.INVISIBLE);
225            tracker.setReleasedKeyGraphics(tracker.mCurrentKey);
226        }
227    }
228
229    // The working and returning object of the following methods,
230    // {@link #getIncrementalBatchPoints()} and {@link #getAllBatchPoints()}.
231    private static final InputPointers mAggregatedPointers = new InputPointers();
232
233    // TODO: This method is called only from GestureTracker and should address the thread-safty
234    // issue soon.
235    public static InputPointers getIncrementalBatchPoints() {
236        final InputPointers pointers = mAggregatedPointers;
237        pointers.reset();
238        for (final PointerTracker tracker : sTrackers) {
239            tracker.getGestureStroke().appendIncrementalBatchPoints(pointers);
240        }
241        return pointers;
242    }
243
244    // TODO: This method is called only from GestureTracker and should address the thread-safety
245    // issue soon.
246    public static InputPointers getAllBatchPoints() {
247        final InputPointers pointers = mAggregatedPointers;
248        pointers.reset();
249        for (final PointerTracker tracker : sTrackers) {
250            tracker.getGestureStroke().appendAllBatchPoints(pointers);
251        }
252        return pointers;
253    }
254
255    public static void clearBatchInputPoints() {
256        for (final PointerTracker tracker : sTrackers) {
257            tracker.getGestureStroke().reset();
258        }
259    }
260
261    private PointerTracker(int id, KeyEventHandler handler) {
262        if (handler == null)
263            throw new NullPointerException();
264        mPointerId = id;
265        mGestureStroke = new GestureStroke(id);
266        setKeyDetectorInner(handler.getKeyDetector());
267        mListener = handler.getKeyboardActionListener();
268        mDrawingProxy = handler.getDrawingProxy();
269        mTimerProxy = handler.getTimerProxy();
270        mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText();
271    }
272
273    public TextView getKeyPreviewText() {
274        return mKeyPreviewText;
275    }
276
277    public GestureStroke getGestureStroke() {
278        return mGestureStroke;
279    }
280
281    // Returns true if keyboard has been changed by this callback.
282    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
283        if (sGestureTracker.isInGesture()) {
284            return false;
285        }
286        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
287        if (DEBUG_LISTENER) {
288            Log.d(TAG, "onPress    : " + KeyDetector.printableCode(key)
289                    + " ignoreModifier=" + ignoreModifierKey
290                    + " enabled=" + key.isEnabled());
291        }
292        if (ignoreModifierKey) {
293            return false;
294        }
295        if (key.isEnabled()) {
296            mListener.onPressKey(key.mCode);
297            final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
298            mKeyboardLayoutHasBeenChanged = false;
299            if (!key.altCodeWhileTyping() && !key.isModifier()) {
300                mTimerProxy.startTypingStateTimer();
301            }
302            return keyboardLayoutHasBeenChanged;
303        }
304        return false;
305    }
306
307    // Note that we need primaryCode argument because the keyboard may in shifted state and the
308    // primaryCode is different from {@link Key#mCode}.
309    private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) {
310        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
311        final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
312        final int code = altersCode ? key.mAltCode : primaryCode;
313        if (DEBUG_LISTENER) {
314            Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText
315                    + " x=" + x + " y=" + y
316                    + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode
317                    + " enabled=" + key.isEnabled());
318        }
319        if (ProductionFlag.IS_EXPERIMENTAL) {
320            ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey,
321                    altersCode, code);
322        }
323        if (ignoreModifierKey) {
324            return;
325        }
326        // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
327        if (key.isEnabled() || altersCode) {
328            if (code == Keyboard.CODE_OUTPUT_TEXT) {
329                mListener.onTextInput(key.mOutputText);
330            } else if (code != Keyboard.CODE_UNSPECIFIED) {
331                mListener.onCodeInput(code, x, y);
332            }
333        }
334    }
335
336    // Note that we need primaryCode argument because the keyboard may in shifted state and the
337    // primaryCode is different from {@link Key#mCode}.
338    private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
339        if (sGestureTracker.isInGesture()) {
340            return;
341        }
342        final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
343        if (DEBUG_LISTENER) {
344            Log.d(TAG, "onRelease  : " + Keyboard.printableCode(primaryCode)
345                    + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey
346                    + " enabled="+ key.isEnabled());
347        }
348        if (ProductionFlag.IS_EXPERIMENTAL) {
349            ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding,
350                    ignoreModifierKey);
351        }
352        if (ignoreModifierKey) {
353            return;
354        }
355        if (key.isEnabled()) {
356            mListener.onReleaseKey(primaryCode, withSliding);
357        }
358    }
359
360    private void callListenerOnCancelInput() {
361        if (DEBUG_LISTENER)
362            Log.d(TAG, "onCancelInput");
363        if (ProductionFlag.IS_EXPERIMENTAL) {
364            ResearchLogger.pointerTracker_callListenerOnCancelInput();
365        }
366        mListener.onCancelInput();
367    }
368
369    private void setKeyDetectorInner(KeyDetector keyDetector) {
370        mKeyDetector = keyDetector;
371        mKeyboard = keyDetector.getKeyboard();
372        mGestureStroke.setGestureSampleLength(
373                mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
374        final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
375        if (newKey != mCurrentKey) {
376            if (mDrawingProxy != null) {
377                setReleasedKeyGraphics(mCurrentKey);
378            }
379            mCurrentKey = newKey;
380        }
381        final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
382        mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
383    }
384
385    public boolean isInSlidingKeyInput() {
386        return mIsInSlidingKeyInput;
387    }
388
389    public Key getKey() {
390        return mCurrentKey;
391    }
392
393    public boolean isModifier() {
394        return mCurrentKey != null && mCurrentKey.isModifier();
395    }
396
397    public Key getKeyOn(int x, int y) {
398        return mKeyDetector.detectHitKey(x, y);
399    }
400
401    private void setReleasedKeyGraphics(Key key) {
402        mDrawingProxy.dismissKeyPreview(this);
403        if (key == null) {
404            return;
405        }
406
407        // Even if the key is disabled, update the key release graphics just in case.
408        updateReleaseKeyGraphics(key);
409
410        if (key.isShift()) {
411            for (final Key shiftKey : mKeyboard.mShiftKeys) {
412                if (shiftKey != key) {
413                    updateReleaseKeyGraphics(shiftKey);
414                }
415            }
416        }
417
418        if (key.altCodeWhileTyping()) {
419            final int altCode = key.mAltCode;
420            final Key altKey = mKeyboard.getKey(altCode);
421            if (altKey != null) {
422                updateReleaseKeyGraphics(altKey);
423            }
424            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
425                if (k != key && k.mAltCode == altCode) {
426                    updateReleaseKeyGraphics(k);
427                }
428            }
429        }
430    }
431
432    private void setPressedKeyGraphics(Key key) {
433        if (key == null) {
434            return;
435        }
436
437        // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
438        final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
439        final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
440        if (!needsToUpdateGraphics) {
441            return;
442        }
443
444        if (!key.noKeyPreview() && !sGestureTracker.isInGesture()) {
445            mDrawingProxy.showKeyPreview(this);
446        }
447        updatePressKeyGraphics(key);
448
449        if (key.isShift()) {
450            for (final Key shiftKey : mKeyboard.mShiftKeys) {
451                if (shiftKey != key) {
452                    updatePressKeyGraphics(shiftKey);
453                }
454            }
455        }
456
457        if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
458            final int altCode = key.mAltCode;
459            final Key altKey = mKeyboard.getKey(altCode);
460            if (altKey != null) {
461                updatePressKeyGraphics(altKey);
462            }
463            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
464                if (k != key && k.mAltCode == altCode) {
465                    updatePressKeyGraphics(k);
466                }
467            }
468        }
469    }
470
471    private void updateReleaseKeyGraphics(Key key) {
472        key.onReleased();
473        mDrawingProxy.invalidateKey(key);
474    }
475
476    private void updatePressKeyGraphics(Key key) {
477        key.onPressed();
478        mDrawingProxy.invalidateKey(key);
479    }
480
481    public int getLastX() {
482        return mLastX;
483    }
484
485    public int getLastY() {
486        return mLastY;
487    }
488
489    public long getDownTime() {
490        return mDownTime;
491    }
492
493    private Key onDownKey(int x, int y, long eventTime) {
494        mDownTime = eventTime;
495        return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
496    }
497
498    private Key onMoveKeyInternal(int x, int y) {
499        mLastX = x;
500        mLastY = y;
501        return mKeyDetector.detectHitKey(x, y);
502    }
503
504    private Key onMoveKey(int x, int y) {
505        return onMoveKeyInternal(x, y);
506    }
507
508    private Key onMoveToNewKey(Key newKey, int x, int y) {
509        mCurrentKey = newKey;
510        mKeyX = x;
511        mKeyY = y;
512        return newKey;
513    }
514
515    public void processMotionEvent(int action, int x, int y, long eventTime,
516            KeyEventHandler handler) {
517        switch (action) {
518        case MotionEvent.ACTION_DOWN:
519        case MotionEvent.ACTION_POINTER_DOWN:
520            onDownEvent(x, y, eventTime, handler);
521            break;
522        case MotionEvent.ACTION_UP:
523        case MotionEvent.ACTION_POINTER_UP:
524            onUpEvent(x, y, eventTime);
525            break;
526        case MotionEvent.ACTION_MOVE:
527            onMoveEvent(x, y, eventTime, null);
528            break;
529        case MotionEvent.ACTION_CANCEL:
530            onCancelEvent(x, y, eventTime);
531            break;
532        }
533    }
534
535    public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
536        if (DEBUG_EVENT)
537            printTouchEvent("onDownEvent:", x, y, eventTime);
538
539        mDrawingProxy = handler.getDrawingProxy();
540        mTimerProxy = handler.getTimerProxy();
541        setKeyboardActionListener(handler.getKeyboardActionListener());
542        setKeyDetectorInner(handler.getKeyDetector());
543        // Naive up-to-down noise filter.
544        final long deltaT = eventTime - mUpTime;
545        if (deltaT < sParams.mTouchNoiseThresholdTime) {
546            final int dx = x - mLastX;
547            final int dy = y - mLastY;
548            final int distanceSquared = (dx * dx + dy * dy);
549            if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
550                if (DEBUG_MODE)
551                    Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
552                            + " distance=" + distanceSquared);
553                if (ProductionFlag.IS_EXPERIMENTAL) {
554                    ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared);
555                }
556                mKeyAlreadyProcessed = true;
557                return;
558            }
559        }
560
561        final PointerTrackerQueue queue = sPointerTrackerQueue;
562        final Key key = getKeyOn(x, y);
563        if (queue != null) {
564            if (key != null && key.isModifier()) {
565                // Before processing a down event of modifier key, all pointers already being
566                // tracked should be released.
567                queue.releaseAllPointers(eventTime);
568            }
569            queue.add(this);
570        }
571        onDownEventInternal(x, y, eventTime);
572        if (queue != null && queue.size() == 1) {
573            sGestureTracker.onDownEvent(this, x, y, eventTime, key);
574        }
575    }
576
577    private void onDownEventInternal(int x, int y, long eventTime) {
578        Key key = onDownKey(x, y, eventTime);
579        // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
580        // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
581        mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
582                || (key != null && key.isModifier())
583                || mKeyDetector.alwaysAllowsSlidingInput();
584        mKeyboardLayoutHasBeenChanged = false;
585        mKeyAlreadyProcessed = false;
586        mIsInSlidingKeyInput = false;
587        mIgnoreModifierKey = false;
588        if (key != null) {
589            // This onPress call may have changed keyboard layout. Those cases are detected at
590            // {@link #setKeyboard}. In those cases, we should update key according to the new
591            // keyboard layout.
592            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
593                key = onDownKey(x, y, eventTime);
594            }
595
596            startRepeatKey(key);
597            startLongPressTimer(key);
598            setPressedKeyGraphics(key);
599        }
600    }
601
602    private void startSlidingKeyInput(Key key) {
603        if (!mIsInSlidingKeyInput) {
604            mIgnoreModifierKey = key.isModifier();
605        }
606        mIsInSlidingKeyInput = true;
607    }
608
609    public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
610        if (DEBUG_MOVE_EVENT)
611            printTouchEvent("onMoveEvent:", x, y, eventTime);
612        if (mKeyAlreadyProcessed)
613            return;
614
615        if (me != null) {
616            // Add historical points to gesture path.
617            final int pointerIndex = me.findPointerIndex(mPointerId);
618            final int historicalSize = me.getHistorySize();
619            for (int h = 0; h < historicalSize; h++) {
620                final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
621                final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
622                final long historicalTime = me.getHistoricalEventTime(h);
623                sGestureTracker.onMoveEvent(this, historicalX, historicalY, historicalTime,
624                        true /* isHistorical */, null);
625            }
626        }
627
628        final int lastX = mLastX;
629        final int lastY = mLastY;
630        final Key oldKey = mCurrentKey;
631        Key key = onMoveKey(x, y);
632
633        // Register move event on gesture tracker.
634        sGestureTracker.onMoveEvent(this, x, y, eventTime, false, key);
635        if (sGestureTracker.isInGesture()) {
636            mIgnoreModifierKey = true;
637            mTimerProxy.cancelLongPressTimer();
638            mIsInSlidingKeyInput = true;
639            mCurrentKey = null;
640            setReleasedKeyGraphics(oldKey);
641        }
642
643        if (key != null) {
644            if (oldKey == null) {
645                // The pointer has been slid in to the new key, but the finger was not on any keys.
646                // In this case, we must call onPress() to notify that the new key is being pressed.
647                // This onPress call may have changed keyboard layout. Those cases are detected at
648                // {@link #setKeyboard}. In those cases, we should update key according to the
649                // new keyboard layout.
650                if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
651                    key = onMoveKey(x, y);
652                }
653                onMoveToNewKey(key, x, y);
654                startLongPressTimer(key);
655                setPressedKeyGraphics(key);
656            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
657                // The pointer has been slid in to the new key from the previous key, we must call
658                // onRelease() first to notify that the previous key has been released, then call
659                // onPress() to notify that the new key is being pressed.
660                setReleasedKeyGraphics(oldKey);
661                callListenerOnRelease(oldKey, oldKey.mCode, true);
662                startSlidingKeyInput(oldKey);
663                mTimerProxy.cancelKeyTimers();
664                startRepeatKey(key);
665                if (mIsAllowedSlidingKeyInput) {
666                    // This onPress call may have changed keyboard layout. Those cases are detected
667                    // at {@link #setKeyboard}. In those cases, we should update key according
668                    // to the new keyboard layout.
669                    if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
670                        key = onMoveKey(x, y);
671                    }
672                    onMoveToNewKey(key, x, y);
673                    startLongPressTimer(key);
674                    setPressedKeyGraphics(key);
675                } else {
676                    // HACK: On some devices, quick successive touches may be translated to sudden
677                    // move by touch panel firmware. This hack detects the case and translates the
678                    // move event to successive up and down events.
679                    final int dx = x - lastX;
680                    final int dy = y - lastY;
681                    final int lastMoveSquared = dx * dx + dy * dy;
682                    if (sNeedsPhantomSuddenMoveEventHack
683                            && lastMoveSquared >= mKeyQuarterWidthSquared) {
684                        if (DEBUG_MODE) {
685                            Log.w(TAG, String.format("onMoveEvent:"
686                                    + " phantom sudden move event is translated to "
687                                    + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
688                        }
689                        if (ProductionFlag.IS_EXPERIMENTAL) {
690                            ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
691                        }
692                        onUpEventInternal(x, y, eventTime);
693                        onDownEventInternal(x, y, eventTime);
694                    } else {
695                        // HACK: If there are currently multiple touches, register the key even if
696                        // the finger slides off the key. This defends against noise from some
697                        // touch panels when there are close multiple touches.
698                        // Caveat: When in chording input mode with a modifier key, we don't use
699                        // this hack.
700                        if (me != null && me.getPointerCount() > 1
701                                && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
702                            onUpEventInternal(x, y, eventTime);
703                        }
704                        mKeyAlreadyProcessed = true;
705                        setReleasedKeyGraphics(oldKey);
706                    }
707                }
708            }
709        } else {
710            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
711                // The pointer has been slid out from the previous key, we must call onRelease() to
712                // notify that the previous key has been released.
713                setReleasedKeyGraphics(oldKey);
714                callListenerOnRelease(oldKey, oldKey.mCode, true);
715                startSlidingKeyInput(oldKey);
716                mTimerProxy.cancelLongPressTimer();
717                if (mIsAllowedSlidingKeyInput) {
718                    onMoveToNewKey(key, x, y);
719                } else {
720                    mKeyAlreadyProcessed = true;
721                }
722            }
723        }
724    }
725
726    public void onUpEvent(int x, int y, long eventTime) {
727        if (DEBUG_EVENT)
728            printTouchEvent("onUpEvent  :", x, y, eventTime);
729
730        final PointerTrackerQueue queue = sPointerTrackerQueue;
731        if (queue != null) {
732            if (!sGestureTracker.isInGesture()) {
733                if (mCurrentKey != null && mCurrentKey.isModifier()) {
734                    // Before processing an up event of modifier key, all pointers already being
735                    // tracked should be released.
736                    queue.releaseAllPointersExcept(this, eventTime);
737                } else {
738                    queue.releaseAllPointersOlderThan(this, eventTime);
739                }
740            }
741            queue.remove(this);
742        }
743        onUpEventInternal(x, y, eventTime);
744    }
745
746    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
747    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
748    // "virtual" up event.
749    public void onPhantomUpEvent(int x, int y, long eventTime) {
750        if (DEBUG_EVENT)
751            printTouchEvent("onPhntEvent:", x, y, eventTime);
752        onUpEventInternal(x, y, eventTime);
753        mKeyAlreadyProcessed = true;
754    }
755
756    private void onUpEventInternal(int x, int y, long eventTime) {
757        mTimerProxy.cancelKeyTimers();
758        mIsInSlidingKeyInput = false;
759        // Release the last pressed key.
760        setReleasedKeyGraphics(mCurrentKey);
761        if (mIsShowingMoreKeysPanel) {
762            mDrawingProxy.dismissMoreKeysPanel();
763            mIsShowingMoreKeysPanel = false;
764        }
765
766        if (sGestureTracker.isInGesture()) {
767            // Register up event on gesture tracker.
768            sGestureTracker.onUpEvent(this, x, y, eventTime);
769            if (!sPointerTrackerQueue.isAnyInSlidingKeyInput()) {
770                // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
771                sGestureTracker.endBatchInput();
772            }
773            if (mCurrentKey != null) {
774                callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
775            }
776            mCurrentKey = null;
777            return;
778        } else {
779            // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
780            sGestureTracker.endBatchInput();
781        }
782
783        if (mKeyAlreadyProcessed)
784            return;
785        if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
786            detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
787        }
788    }
789
790    public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
791        onLongPressed();
792        onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
793        mIsShowingMoreKeysPanel = true;
794        // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
795        sGestureTracker.abortBatchInput();
796    }
797
798    public void onLongPressed() {
799        mKeyAlreadyProcessed = true;
800        setReleasedKeyGraphics(mCurrentKey);
801        final PointerTrackerQueue queue = sPointerTrackerQueue;
802        if (queue != null) {
803            queue.remove(this);
804        }
805    }
806
807    public void onCancelEvent(int x, int y, long eventTime) {
808        if (DEBUG_EVENT)
809            printTouchEvent("onCancelEvt:", x, y, eventTime);
810
811        final PointerTrackerQueue queue = sPointerTrackerQueue;
812        if (queue != null) {
813            queue.releaseAllPointersExcept(this, eventTime);
814            queue.remove(this);
815        }
816        onCancelEventInternal();
817    }
818
819    private void onCancelEventInternal() {
820        mTimerProxy.cancelKeyTimers();
821        setReleasedKeyGraphics(mCurrentKey);
822        mIsInSlidingKeyInput = false;
823        if (mIsShowingMoreKeysPanel) {
824            mDrawingProxy.dismissMoreKeysPanel();
825            mIsShowingMoreKeysPanel = false;
826        }
827    }
828
829    private void startRepeatKey(Key key) {
830        if (key != null && key.isRepeatable() && !sGestureTracker.isInGesture()) {
831            onRegisterKey(key);
832            mTimerProxy.startKeyRepeatTimer(this);
833        }
834    }
835
836    public void onRegisterKey(Key key) {
837        if (key != null) {
838            detectAndSendKey(key, key.mX, key.mY);
839            if (!key.altCodeWhileTyping() && !key.isModifier()) {
840                mTimerProxy.startTypingStateTimer();
841            }
842        }
843    }
844
845    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
846        if (mKeyDetector == null)
847            throw new NullPointerException("keyboard and/or key detector not set");
848        Key curKey = mCurrentKey;
849        if (newKey == curKey) {
850            return false;
851        } else if (curKey != null) {
852            return curKey.squaredDistanceToEdge(x, y)
853                    >= mKeyDetector.getKeyHysteresisDistanceSquared();
854        } else {
855            return true;
856        }
857    }
858
859    private void startLongPressTimer(Key key) {
860        if (key != null && key.isLongPressEnabled() && !sGestureTracker.isInGesture()) {
861            mTimerProxy.startLongPressTimer(this);
862        }
863    }
864
865    private void detectAndSendKey(Key key, int x, int y) {
866        if (key == null) {
867            callListenerOnCancelInput();
868            return;
869        }
870
871        int code = key.mCode;
872        callListenerOnCodeInput(key, code, x, y);
873        callListenerOnRelease(key, code, false);
874    }
875
876    private void printTouchEvent(String title, int x, int y, long eventTime) {
877        final Key key = mKeyDetector.detectHitKey(x, y);
878        final String code = KeyDetector.printableCode(key);
879        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
880                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code));
881    }
882}
883