GestureStrokeRecognitionPoints.java revision 0c5f72e2bf22df48af051827f97ab6052026d531
1f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka/* 2f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Copyright (C) 2012 The Android Open Source Project 3f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 4f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * in compliance with the License. You may obtain a copy of the License at 6f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 7f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0 8f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * 9f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software distributed under the License 10f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * or implied. See the License for the specific language governing permissions and limitations under 12f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka * the License. 13f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka */ 14f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 15f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapackage com.android.inputmethod.keyboard.internal; 16f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 17f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport android.util.FloatMath; 18f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 19f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport com.android.inputmethod.latin.InputPointers; 20f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 21f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapublic class GestureStroke { 2257f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka public static final int DEFAULT_CAPACITY = 128; 2357f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka 24f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private final int mPointerId; 2557f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka // TODO: Replace this {@link InputPointers} with a set of {@link ScalableIntArray}s. 2657f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka private final InputPointers mInputPointers = new InputPointers(DEFAULT_CAPACITY); 27f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private float mLength; 28f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private float mAngle; 291e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private int mIncrementalRecognitionSize; 300c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang private int mLastIncrementalBatchSize; 31f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private long mLastPointTime; 32f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mLastPointX; 33f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mLastPointY; 34f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 35f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mMinGestureLength; 361e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private int mMinGestureLengthWhileInGesture; 37f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private int mMinGestureSampleLength; 38f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 391e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // TODO: Move some of these to resource. 401e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f; 411e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f; 421e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final int MIN_GESTURE_DURATION = 150; // msec 431e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec 44f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f; 45f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec 46f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f); 47f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 48f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static final float DOUBLE_PI = (float)(2 * Math.PI); 49f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 50f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public GestureStroke(int pointerId) { 51f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mPointerId = pointerId; 52f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka reset(); 53f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 54f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 55f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void setGestureSampleLength(final int keyWidth, final int keyHeight) { 561e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? 571e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH); 581e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mMinGestureLengthWhileInGesture = (int)( 591e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE); 60f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT); 61f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 62f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 631e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) { 641e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // The tolerance of the time duration and the stroke length to detect the start of a 651e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka // gesture stroke should be eased when the previous input was a gesture input. 661e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka if (wasInGesture) { 671e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE 681e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka && mLength > mMinGestureLengthWhileInGesture; 691e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka } 70f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; 71f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 72f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 73f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void reset() { 74f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLength = 0; 75f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mAngle = 0; 761e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = 0; 770c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang mLastIncrementalBatchSize = 0; 78f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointTime = 0; 79f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mInputPointers.reset(); 80f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 81f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 82f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private void updateLastPoint(final int x, final int y, final int time) { 83f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointTime = time; 84f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointX = x; 85f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLastPointY = y; 86f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 87f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 88f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { 89f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int size = mInputPointers.getPointerSize(); 90f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (size == 0) { 91f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mInputPointers.addPointer(x, y, mPointerId, time); 92f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (!isHistorical) { 93f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka updateLastPoint(x, y, time); 94f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 95f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return; 96f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 97f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 98f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int[] xCoords = mInputPointers.getXCoordinates(); 99f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int[] yCoords = mInputPointers.getYCoordinates(); 100f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int lastX = xCoords[size - 1]; 101f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int lastY = yCoords[size - 1]; 102f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dist = getDistance(lastX, lastY, x, y); 103f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (dist > mMinGestureSampleLength) { 104f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mInputPointers.addPointer(x, y, mPointerId, time); 105f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mLength += dist; 106f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float angle = getAngle(lastX, lastY, x, y); 107f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (size > 1) { 1081e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka final float curvature = getAngleDiff(angle, mAngle); 109f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { 1101e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka if (size > mIncrementalRecognitionSize) { 1111e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = size; 112f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 113f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 114f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 115f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka mAngle = angle; 116f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 117f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 118f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (!isHistorical) { 119f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int duration = (int)(time - mLastPointTime); 120f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (mLastPointTime != 0 && duration > 0) { 121f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration; 122f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (speed < GESTURE_RECOG_SPEED_THRESHOLD) { 1231e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka mIncrementalRecognitionSize = size; 124f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 125f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 126f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka updateLastPoint(x, y, time); 127f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 128f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 129f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 130f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void appendAllBatchPoints(final InputPointers out) { 1310c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang final int size = mInputPointers.getPointerSize(); 1320c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang out.append(mInputPointers, mLastIncrementalBatchSize, size - mLastIncrementalBatchSize); 1330c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang mLastIncrementalBatchSize = size; 134f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 135f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 136f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka public void appendIncrementalBatchPoints(final InputPointers out) { 1370c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang out.append(mInputPointers, mLastIncrementalBatchSize, 1380c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang mIncrementalRecognitionSize - mLastIncrementalBatchSize); 1390c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang mLastIncrementalBatchSize = mIncrementalRecognitionSize; 140f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 141f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 142f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getDistance(final int p1x, final int p1y, 143f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int p2x, final int p2y) { 144f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dx = p1x - p2x; 145f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float dy = p1y - p2y; 146f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka // TODO: Optimize out this {@link FloatMath#sqrt(float)} call. 147f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return FloatMath.sqrt(dx * dx + dy * dy); 148f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 149f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 150f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { 151f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int dx = p1x - p2x; 152f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final int dy = p1y - p2y; 153f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (dx == 0 && dy == 0) return 0; 154f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return (float)Math.atan2(dy, dx); 155f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 156f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka 157f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka private static float getAngleDiff(final float a1, final float a2) { 158f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka final float diff = Math.abs(a1 - a2); 159f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka if (diff > Math.PI) { 160f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return DOUBLE_PI - diff; 161f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 162f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka return diff; 163f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka } 164f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka} 165