GestureStrokeRecognitionPoints.java revision 7519091f7c15c50a9a1e50d82fa92400335852ec
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
174daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Canvas;
184daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Paint;
194daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport android.graphics.Rect;
20f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport android.util.FloatMath;
21f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
224daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyangimport com.android.inputmethod.latin.Constants;
23f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokaimport com.android.inputmethod.latin.InputPointers;
247519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaokaimport com.android.inputmethod.latin.ResizableIntArray;
25f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
26f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaokapublic class GestureStroke {
2757f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka    public static final int DEFAULT_CAPACITY = 128;
2857f7de0ba664187e13bcea5adff7f5f65eddd823Tadashi G. Takaoka
29f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private final int mPointerId;
307519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
317519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
327519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
33f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private float mLength;
34f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private float mAngle;
351e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private int mIncrementalRecognitionSize;
360c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang    private int mLastIncrementalBatchSize;
37f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private long mLastPointTime;
38f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private int mLastPointX;
39f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private int mLastPointY;
40f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
41f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private int mMinGestureLength;
421e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private int mMinGestureLengthWhileInGesture;
43f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private int mMinGestureSampleLength;
444daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    private final Rect mDrawingRect = new Rect();
45f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
461e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    // TODO: Move some of these to resource.
471e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
481e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
491e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private static final int MIN_GESTURE_DURATION = 150; // msec
501e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
51f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
52f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
53f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
54f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
55f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static final float DOUBLE_PI = (float)(2 * Math.PI);
56f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
574daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
584daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    private static final int DRAWING_GESTURE_FADE_START = 10;
594daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    private static final int DRAWING_GESTURE_FADE_RATE = 6;
604daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang
61f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public GestureStroke(int pointerId) {
62f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mPointerId = pointerId;
63f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        reset();
64f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
65f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
66f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public void setGestureSampleLength(final int keyWidth, final int keyHeight) {
671e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
681e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
691e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        mMinGestureLengthWhileInGesture = (int)(
701e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
71f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
72f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
73f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
741e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka    public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
751e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        // The tolerance of the time duration and the stroke length to detect the start of a
761e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        // gesture stroke should be eased when the previous input was a gesture input.
771e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        if (wasInGesture) {
781e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka            return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
791e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                    && mLength > mMinGestureLengthWhileInGesture;
801e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        }
81f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
82f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
83f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
84f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public void reset() {
85f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mLength = 0;
86f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mAngle = 0;
871e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka        mIncrementalRecognitionSize = 0;
880c5f72e2bf22df48af051827f97ab6052026d531Tom Ouyang        mLastIncrementalBatchSize = 0;
89f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mLastPointTime = 0;
907519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mEventTimes.setLength(0);
917519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mXCoordinates.setLength(0);
927519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mYCoordinates.setLength(0);
934daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        mDrawingRect.setEmpty();
94f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
95f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
96f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private void updateLastPoint(final int x, final int y, final int time) {
97f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mLastPointTime = time;
98f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mLastPointX = x;
99f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        mLastPointY = y;
100f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
101f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
102f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
1037519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        final int size = mEventTimes.getLength();
104f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        if (size == 0) {
1057519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mEventTimes.add(time);
1067519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mXCoordinates.add(x);
1077519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mYCoordinates.add(y);
108f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            if (!isHistorical) {
109f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                updateLastPoint(x, y, time);
110f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            }
111f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            return;
112f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
1137519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka
1147519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        final int lastX = mXCoordinates.get(size - 1);
1157519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        final int lastY = mYCoordinates.get(size - 1);
116f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final float dist = getDistance(lastX, lastY, x, y);
117f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        if (dist > mMinGestureSampleLength) {
1187519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mEventTimes.add(time);
1197519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mXCoordinates.add(x);
1207519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka            mYCoordinates.add(y);
121f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            mLength += dist;
122f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            final float angle = getAngle(lastX, lastY, x, y);
123f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            if (size > 1) {
1241e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                final float curvature = getAngleDiff(angle, mAngle);
125f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) {
1261e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                    if (size > mIncrementalRecognitionSize) {
1271e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                        mIncrementalRecognitionSize = size;
128f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                    }
129f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                }
130f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            }
131f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            mAngle = angle;
132f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
133f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
134f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        if (!isHistorical) {
135f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            final int duration = (int)(time - mLastPointTime);
136f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            if (mLastPointTime != 0 && duration > 0) {
137f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration;
138f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                if (speed < GESTURE_RECOG_SPEED_THRESHOLD) {
1391e6f39a9f994e21b749a1cbae55a3adbfb5640e9Tadashi G. Takaoka                    mIncrementalRecognitionSize = size;
140f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka                }
141f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            }
142f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            updateLastPoint(x, y, time);
143f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
144f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
145f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
146f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public void appendAllBatchPoints(final InputPointers out) {
1477519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        appendBatchPoints(out, mEventTimes.getLength());
148f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
149f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
150f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    public void appendIncrementalBatchPoints(final InputPointers out) {
1517519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        appendBatchPoints(out, mIncrementalRecognitionSize);
1527519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    }
1537519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka
1547519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka    private void appendBatchPoints(final InputPointers out, final int size) {
1557519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates,
1567519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka                mLastIncrementalBatchSize, size - mLastIncrementalBatchSize);
1577519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        mLastIncrementalBatchSize = size;
158f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
159f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
160f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static float getDistance(final int p1x, final int p1y,
161f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            final int p2x, final int p2y) {
162f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final float dx = p1x - p2x;
163f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final float dy = p1y - p2y;
164f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        // TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
165f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        return FloatMath.sqrt(dx * dx + dy * dy);
166f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
167f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
168f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
169f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final int dx = p1x - p2x;
170f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final int dy = p1y - p2y;
171f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        if (dx == 0 && dy == 0) return 0;
172f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        return (float)Math.atan2(dy, dx);
173f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
174f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka
175f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    private static float getAngleDiff(final float a1, final float a2) {
176f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        final float diff = Math.abs(a1 - a2);
177f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        if (diff > Math.PI) {
178f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka            return DOUBLE_PI - diff;
179f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        }
180f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka        return diff;
181f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka    }
1824daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang
1834daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
1844daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        // TODO: These paint parameter interpolation should be tunable, possibly introduce an object
1854daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        // that implements an interface such as Paint getPaint(int step, int strokePoints)
1867519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        final int size = mXCoordinates.getLength();
1877519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        int[] xCoords = mXCoordinates.getPrimitiveArray();
1887519091f7c15c50a9a1e50d82fa92400335852ecTadashi G. Takaoka        int[] yCoords = mYCoordinates.getPrimitiveArray();
1894daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        int alpha = Constants.Color.ALPHA_OPAQUE;
1904daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        for (int i = size - 1; i > 0 && alpha > 0; i--) {
1914daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            paint.setAlpha(alpha);
1924daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            if (size - i > DRAWING_GESTURE_FADE_START) {
1934daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang                alpha -= DRAWING_GESTURE_FADE_RATE;
1944daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            }
1954daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
1964daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            if (i == size - 1) {
1974daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang                canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
1984daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang            }
1994daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        }
2004daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    }
2014daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang
2024daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    public Rect getDrawingRect() {
2034daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang        return mDrawingRect;
2044daf32b6c0358f0273a99b622a259ecdf6b44fa4Tom Ouyang    }
205f39fccbd0fd63647c52e8eabcb60df69f97492b5Tadashi G. Takaoka}
206