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