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