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