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