1f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka/*
2f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Copyright (C) 2012 The Android Open Source Project
3f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
7f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
9f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka *
108aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
15f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka */
16f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
17f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapackage com.android.inputmethod.keyboard.internal;
18f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
1902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaokaimport android.util.Log;
2002a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
219342484e8d573a40f470b6a593df31c602fa4076Ken Wakasaimport com.android.inputmethod.latin.common.Constants;
2236799b2aa2982ec17341cd2c5ed81e608bcee8c6Jean Chalardimport com.android.inputmethod.latin.common.InputPointers;
2336799b2aa2982ec17341cd2c5ed81e608bcee8c6Jean Chalardimport com.android.inputmethod.latin.common.ResizableIntArray;
24f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
25e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka/**
26e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka * This class holds event points to recognize a gesture stroke.
274a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka * TODO: Should be package private class.
28e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka */
294a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaokapublic final class GestureStrokeRecognitionPoints {
30e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka    private static final String TAG = GestureStrokeRecognitionPoints.class.getSimpleName();
3102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private static final boolean DEBUG = false;
3258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private static final boolean DEBUG_SPEED = false;
3302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
34b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    // The height of extra area above the keyboard to draw gesture trails.
35b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    // Proportional to the keyboard height.
36b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    public static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f;
37b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka
38f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private final int mPointerId;
394a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    private final ResizableIntArray mEventTimes = new ResizableIntArray(
404a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
414a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    private final ResizableIntArray mXCoordinates = new ResizableIntArray(
424a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
434a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    private final ResizableIntArray mYCoordinates = new ResizableIntArray(
444a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka            Constants.DEFAULT_GESTURE_POINTS_CAPACITY);
4502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
46e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka    private final GestureStrokeRecognitionParams mRecognitionParams;
4780bcb9963259994cfb6497a19709198414aa860aTadashi G. Takaoka
4858fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mKeyWidth; // pixel
49b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    private int mMinYCoordinate; // pixel
50b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    private int mMaxYCoordinate; // pixel
5158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    // Static threshold for starting gesture detection
5202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private int mDetectFastMoveSpeedThreshold; // pixel /sec
5302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private int mDetectFastMoveTime;
5402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private int mDetectFastMoveX;
5502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private int mDetectFastMoveY;
5658fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    // Dynamic threshold for gesture after fast typing
5758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private boolean mAfterFastTyping;
5858fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mGestureDynamicDistanceThresholdFrom; // pixel
5958fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mGestureDynamicDistanceThresholdTo; // pixel
6058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    // Variables for gesture sampling
6158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mGestureSamplingMinimumDistance; // pixel
6258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private long mLastMajorEventTime;
6358fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mLastMajorEventX;
6458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mLastMajorEventY;
6558fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    // Variables for gesture recognition
6658fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mGestureRecognitionSpeedThreshold; // pixel / sec
6758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mIncrementalRecognitionSize;
6858fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int mLastIncrementalBatchSize;
69f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
7058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private static final int MSEC_PER_SEC = 1000;
71f80f09c7eed430827ae8294a5b0f33d5f21cee60Tadashi G. Takaoka
724a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
73e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka    public GestureStrokeRecognitionPoints(final int pointerId,
74e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka            final GestureStrokeRecognitionParams recognitionParams) {
75f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mPointerId = pointerId;
76e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mRecognitionParams = recognitionParams;
77f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
78f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
794a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
80b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
8102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mKeyWidth = keyWidth;
82b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka        mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO);
832ad7023c548afe829eb87f0ea1be48b533a2ac2bTadashi G. Takaoka        mMaxYCoordinate = keyboardHeight;
841e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
85e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mDetectFastMoveSpeedThreshold = (int)(
86e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                keyWidth * mRecognitionParams.mDetectFastMoveSpeedThreshold);
87e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mGestureDynamicDistanceThresholdFrom = (int)(
88e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                keyWidth * mRecognitionParams.mDynamicDistanceThresholdFrom);
89e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mGestureDynamicDistanceThresholdTo = (int)(
90e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                keyWidth * mRecognitionParams.mDynamicDistanceThresholdTo);
91e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mGestureSamplingMinimumDistance = (int)(
92e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                keyWidth * mRecognitionParams.mSamplingMinimumDistance);
93e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        mGestureRecognitionSpeedThreshold = (int)(
94e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                keyWidth * mRecognitionParams.mRecognitionSpeedThreshold);
9502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (DEBUG) {
9658fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            Log.d(TAG, String.format(
9758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    "[%d] setKeyboardGeometry: keyWidth=%3d tT=%3d >> %3d tD=%3d >> %3d",
9858fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    mPointerId, keyWidth,
99e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                    mRecognitionParams.mDynamicTimeThresholdFrom,
100e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                    mRecognitionParams.mDynamicTimeThresholdTo,
10158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    mGestureDynamicDistanceThresholdFrom,
10258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    mGestureDynamicDistanceThresholdTo));
10302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        }
104f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
105f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
1064a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
107b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka    public int getLength() {
108b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        return mEventTimes.getLength();
109b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka    }
110b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka
1114a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
1124a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    public void addDownEventPoint(final int x, final int y, final int elapsedTimeSinceFirstDown,
113c3fe1425a5941e4801caa681dd53fb742d4489d9Tadashi G. Takaoka            final int elapsedTimeSinceLastTyping) {
11458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        reset();
115c3fe1425a5941e4801caa681dd53fb742d4489d9Tadashi G. Takaoka        if (elapsedTimeSinceLastTyping < mRecognitionParams.mStaticTimeThresholdAfterFastTyping) {
116630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka            mAfterFastTyping = true;
117630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        }
118630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        if (DEBUG) {
11958fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            Log.d(TAG, String.format("[%d] onDownEvent: dT=%3d%s", mPointerId,
120c3fe1425a5941e4801caa681dd53fb742d4489d9Tadashi G. Takaoka                    elapsedTimeSinceLastTyping, mAfterFastTyping ? " afterFastTyping" : ""));
121630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        }
1224a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka        // Call {@link #addEventPoint(int,int,int,boolean)} to record this down event point as a
1234a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka        // major event point.
1244a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka        addEventPoint(x, y, elapsedTimeSinceFirstDown, true /* isMajorEvent */);
125630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka    }
126630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka
12758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int getGestureDynamicDistanceThreshold(final int deltaTime) {
128e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) {
12958fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            return mGestureDynamicDistanceThresholdTo;
130630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        }
131630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        final int decayedThreshold =
13258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                (mGestureDynamicDistanceThresholdFrom - mGestureDynamicDistanceThresholdTo)
133e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration;
13458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        return mGestureDynamicDistanceThresholdFrom - decayedThreshold;
13558fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    }
13658fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka
13758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    private int getGestureDynamicTimeThreshold(final int deltaTime) {
138e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        if (!mAfterFastTyping || deltaTime >= mRecognitionParams.mDynamicThresholdDecayDuration) {
139e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka            return mRecognitionParams.mDynamicTimeThresholdTo;
14058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        }
14158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        final int decayedThreshold =
142e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                (mRecognitionParams.mDynamicTimeThresholdFrom
143e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                        - mRecognitionParams.mDynamicTimeThresholdTo)
144e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka                * deltaTime / mRecognitionParams.mDynamicThresholdDecayDuration;
145e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        return mRecognitionParams.mDynamicTimeThresholdFrom - decayedThreshold;
146630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka    }
147630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka
1484a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
1497a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka    public final boolean isStartOfAGesture() {
1507a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka        if (!hasDetectedFastMove()) {
15102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            return false;
15202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        }
153b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        final int size = getLength();
15402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (size <= 0) {
15502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            return false;
15602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        }
15702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int lastIndex = size - 1;
15802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int deltaTime = mEventTimes.get(lastIndex) - mDetectFastMoveTime;
1597a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka        if (deltaTime < 0) {
1607a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka            return false;
1617a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka        }
16258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        final int deltaDistance = getDistance(
16302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
16402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                mDetectFastMoveX, mDetectFastMoveY);
16558fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        final int distanceThreshold = getGestureDynamicDistanceThreshold(deltaTime);
16658fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        final int timeThreshold = getGestureDynamicTimeThreshold(deltaTime);
16758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka        final boolean isStartOfAGesture = deltaTime >= timeThreshold
16858fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                && deltaDistance >= distanceThreshold;
16902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (DEBUG) {
17058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            Log.d(TAG, String.format("[%d] isStartOfAGesture: dT=%3d tT=%3d dD=%3d tD=%3d%s%s",
17158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    mPointerId, deltaTime, timeThreshold,
17258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    deltaDistance, distanceThreshold,
17358fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    mAfterFastTyping ? " afterFastTyping" : "",
17458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    isStartOfAGesture ? " startOfAGesture" : ""));
17502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        }
17602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        return isStartOfAGesture;
177f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
178f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
1794a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
18072fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka    public void duplicateLastPointWith(final int time) {
181b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        final int lastIndex = getLength() - 1;
18272fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka        if (lastIndex >= 0) {
18372fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka            final int x = mXCoordinates.get(lastIndex);
18472fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka            final int y = mYCoordinates.get(lastIndex);
185915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka            if (DEBUG) {
186915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka                Log.d(TAG, String.format("[%d] duplicateLastPointWith: %d,%d|%d", mPointerId,
187915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka                        x, y, time));
188915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka            }
18972fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka            // TODO: Have appendMajorPoint()
19072fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka            appendPoint(x, y, time);
19172fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka            updateIncrementalRecognitionSize(x, y, time);
19272fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka        }
19372fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka    }
19472fd0968e5227ffc383b1f9d096872ba39cfdce8Tadashi G. Takaoka
195c3fe1425a5941e4801caa681dd53fb742d4489d9Tadashi G. Takaoka    private void reset() {
1961e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        mIncrementalRecognitionSize = 0;
1970c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang        mLastIncrementalBatchSize = 0;
1987519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mEventTimes.setLength(0);
1997519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mXCoordinates.setLength(0);
2007519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mYCoordinates.setLength(0);
20102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mLastMajorEventTime = 0;
20202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mDetectFastMoveTime = 0;
203630d9c95e596c902b80dcb57eb0386e94290406dTadashi G. Takaoka        mAfterFastTyping = false;
20402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    }
20502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
20602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private void appendPoint(final int x, final int y, final int time) {
207b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        final int lastIndex = getLength() - 1;
208915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka        // The point that is created by {@link duplicateLastPointWith(int)} may have later event
209915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka        // time than the next {@link MotionEvent}. To maintain the monotonicity of the event time,
210915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka        // drop the successive point here.
211915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka        if (lastIndex >= 0 && mEventTimes.get(lastIndex) > time) {
212915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka            Log.w(TAG, String.format("[%d] drop stale event: %d,%d|%d last: %d,%d|%d", mPointerId,
213915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka                    x, y, time, mXCoordinates.get(lastIndex), mYCoordinates.get(lastIndex),
214915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka                    mEventTimes.get(lastIndex)));
215915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka            return;
216915f348b35cb66ed9696a51c9250f9b25799fb82Tadashi G. Takaoka        }
21702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mEventTimes.add(time);
21802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mXCoordinates.add(x);
21902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mYCoordinates.add(y);
220f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
221f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
22202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private void updateMajorEvent(final int x, final int y, final int time) {
22302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mLastMajorEventTime = time;
22402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mLastMajorEventX = x;
22502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        mLastMajorEventY = y;
22602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    }
22702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
228c9ba26994b946d35c375cd1cd9a6db2b23b3de7eTadashi G. Takaoka    private final boolean hasDetectedFastMove() {
2297a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka        return mDetectFastMoveTime > 0;
2307a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka    }
2317a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka
23202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private int detectFastMove(final int x, final int y, final int time) {
233b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        final int size = getLength();
23402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int lastIndex = size - 1;
23502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int lastX = mXCoordinates.get(lastIndex);
23602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int lastY = mYCoordinates.get(lastIndex);
23702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int dist = getDistance(lastX, lastY, x, y);
23802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int msecs = time - mEventTimes.get(lastIndex);
23902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (msecs > 0) {
24002a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            final int pixels = getDistance(lastX, lastY, x, y);
24102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            final int pixelsPerSec = pixels * MSEC_PER_SEC;
24258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            if (DEBUG_SPEED) {
24302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
24458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                Log.d(TAG, String.format("[%d] detectFastMove: speed=%5.2f", mPointerId, speed));
24502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            }
24602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            // Equivalent to (pixels / msecs < mStartSpeedThreshold / MSEC_PER_SEC)
2477a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka            if (!hasDetectedFastMove() && pixelsPerSec > mDetectFastMoveSpeedThreshold * msecs) {
24802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                if (DEBUG) {
24958fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    final float speed = (float)pixelsPerSec / msecs / mKeyWidth;
25058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                    Log.d(TAG, String.format(
25158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                            "[%d] detectFastMove: speed=%5.2f T=%3d points=%3d fastMove",
25258fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka                            mPointerId, speed, time, size));
25302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                }
25402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                mDetectFastMoveTime = time;
25502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                mDetectFastMoveX = x;
25602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                mDetectFastMoveY = y;
25702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            }
258f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
25902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        return dist;
26002a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    }
26102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka
262b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka    /**
2634a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka     * Add an event point to this gesture stroke recognition points. Returns true if the event
2644a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka     * point is on the valid gesture area.
2654a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka     * @param x the x-coordinate of the event point
2664a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka     * @param y the y-coordinate of the event point
267b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka     * @param time the elapsed time in millisecond from the first gesture down
268b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka     * @param isMajorEvent false if this is a historical move event
2694a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka     * @return true if the event point is on the valid gesture area
270b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka     */
2714a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
2724a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    public boolean addEventPoint(final int x, final int y, final int time,
273b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka            final boolean isMajorEvent) {
274b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        final int size = getLength();
27502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (size <= 0) {
2764a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka            // The first event of this stroke (a.k.a. down event).
27702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            appendPoint(x, y, time);
27802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            updateMajorEvent(x, y, time);
27902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        } else {
28058fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            final int distance = detectFastMove(x, y, time);
28158fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            if (distance > mGestureSamplingMinimumDistance) {
28202a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka                appendPoint(x, y, time);
28302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            }
284f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
28502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (isMajorEvent) {
28661dcaaf17e4d1f9e941b961559a46823e6e25c99Tadashi G. Takaoka            updateIncrementalRecognitionSize(x, y, time);
28702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            updateMajorEvent(x, y, time);
28861dcaaf17e4d1f9e941b961559a46823e6e25c99Tadashi G. Takaoka        }
289b3f789799a2983a9c97288686f11dfab369243c0Tadashi G. Takaoka        return y >= mMinYCoordinate && y < mMaxYCoordinate;
29061dcaaf17e4d1f9e941b961559a46823e6e25c99Tadashi G. Takaoka    }
29161dcaaf17e4d1f9e941b961559a46823e6e25c99Tadashi G. Takaoka
29261dcaaf17e4d1f9e941b961559a46823e6e25c99Tadashi G. Takaoka    private void updateIncrementalRecognitionSize(final int x, final int y, final int time) {
29302a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int msecs = (int)(time - mLastMajorEventTime);
29402a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (msecs <= 0) {
29502a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka            return;
29602a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        }
29702a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int pixels = getDistance(mLastMajorEventX, mLastMajorEventY, x, y);
29802a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        final int pixelsPerSec = pixels * MSEC_PER_SEC;
29902a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        // Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
30002a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka        if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) {
301b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka            mIncrementalRecognitionSize = getLength();
302f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
30358fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka    }
30458fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka
3054a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
30680bcb9963259994cfb6497a19709198414aa860aTadashi G. Takaoka    public final boolean hasRecognitionTimePast(
30758fe5a421f3334641209300c5bc60c0e6a842220Tadashi G. Takaoka            final long currentTime, final long lastRecognitionTime) {
308e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        return currentTime > lastRecognitionTime + mRecognitionParams.mRecognitionMinimumTime;
309f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
310f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
3114a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
3127a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka    public final void appendAllBatchPoints(final InputPointers out) {
313b2f5d1525093e66faa4a46d6cf10c0144fca2041Tadashi G. Takaoka        appendBatchPoints(out, getLength());
314f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
315f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
3164a4b6d42a79779fe2a1eaf9c251cf98ab6fdccb5Tadashi G. Takaoka    // TODO: Make this package private
3177a17c1fcb52f0249108cfcbd789928320706718aTadashi G. Takaoka    public final void appendIncrementalBatchPoints(final InputPointers out) {
3187519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        appendBatchPoints(out, mIncrementalRecognitionSize);
3197519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    }
3207519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka
3217519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    private void appendBatchPoints(final InputPointers out, final int size) {
3226c3304ea961fd4da0a1da01dc1fac4797c713bccTadashi G. Takaoka        final int length = size - mLastIncrementalBatchSize;
3236c3304ea961fd4da0a1da01dc1fac4797c713bccTadashi G. Takaoka        if (length <= 0) {
3246c3304ea961fd4da0a1da01dc1fac4797c713bccTadashi G. Takaoka            return;
3256c3304ea961fd4da0a1da01dc1fac4797c713bccTadashi G. Takaoka        }
3267519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
3276c3304ea961fd4da0a1da01dc1fac4797c713bccTadashi G. Takaoka                mLastIncrementalBatchSize, length);
3287519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mLastIncrementalBatchSize = size;
329f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
330f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
33102a67200fc25d1be9dfbc35e3bb4b59bef28f386Tadashi G. Takaoka    private static int getDistance(final int x1, final int y1, final int x2, final int y2) {
332e2a6253cb581f9ab70cfb723d32b14f9ac7d2ab7Tadashi G. Takaoka        return (int)Math.hypot(x1 - x2, y1 - y2);
333f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
334f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka}
335