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