PointerTracker.java revision 0cc425bd9c476d3cb6708554282a3242019eb317
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 implements PointerTrackerQueue.ElementActions {
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            // Keep {@link #mCurrentKey} that comes from previous keyboard.
412        }
413        final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
414        mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
415    }
416
417    @Override
418    public boolean isInSlidingKeyInput() {
419        return mIsInSlidingKeyInput;
420    }
421
422    public Key getKey() {
423        return mCurrentKey;
424    }
425
426    @Override
427    public boolean isModifier() {
428        return mCurrentKey != null && mCurrentKey.isModifier();
429    }
430
431    public Key getKeyOn(int x, int y) {
432        return mKeyDetector.detectHitKey(x, y);
433    }
434
435    private void setReleasedKeyGraphics(Key key) {
436        mDrawingProxy.dismissKeyPreview(this);
437        if (key == null) {
438            return;
439        }
440
441        // Even if the key is disabled, update the key release graphics just in case.
442        updateReleaseKeyGraphics(key);
443
444        if (key.isShift()) {
445            for (final Key shiftKey : mKeyboard.mShiftKeys) {
446                if (shiftKey != key) {
447                    updateReleaseKeyGraphics(shiftKey);
448                }
449            }
450        }
451
452        if (key.altCodeWhileTyping()) {
453            final int altCode = key.mAltCode;
454            final Key altKey = mKeyboard.getKey(altCode);
455            if (altKey != null) {
456                updateReleaseKeyGraphics(altKey);
457            }
458            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
459                if (k != key && k.mAltCode == altCode) {
460                    updateReleaseKeyGraphics(k);
461                }
462            }
463        }
464    }
465
466    private void setPressedKeyGraphics(Key key) {
467        if (key == null) {
468            return;
469        }
470
471        // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state.
472        final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
473        final boolean needsToUpdateGraphics = key.isEnabled() || altersCode;
474        if (!needsToUpdateGraphics) {
475            return;
476        }
477
478        if (!key.noKeyPreview() && !mInGesture) {
479            mDrawingProxy.showKeyPreview(this);
480        }
481        updatePressKeyGraphics(key);
482
483        if (key.isShift()) {
484            for (final Key shiftKey : mKeyboard.mShiftKeys) {
485                if (shiftKey != key) {
486                    updatePressKeyGraphics(shiftKey);
487                }
488            }
489        }
490
491        if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) {
492            final int altCode = key.mAltCode;
493            final Key altKey = mKeyboard.getKey(altCode);
494            if (altKey != null) {
495                updatePressKeyGraphics(altKey);
496            }
497            for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) {
498                if (k != key && k.mAltCode == altCode) {
499                    updatePressKeyGraphics(k);
500                }
501            }
502        }
503    }
504
505    private void updateReleaseKeyGraphics(Key key) {
506        key.onReleased();
507        mDrawingProxy.invalidateKey(key);
508    }
509
510    private void updatePressKeyGraphics(Key key) {
511        key.onPressed();
512        mDrawingProxy.invalidateKey(key);
513    }
514
515    public void drawGestureTrail(Canvas canvas, Paint paint) {
516        if (mInGesture) {
517            mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
518        }
519    }
520
521    public int getLastX() {
522        return mLastX;
523    }
524
525    public int getLastY() {
526        return mLastY;
527    }
528
529    public long getDownTime() {
530        return mDownTime;
531    }
532
533    private Key onDownKey(int x, int y, long eventTime) {
534        mDownTime = eventTime;
535        return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
536    }
537
538    private Key onMoveKeyInternal(int x, int y) {
539        mLastX = x;
540        mLastY = y;
541        return mKeyDetector.detectHitKey(x, y);
542    }
543
544    private Key onMoveKey(int x, int y) {
545        return onMoveKeyInternal(x, y);
546    }
547
548    private Key onMoveToNewKey(Key newKey, int x, int y) {
549        mCurrentKey = newKey;
550        mKeyX = x;
551        mKeyY = y;
552        return newKey;
553    }
554
555    private void startBatchInput() {
556        if (DEBUG_LISTENER) {
557            Log.d(TAG, "onStartBatchInput");
558        }
559        mInGesture = true;
560        mListener.onStartBatchInput();
561    }
562
563    private void updateBatchInput(InputPointers batchPoints) {
564        if (DEBUG_LISTENER) {
565            Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
566        }
567        mListener.onUpdateBatchInput(batchPoints);
568    }
569
570    private void endBatchInput(InputPointers batchPoints) {
571        if (DEBUG_LISTENER) {
572            Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize());
573        }
574        mListener.onEndBatchInput(batchPoints);
575        clearBatchInputRecognitionStateOfThisPointerTracker();
576        clearBatchInputPointsOfAllPointerTrackers();
577        sWasInGesture = true;
578    }
579
580    private void abortBatchInput() {
581        clearBatchInputRecognitionStateOfThisPointerTracker();
582        clearBatchInputPointsOfAllPointerTrackers();
583    }
584
585    private void clearBatchInputRecognitionStateOfThisPointerTracker() {
586        mIsPossibleGesture = false;
587        mInGesture = false;
588        mLastRecognitionPointSize = 0;
589        mLastRecognitionTime = 0;
590    }
591
592    private boolean updateBatchInputRecognitionState(long eventTime, int size) {
593        if (size > mLastRecognitionPointSize
594                && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) {
595            mLastRecognitionPointSize = size;
596            mLastRecognitionTime = eventTime;
597            return true;
598        }
599        return false;
600    }
601
602    public void processMotionEvent(int action, int x, int y, long eventTime,
603            KeyEventHandler handler) {
604        switch (action) {
605        case MotionEvent.ACTION_DOWN:
606        case MotionEvent.ACTION_POINTER_DOWN:
607            onDownEvent(x, y, eventTime, handler);
608            break;
609        case MotionEvent.ACTION_UP:
610        case MotionEvent.ACTION_POINTER_UP:
611            onUpEvent(x, y, eventTime);
612            break;
613        case MotionEvent.ACTION_MOVE:
614            onMoveEvent(x, y, eventTime, null);
615            break;
616        case MotionEvent.ACTION_CANCEL:
617            onCancelEvent(x, y, eventTime);
618            break;
619        }
620    }
621
622    public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) {
623        if (DEBUG_EVENT)
624            printTouchEvent("onDownEvent:", x, y, eventTime);
625
626        mDrawingProxy = handler.getDrawingProxy();
627        mTimerProxy = handler.getTimerProxy();
628        setKeyboardActionListener(handler.getKeyboardActionListener());
629        setKeyDetectorInner(handler.getKeyDetector());
630        // Naive up-to-down noise filter.
631        final long deltaT = eventTime - mUpTime;
632        if (deltaT < sParams.mTouchNoiseThresholdTime) {
633            final int dx = x - mLastX;
634            final int dy = y - mLastY;
635            final int distanceSquared = (dx * dx + dy * dy);
636            if (distanceSquared < sTouchNoiseThresholdDistanceSquared) {
637                if (DEBUG_MODE)
638                    Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
639                            + " distance=" + distanceSquared);
640                if (ProductionFlag.IS_EXPERIMENTAL) {
641                    ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared);
642                }
643                mKeyAlreadyProcessed = true;
644                return;
645            }
646        }
647
648        final PointerTrackerQueue queue = sPointerTrackerQueue;
649        final Key key = getKeyOn(x, y);
650        if (queue != null) {
651            if (key != null && key.isModifier()) {
652                // Before processing a down event of modifier key, all pointers already being
653                // tracked should be released.
654                queue.releaseAllPointers(eventTime);
655            }
656            queue.add(this);
657        }
658        onDownEventInternal(x, y, eventTime);
659        if (queue != null && queue.size() == 1) {
660            mIsPossibleGesture = false;
661            // A gesture should start only from the letter key.
662            if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel
663                    && key != null && Keyboard.isLetterCode(key.mCode)) {
664                mIsPossibleGesture = true;
665                // TODO: pointer times should be relative to first down even in entire batch input
666                // instead of resetting to 0 for each new down event.
667                mGestureStroke.addPoint(x, y, 0, false);
668            }
669        }
670    }
671
672    private void onDownEventInternal(int x, int y, long eventTime) {
673        Key key = onDownKey(x, y, eventTime);
674        // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
675        // from modifier key, or 3) this pointer's KeyDetector always allows sliding input.
676        mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled
677                || (key != null && key.isModifier())
678                || mKeyDetector.alwaysAllowsSlidingInput();
679        mKeyboardLayoutHasBeenChanged = false;
680        mKeyAlreadyProcessed = false;
681        mIsInSlidingKeyInput = false;
682        mIgnoreModifierKey = false;
683        if (key != null) {
684            // This onPress call may have changed keyboard layout. Those cases are detected at
685            // {@link #setKeyboard}. In those cases, we should update key according to the new
686            // keyboard layout.
687            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
688                key = onDownKey(x, y, eventTime);
689            }
690
691            startRepeatKey(key);
692            startLongPressTimer(key);
693            setPressedKeyGraphics(key);
694        }
695    }
696
697    private void startSlidingKeyInput(Key key) {
698        if (!mIsInSlidingKeyInput) {
699            mIgnoreModifierKey = key.isModifier();
700        }
701        mIsInSlidingKeyInput = true;
702    }
703
704    private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
705            boolean isHistorical, Key key) {
706        final int gestureTime = (int)(eventTime - tracker.getDownTime());
707        if (sShouldHandleGesture && mIsPossibleGesture) {
708            final GestureStroke stroke = mGestureStroke;
709            stroke.addPoint(x, y, gestureTime, isHistorical);
710            if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) {
711                startBatchInput();
712            }
713        }
714
715        if (key != null && mInGesture) {
716            final InputPointers batchPoints = getIncrementalBatchPoints();
717            mDrawingProxy.showGestureTrail(this);
718            if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
719                updateBatchInput(batchPoints);
720            }
721        }
722    }
723
724    public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) {
725        if (DEBUG_MOVE_EVENT)
726            printTouchEvent("onMoveEvent:", x, y, eventTime);
727        if (mKeyAlreadyProcessed)
728            return;
729
730        if (me != null) {
731            // Add historical points to gesture path.
732            final int pointerIndex = me.findPointerIndex(mPointerId);
733            final int historicalSize = me.getHistorySize();
734            for (int h = 0; h < historicalSize; h++) {
735                final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
736                final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
737                final long historicalTime = me.getHistoricalEventTime(h);
738                onGestureMoveEvent(this, historicalX, historicalY, historicalTime,
739                        true /* isHistorical */, null);
740            }
741        }
742
743        final int lastX = mLastX;
744        final int lastY = mLastY;
745        final Key oldKey = mCurrentKey;
746        Key key = onMoveKey(x, y);
747
748        // Register move event on gesture tracker.
749        onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key);
750        if (mInGesture) {
751            mIgnoreModifierKey = true;
752            mTimerProxy.cancelLongPressTimer();
753            mIsInSlidingKeyInput = true;
754            mCurrentKey = null;
755            setReleasedKeyGraphics(oldKey);
756        }
757
758        if (key != null) {
759            if (oldKey == null) {
760                // The pointer has been slid in to the new key, but the finger was not on any keys.
761                // In this case, we must call onPress() to notify that the new key is being pressed.
762                // This onPress call may have changed keyboard layout. Those cases are detected at
763                // {@link #setKeyboard}. In those cases, we should update key according to the
764                // new keyboard layout.
765                if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
766                    key = onMoveKey(x, y);
767                }
768                onMoveToNewKey(key, x, y);
769                startLongPressTimer(key);
770                setPressedKeyGraphics(key);
771            } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
772                // The pointer has been slid in to the new key from the previous key, we must call
773                // onRelease() first to notify that the previous key has been released, then call
774                // onPress() to notify that the new key is being pressed.
775                setReleasedKeyGraphics(oldKey);
776                callListenerOnRelease(oldKey, oldKey.mCode, true);
777                startSlidingKeyInput(oldKey);
778                mTimerProxy.cancelKeyTimers();
779                startRepeatKey(key);
780                if (mIsAllowedSlidingKeyInput) {
781                    // This onPress call may have changed keyboard layout. Those cases are detected
782                    // at {@link #setKeyboard}. In those cases, we should update key according
783                    // to the new keyboard layout.
784                    if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
785                        key = onMoveKey(x, y);
786                    }
787                    onMoveToNewKey(key, x, y);
788                    startLongPressTimer(key);
789                    setPressedKeyGraphics(key);
790                } else {
791                    // HACK: On some devices, quick successive touches may be translated to sudden
792                    // move by touch panel firmware. This hack detects the case and translates the
793                    // move event to successive up and down events.
794                    final int dx = x - lastX;
795                    final int dy = y - lastY;
796                    final int lastMoveSquared = dx * dx + dy * dy;
797                    // TODO: Should find a way to balance gesture detection and this hack.
798                    if (sNeedsPhantomSuddenMoveEventHack
799                            && lastMoveSquared >= mKeyQuarterWidthSquared
800                            && !mIsPossibleGesture) {
801                        if (DEBUG_MODE) {
802                            Log.w(TAG, String.format("onMoveEvent:"
803                                    + " phantom sudden move event is translated to "
804                                    + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
805                        }
806                        // TODO: This should be moved to outside of this nested if-clause?
807                        if (ProductionFlag.IS_EXPERIMENTAL) {
808                            ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
809                        }
810                        onUpEventInternal();
811                        onDownEventInternal(x, y, eventTime);
812                    } else {
813                        // HACK: If there are currently multiple touches, register the key even if
814                        // the finger slides off the key. This defends against noise from some
815                        // touch panels when there are close multiple touches.
816                        // Caveat: When in chording input mode with a modifier key, we don't use
817                        // this hack.
818                        if (me != null && me.getPointerCount() > 1
819                                && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
820                            onUpEventInternal();
821                        }
822                        if (!mIsPossibleGesture) {
823                            mKeyAlreadyProcessed = true;
824                        }
825                        setReleasedKeyGraphics(oldKey);
826                    }
827                }
828            }
829        } else {
830            if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) {
831                // The pointer has been slid out from the previous key, we must call onRelease() to
832                // notify that the previous key has been released.
833                setReleasedKeyGraphics(oldKey);
834                callListenerOnRelease(oldKey, oldKey.mCode, true);
835                startSlidingKeyInput(oldKey);
836                mTimerProxy.cancelLongPressTimer();
837                if (mIsAllowedSlidingKeyInput) {
838                    onMoveToNewKey(key, x, y);
839                } else {
840                    if (!mIsPossibleGesture) {
841                        mKeyAlreadyProcessed = true;
842                    }
843                }
844            }
845        }
846    }
847
848    public void onUpEvent(int x, int y, long eventTime) {
849        if (DEBUG_EVENT)
850            printTouchEvent("onUpEvent  :", x, y, eventTime);
851
852        final PointerTrackerQueue queue = sPointerTrackerQueue;
853        if (queue != null) {
854            if (!mInGesture) {
855                if (mCurrentKey != null && mCurrentKey.isModifier()) {
856                    // Before processing an up event of modifier key, all pointers already being
857                    // tracked should be released.
858                    queue.releaseAllPointersExcept(this, eventTime);
859                } else {
860                    queue.releaseAllPointersOlderThan(this, eventTime);
861                }
862            }
863            queue.remove(this);
864        }
865        onUpEventInternal();
866    }
867
868    // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
869    // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
870    // "virtual" up event.
871    @Override
872    public void onPhantomUpEvent(long eventTime) {
873        if (DEBUG_EVENT)
874            printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime);
875        onUpEventInternal();
876        mKeyAlreadyProcessed = true;
877    }
878
879    private void onUpEventInternal() {
880        mTimerProxy.cancelKeyTimers();
881        mIsInSlidingKeyInput = false;
882        mIsPossibleGesture = false;
883        // Release the last pressed key.
884        setReleasedKeyGraphics(mCurrentKey);
885        if (mIsShowingMoreKeysPanel) {
886            mDrawingProxy.dismissMoreKeysPanel();
887            mIsShowingMoreKeysPanel = false;
888        }
889
890        if (mInGesture) {
891            // Register up event on gesture tracker.
892            // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding,
893            // and/or tapping mode?
894            endBatchInput(getAllBatchPoints());
895            if (mCurrentKey != null) {
896                callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
897                mCurrentKey = null;
898            }
899            mDrawingProxy.showGestureTrail(this);
900            return;
901        }
902        // This event will be recognized as a regular code input. Clear unused batch points so they
903        // are not mistakenly included in the next batch event.
904        clearBatchInputPointsOfAllPointerTrackers();
905        if (mKeyAlreadyProcessed)
906            return;
907        if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
908            detectAndSendKey(mCurrentKey, mKeyX, mKeyY);
909        }
910    }
911
912    public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) {
913        abortBatchInput();
914        onLongPressed();
915        mIsShowingMoreKeysPanel = true;
916        onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
917    }
918
919    public void onLongPressed() {
920        mKeyAlreadyProcessed = true;
921        setReleasedKeyGraphics(mCurrentKey);
922        final PointerTrackerQueue queue = sPointerTrackerQueue;
923        if (queue != null) {
924            queue.remove(this);
925        }
926    }
927
928    public void onCancelEvent(int x, int y, long eventTime) {
929        if (DEBUG_EVENT)
930            printTouchEvent("onCancelEvt:", x, y, eventTime);
931
932        final PointerTrackerQueue queue = sPointerTrackerQueue;
933        if (queue != null) {
934            queue.releaseAllPointersExcept(this, eventTime);
935            queue.remove(this);
936        }
937        onCancelEventInternal();
938    }
939
940    private void onCancelEventInternal() {
941        mTimerProxy.cancelKeyTimers();
942        setReleasedKeyGraphics(mCurrentKey);
943        mIsInSlidingKeyInput = false;
944        if (mIsShowingMoreKeysPanel) {
945            mDrawingProxy.dismissMoreKeysPanel();
946            mIsShowingMoreKeysPanel = false;
947        }
948    }
949
950    private void startRepeatKey(Key key) {
951        if (key != null && key.isRepeatable() && !mInGesture) {
952            onRegisterKey(key);
953            mTimerProxy.startKeyRepeatTimer(this);
954        }
955    }
956
957    public void onRegisterKey(Key key) {
958        if (key != null) {
959            detectAndSendKey(key, key.mX, key.mY);
960            mTimerProxy.startTypingStateTimer(key);
961        }
962    }
963
964    private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) {
965        if (mKeyDetector == null)
966            throw new NullPointerException("keyboard and/or key detector not set");
967        Key curKey = mCurrentKey;
968        if (newKey == curKey) {
969            return false;
970        } else if (curKey != null) {
971            return curKey.squaredDistanceToEdge(x, y)
972                    >= mKeyDetector.getKeyHysteresisDistanceSquared();
973        } else {
974            return true;
975        }
976    }
977
978    private void startLongPressTimer(Key key) {
979        if (key != null && key.isLongPressEnabled() && !mInGesture) {
980            mTimerProxy.startLongPressTimer(this);
981        }
982    }
983
984    private void detectAndSendKey(Key key, int x, int y) {
985        if (key == null) {
986            callListenerOnCancelInput();
987            return;
988        }
989
990        int code = key.mCode;
991        callListenerOnCodeInput(key, code, x, y);
992        callListenerOnRelease(key, code, false);
993        sWasInGesture = false;
994    }
995
996    private void printTouchEvent(String title, int x, int y, long eventTime) {
997        final Key key = mKeyDetector.detectHitKey(x, y);
998        final String code = KeyDetector.printableCode(key);
999        Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title,
1000                (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code));
1001    }
1002}
1003