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