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