blob: 56f4ae2b583255ba8665091f9d34a51ebb507814 [file] [log] [blame]
Phil Weavera6b64f52015-12-04 15:21:35 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accessibilityservice;
18
19import android.annotation.IntRange;
20import android.annotation.NonNull;
Phil Weavera6b64f52015-12-04 15:21:35 -080021import android.graphics.Path;
22import android.graphics.PathMeasure;
23import android.graphics.RectF;
Phil Weavera8918f22016-08-05 11:23:50 -070024import android.os.Parcel;
25import android.os.Parcelable;
Phil Weavera6b64f52015-12-04 15:21:35 -080026
Phil Weaverbe2922f2017-04-28 14:58:35 -070027import com.android.internal.util.Preconditions;
28
Phil Weavera6b64f52015-12-04 15:21:35 -080029import java.util.ArrayList;
Phil Weavera6b64f52015-12-04 15:21:35 -080030import java.util.List;
31
32/**
33 * Accessibility services with the
34 * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
35 * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
Phil Weaver83138812016-03-29 18:15:28 -070036 * Gestures are immutable once built.
Phil Weavera6b64f52015-12-04 15:21:35 -080037 * <p>
38 * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
39 */
40public final class GestureDescription {
41 /** Gestures may contain no more than this many strokes */
Phil Weaver4503fcf2016-03-08 16:29:44 -080042 private static final int MAX_STROKE_COUNT = 10;
Phil Weavera6b64f52015-12-04 15:21:35 -080043
44 /**
45 * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
46 */
Phil Weaver4503fcf2016-03-08 16:29:44 -080047 private static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
Phil Weavera6b64f52015-12-04 15:21:35 -080048
49 private final List<StrokeDescription> mStrokes = new ArrayList<>();
50 private final float[] mTempPos = new float[2];
51
52 /**
Phil Weaver4503fcf2016-03-08 16:29:44 -080053 * Get the upper limit for the number of strokes a gesture may contain.
Phil Weavera6b64f52015-12-04 15:21:35 -080054 *
Phil Weaver4503fcf2016-03-08 16:29:44 -080055 * @return The maximum number of strokes.
Phil Weavera6b64f52015-12-04 15:21:35 -080056 */
Phil Weaver4503fcf2016-03-08 16:29:44 -080057 public static int getMaxStrokeCount() {
58 return MAX_STROKE_COUNT;
Phil Weavera6b64f52015-12-04 15:21:35 -080059 }
60
61 /**
Phil Weaver4503fcf2016-03-08 16:29:44 -080062 * Get the upper limit on a gesture's duration.
Phil Weavera6b64f52015-12-04 15:21:35 -080063 *
Phil Weaver4503fcf2016-03-08 16:29:44 -080064 * @return The maximum duration in milliseconds.
Phil Weavera6b64f52015-12-04 15:21:35 -080065 */
Phil Weaver4503fcf2016-03-08 16:29:44 -080066 public static long getMaxGestureDuration() {
67 return MAX_GESTURE_DURATION_MS;
Phil Weavera6b64f52015-12-04 15:21:35 -080068 }
69
70 private GestureDescription() {}
71
72 private GestureDescription(List<StrokeDescription> strokes) {
73 mStrokes.addAll(strokes);
74 }
75
Phil Weavera6b64f52015-12-04 15:21:35 -080076 /**
77 * Get the number of stroke in the gesture.
78 *
79 * @return the number of strokes in this gesture
80 */
81 public int getStrokeCount() {
82 return mStrokes.size();
83 }
84
85 /**
86 * Read a stroke from the gesture
87 *
88 * @param index the index of the stroke
89 *
90 * @return A description of the stroke.
91 */
92 public StrokeDescription getStroke(@IntRange(from = 0) int index) {
93 return mStrokes.get(index);
94 }
95
96 /**
97 * Return the smallest key point (where a path starts or ends) that is at least a specified
98 * offset
99 * @param offset the minimum start time
100 * @return The next key time that is at least the offset or -1 if one can't be found
101 */
102 private long getNextKeyPointAtLeast(long offset) {
103 long nextKeyPoint = Long.MAX_VALUE;
104 for (int i = 0; i < mStrokes.size(); i++) {
105 long thisStartTime = mStrokes.get(i).mStartTime;
106 if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
107 nextKeyPoint = thisStartTime;
108 }
109 long thisEndTime = mStrokes.get(i).mEndTime;
110 if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
111 nextKeyPoint = thisEndTime;
112 }
113 }
114 return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
115 }
116
117 /**
118 * Get the points that correspond to a particular moment in time.
119 * @param time The time of interest
120 * @param touchPoints An array to hold the current touch points. Must be preallocated to at
121 * least the number of paths in the gesture to prevent going out of bounds
122 * @return The number of points found, and thus the number of elements set in each array
123 */
124 private int getPointsForTime(long time, TouchPoint[] touchPoints) {
125 int numPointsFound = 0;
126 for (int i = 0; i < mStrokes.size(); i++) {
127 StrokeDescription strokeDescription = mStrokes.get(i);
128 if (strokeDescription.hasPointForTime(time)) {
Phil Weaver2f165942016-09-21 11:18:05 -0700129 touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
130 touchPoints[numPointsFound].mContinuedStrokeId =
131 strokeDescription.getContinuedStrokeId();
132 touchPoints[numPointsFound].mIsStartOfPath =
133 (strokeDescription.getContinuedStrokeId() < 0)
134 && (time == strokeDescription.mStartTime);
Phil Weaverbe2922f2017-04-28 14:58:35 -0700135 touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.willContinue()
Phil Weaver2f165942016-09-21 11:18:05 -0700136 && (time == strokeDescription.mEndTime);
Phil Weavera6b64f52015-12-04 15:21:35 -0800137 strokeDescription.getPosForTime(time, mTempPos);
138 touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
139 touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
140 numPointsFound++;
141 }
142 }
143 return numPointsFound;
144 }
145
146 // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
147 // counts against total duration
148 private static long getTotalDuration(List<StrokeDescription> paths) {
149 long latestEnd = Long.MIN_VALUE;
150 for (int i = 0; i < paths.size(); i++) {
151 StrokeDescription path = paths.get(i);
152 latestEnd = Math.max(latestEnd, path.mEndTime);
153 }
154 return Math.max(latestEnd, 0);
155 }
156
157 /**
158 * Builder for a {@code GestureDescription}
159 */
160 public static class Builder {
161
162 private final List<StrokeDescription> mStrokes = new ArrayList<>();
163
164 /**
Phil Weaveraa866972016-04-25 17:26:53 -0700165 * Add a stroke to the gesture description. Up to
166 * {@link GestureDescription#getMaxStrokeCount()} paths may be
167 * added to a gesture, and the total gesture duration (earliest path start time to latest
168 * path end time) may not exceed {@link GestureDescription#getMaxGestureDuration()}.
Phil Weavera6b64f52015-12-04 15:21:35 -0800169 *
170 * @param strokeDescription the stroke to add.
171 *
172 * @return this
173 */
174 public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
175 if (mStrokes.size() >= MAX_STROKE_COUNT) {
Phil Weaver4503fcf2016-03-08 16:29:44 -0800176 throw new IllegalStateException(
177 "Attempting to add too many strokes to a gesture");
Phil Weavera6b64f52015-12-04 15:21:35 -0800178 }
179
180 mStrokes.add(strokeDescription);
181
182 if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
183 mStrokes.remove(strokeDescription);
Phil Weaver4503fcf2016-03-08 16:29:44 -0800184 throw new IllegalStateException(
185 "Gesture would exceed maximum duration with new stroke");
Phil Weavera6b64f52015-12-04 15:21:35 -0800186 }
187 return this;
188 }
189
190 public GestureDescription build() {
191 if (mStrokes.size() == 0) {
Phil Weaver4503fcf2016-03-08 16:29:44 -0800192 throw new IllegalStateException("Gestures must have at least one stroke");
Phil Weavera6b64f52015-12-04 15:21:35 -0800193 }
194 return new GestureDescription(mStrokes);
195 }
196 }
197
198 /**
199 * Immutable description of stroke that can be part of a gesture.
200 */
201 public static class StrokeDescription {
Phil Weaverbe2922f2017-04-28 14:58:35 -0700202 private static final int INVALID_STROKE_ID = -1;
Phil Weaver2f165942016-09-21 11:18:05 -0700203
204 static int sIdCounter;
205
Phil Weavera6b64f52015-12-04 15:21:35 -0800206 Path mPath;
207 long mStartTime;
208 long mEndTime;
209 private float mTimeToLengthConversion;
210 private PathMeasure mPathMeasure;
Phil Weaveraa866972016-04-25 17:26:53 -0700211 // The tap location is only set for zero-length paths
212 float[] mTapLocation;
Phil Weaver2f165942016-09-21 11:18:05 -0700213 int mId;
214 boolean mContinued;
Phil Weaverbe2922f2017-04-28 14:58:35 -0700215 int mContinuedStrokeId = INVALID_STROKE_ID;
Phil Weavera6b64f52015-12-04 15:21:35 -0800216
217 /**
Phil Weaveraa866972016-04-25 17:26:53 -0700218 * @param path The path to follow. Must have exactly one contour. The bounds of the path
219 * must not be negative. The path must not be empty. If the path has zero length
220 * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
Phil Weavera6b64f52015-12-04 15:21:35 -0800221 * @param startTime The time, in milliseconds, from the time the gesture starts to the
222 * time the stroke should start. Must not be negative.
223 * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
Phil Weaverbe2922f2017-04-28 14:58:35 -0700224 * Must be positive.
Phil Weavera6b64f52015-12-04 15:21:35 -0800225 */
226 public StrokeDescription(@NonNull Path path,
Phil Weaver4503fcf2016-03-08 16:29:44 -0800227 @IntRange(from = 0) long startTime,
228 @IntRange(from = 0) long duration) {
Phil Weaverbe2922f2017-04-28 14:58:35 -0700229 this(path, startTime, duration, false);
Phil Weaver2f165942016-09-21 11:18:05 -0700230 }
231
232 /**
233 * @param path The path to follow. Must have exactly one contour. The bounds of the path
234 * must not be negative. The path must not be empty. If the path has zero length
235 * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
236 * @param startTime The time, in milliseconds, from the time the gesture starts to the
237 * time the stroke should start. Must not be negative.
238 * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
239 * Must be positive.
Phil Weaverbe2922f2017-04-28 14:58:35 -0700240 * @param willContinue {@code true} if this stroke will be continued by one in the
Phil Weaver2f165942016-09-21 11:18:05 -0700241 * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
242 * the gesture completes.
243 */
244 public StrokeDescription(@NonNull Path path,
245 @IntRange(from = 0) long startTime,
246 @IntRange(from = 0) long duration,
Phil Weaverbe2922f2017-04-28 14:58:35 -0700247 boolean willContinue) {
248 mContinued = willContinue;
249 Preconditions.checkArgument(duration > 0, "Duration must be positive");
250 Preconditions.checkArgument(startTime >= 0, "Start time must not be negative");
251 Preconditions.checkArgument(!path.isEmpty(), "Path is empty");
Phil Weavera6b64f52015-12-04 15:21:35 -0800252 RectF bounds = new RectF();
253 path.computeBounds(bounds, false /* unused */);
Phil Weaverbe2922f2017-04-28 14:58:35 -0700254 Preconditions.checkArgument((bounds.bottom >= 0) && (bounds.top >= 0)
255 && (bounds.right >= 0) && (bounds.left >= 0),
256 "Path bounds must not be negative");
Phil Weavera6b64f52015-12-04 15:21:35 -0800257 mPath = new Path(path);
258 mPathMeasure = new PathMeasure(path, false);
259 if (mPathMeasure.getLength() == 0) {
Phil Weaveraa866972016-04-25 17:26:53 -0700260 // Treat zero-length paths as taps
261 Path tempPath = new Path(path);
262 tempPath.lineTo(-1, -1);
263 mTapLocation = new float[2];
264 PathMeasure pathMeasure = new PathMeasure(tempPath, false);
265 pathMeasure.getPosTan(0, mTapLocation, null);
Phil Weavera6b64f52015-12-04 15:21:35 -0800266 }
267 if (mPathMeasure.nextContour()) {
268 throw new IllegalArgumentException("Path has more than one contour");
269 }
270 /*
271 * Calling nextContour has moved mPathMeasure off the first contour, which is the only
272 * one we care about. Set the path again to go back to the first contour.
273 */
Phil Weaveraa866972016-04-25 17:26:53 -0700274 mPathMeasure.setPath(mPath, false);
Phil Weavera6b64f52015-12-04 15:21:35 -0800275 mStartTime = startTime;
276 mEndTime = startTime + duration;
Phil Weaveraa866972016-04-25 17:26:53 -0700277 mTimeToLengthConversion = getLength() / duration;
Phil Weaver2f165942016-09-21 11:18:05 -0700278 mId = sIdCounter++;
Phil Weavera6b64f52015-12-04 15:21:35 -0800279 }
280
281 /**
282 * Retrieve a copy of the path for this stroke
283 *
284 * @return A copy of the path
285 */
286 public Path getPath() {
287 return new Path(mPath);
288 }
289
290 /**
291 * Get the stroke's start time
292 *
293 * @return the start time for this stroke.
294 */
295 public long getStartTime() {
296 return mStartTime;
297 }
298
299 /**
300 * Get the stroke's duration
301 *
302 * @return the duration for this stroke
303 */
304 public long getDuration() {
305 return mEndTime - mStartTime;
306 }
307
Phil Weaver2f165942016-09-21 11:18:05 -0700308 /**
309 * Get the stroke's ID. The ID is used when a stroke is to be continued by another
310 * stroke in a future gesture.
311 *
312 * @return the ID of this stroke
Phil Weaverbe2922f2017-04-28 14:58:35 -0700313 * @hide
Phil Weaver2f165942016-09-21 11:18:05 -0700314 */
315 public int getId() {
316 return mId;
317 }
318
319 /**
Phil Weaverbe2922f2017-04-28 14:58:35 -0700320 * Create a new stroke that will continue this one. This is only possible if this stroke
321 * will continue.
322 *
323 * @param path The path for the stroke that continues this one. The starting point of
324 * this path must match the ending point of the stroke it continues.
325 * @param startTime The time, in milliseconds, from the time the gesture starts to the
326 * time this stroke should start. Must not be negative. This time is from
327 * the start of the new gesture, not the one being continued.
328 * @param duration The duration for the new stroke. Must not be negative.
329 * @param willContinue {@code true} if this stroke will be continued by one in the
330 * next gesture {@code false} otherwise.
331 * @return
332 */
333 public StrokeDescription continueStroke(Path path, long startTime, long duration,
334 boolean willContinue) {
335 if (!mContinued) {
336 throw new IllegalStateException(
337 "Only strokes marked willContinue can be continued");
338 }
339 StrokeDescription strokeDescription =
340 new StrokeDescription(path, startTime, duration, willContinue);
341 strokeDescription.mContinuedStrokeId = mId;
342 return strokeDescription;
343 }
344
345 /**
Phil Weaver2f165942016-09-21 11:18:05 -0700346 * Check if this stroke is marked to continue in the next gesture.
347 *
348 * @return {@code true} if the stroke is to be continued.
349 */
Phil Weaverbe2922f2017-04-28 14:58:35 -0700350 public boolean willContinue() {
Phil Weaver2f165942016-09-21 11:18:05 -0700351 return mContinued;
352 }
353
354 /**
355 * Get the ID of the stroke that this one will continue.
356 *
357 * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
Phil Weaverbe2922f2017-04-28 14:58:35 -0700358 * @hide
Phil Weaver2f165942016-09-21 11:18:05 -0700359 */
360 public int getContinuedStrokeId() {
361 return mContinuedStrokeId;
362 }
363
Phil Weavera6b64f52015-12-04 15:21:35 -0800364 float getLength() {
365 return mPathMeasure.getLength();
366 }
367
368 /* Assumes hasPointForTime returns true */
369 boolean getPosForTime(long time, float[] pos) {
Phil Weaveraa866972016-04-25 17:26:53 -0700370 if (mTapLocation != null) {
371 pos[0] = mTapLocation[0];
372 pos[1] = mTapLocation[1];
373 return true;
374 }
Phil Weavera6b64f52015-12-04 15:21:35 -0800375 if (time == mEndTime) {
376 // Close to the end time, roundoff can be a problem
377 return mPathMeasure.getPosTan(getLength(), pos, null);
378 }
379 float length = mTimeToLengthConversion * ((float) (time - mStartTime));
380 return mPathMeasure.getPosTan(length, pos, null);
381 }
382
383 boolean hasPointForTime(long time) {
384 return ((time >= mStartTime) && (time <= mEndTime));
385 }
386 }
387
Phil Weavera8918f22016-08-05 11:23:50 -0700388 /**
389 * The location of a finger for gesture dispatch
390 *
391 * @hide
392 */
393 public static class TouchPoint implements Parcelable {
394 private static final int FLAG_IS_START_OF_PATH = 0x01;
395 private static final int FLAG_IS_END_OF_PATH = 0x02;
396
Phil Weaver2f165942016-09-21 11:18:05 -0700397 public int mStrokeId;
398 public int mContinuedStrokeId;
399 public boolean mIsStartOfPath;
400 public boolean mIsEndOfPath;
401 public float mX;
402 public float mY;
Phil Weavera6b64f52015-12-04 15:21:35 -0800403
Phil Weavera8918f22016-08-05 11:23:50 -0700404 public TouchPoint() {
405 }
406
407 public TouchPoint(TouchPoint pointToCopy) {
408 copyFrom(pointToCopy);
409 }
410
411 public TouchPoint(Parcel parcel) {
Phil Weaver2f165942016-09-21 11:18:05 -0700412 mStrokeId = parcel.readInt();
413 mContinuedStrokeId = parcel.readInt();
Phil Weavera8918f22016-08-05 11:23:50 -0700414 int startEnd = parcel.readInt();
415 mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
416 mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
417 mX = parcel.readFloat();
418 mY = parcel.readFloat();
419 }
420
Phil Weaver2f165942016-09-21 11:18:05 -0700421 public void copyFrom(TouchPoint other) {
422 mStrokeId = other.mStrokeId;
423 mContinuedStrokeId = other.mContinuedStrokeId;
Phil Weavera6b64f52015-12-04 15:21:35 -0800424 mIsStartOfPath = other.mIsStartOfPath;
425 mIsEndOfPath = other.mIsEndOfPath;
426 mX = other.mX;
427 mY = other.mY;
428 }
Phil Weavera8918f22016-08-05 11:23:50 -0700429
430 @Override
Eugene Suslaa55f74e2017-09-21 13:48:50 -0700431 public String toString() {
432 return "TouchPoint{"
433 + "mStrokeId=" + mStrokeId
434 + ", mContinuedStrokeId=" + mContinuedStrokeId
435 + ", mIsStartOfPath=" + mIsStartOfPath
436 + ", mIsEndOfPath=" + mIsEndOfPath
437 + ", mX=" + mX
438 + ", mY=" + mY
439 + '}';
440 }
441
442 @Override
Phil Weavera8918f22016-08-05 11:23:50 -0700443 public int describeContents() {
444 return 0;
445 }
446
447 @Override
448 public void writeToParcel(Parcel dest, int flags) {
Phil Weaver2f165942016-09-21 11:18:05 -0700449 dest.writeInt(mStrokeId);
450 dest.writeInt(mContinuedStrokeId);
Phil Weavera8918f22016-08-05 11:23:50 -0700451 int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
452 startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
453 dest.writeInt(startEnd);
454 dest.writeFloat(mX);
455 dest.writeFloat(mY);
456 }
457
458 public static final Parcelable.Creator<TouchPoint> CREATOR
459 = new Parcelable.Creator<TouchPoint>() {
460 public TouchPoint createFromParcel(Parcel in) {
461 return new TouchPoint(in);
462 }
463
464 public TouchPoint[] newArray(int size) {
465 return new TouchPoint[size];
466 }
467 };
468 }
469
470 /**
471 * A step along a gesture. Contains all of the touch points at a particular time
472 *
473 * @hide
474 */
475 public static class GestureStep implements Parcelable {
476 public long timeSinceGestureStart;
477 public int numTouchPoints;
478 public TouchPoint[] touchPoints;
479
480 public GestureStep(long timeSinceGestureStart, int numTouchPoints,
481 TouchPoint[] touchPointsToCopy) {
482 this.timeSinceGestureStart = timeSinceGestureStart;
483 this.numTouchPoints = numTouchPoints;
484 this.touchPoints = new TouchPoint[numTouchPoints];
485 for (int i = 0; i < numTouchPoints; i++) {
486 this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
487 }
488 }
489
490 public GestureStep(Parcel parcel) {
491 timeSinceGestureStart = parcel.readLong();
492 Parcelable[] parcelables =
493 parcel.readParcelableArray(TouchPoint.class.getClassLoader());
494 numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
495 touchPoints = new TouchPoint[numTouchPoints];
496 for (int i = 0; i < numTouchPoints; i++) {
497 touchPoints[i] = (TouchPoint) parcelables[i];
498 }
499 }
500
501 @Override
502 public int describeContents() {
503 return 0;
504 }
505
506 @Override
507 public void writeToParcel(Parcel dest, int flags) {
508 dest.writeLong(timeSinceGestureStart);
509 dest.writeParcelableArray(touchPoints, flags);
510 }
511
512 public static final Parcelable.Creator<GestureStep> CREATOR
513 = new Parcelable.Creator<GestureStep>() {
514 public GestureStep createFromParcel(Parcel in) {
515 return new GestureStep(in);
516 }
517
518 public GestureStep[] newArray(int size) {
519 return new GestureStep[size];
520 }
521 };
Phil Weavera6b64f52015-12-04 15:21:35 -0800522 }
523
524 /**
Phil Weaver2f165942016-09-21 11:18:05 -0700525 * Class to convert a GestureDescription to a series of GestureSteps.
Phil Weavera8918f22016-08-05 11:23:50 -0700526 *
527 * @hide
Phil Weavera6b64f52015-12-04 15:21:35 -0800528 */
Phil Weavera8918f22016-08-05 11:23:50 -0700529 public static class MotionEventGenerator {
Phil Weavera6b64f52015-12-04 15:21:35 -0800530 /* Lazily-created scratch memory for processing touches */
531 private static TouchPoint[] sCurrentTouchPoints;
Phil Weavera6b64f52015-12-04 15:21:35 -0800532
Phil Weaver2f165942016-09-21 11:18:05 -0700533 public static List<GestureStep> getGestureStepsFromGestureDescription(
Phil Weavera6b64f52015-12-04 15:21:35 -0800534 GestureDescription description, int sampleTimeMs) {
Phil Weavera8918f22016-08-05 11:23:50 -0700535 final List<GestureStep> gestureSteps = new ArrayList<>();
Phil Weavera6b64f52015-12-04 15:21:35 -0800536
537 // Point data at each time we generate an event for
538 final TouchPoint[] currentTouchPoints =
539 getCurrentTouchPoints(description.getStrokeCount());
Phil Weavera8918f22016-08-05 11:23:50 -0700540 int currentTouchPointSize = 0;
Phil Weavera6b64f52015-12-04 15:21:35 -0800541 /* Loop through each time slice where there are touch points */
542 long timeSinceGestureStart = 0;
543 long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
544 while (nextKeyPointTime >= 0) {
Phil Weavera8918f22016-08-05 11:23:50 -0700545 timeSinceGestureStart = (currentTouchPointSize == 0) ? nextKeyPointTime
Phil Weavera6b64f52015-12-04 15:21:35 -0800546 : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
Phil Weavera8918f22016-08-05 11:23:50 -0700547 currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
Phil Weavera6b64f52015-12-04 15:21:35 -0800548 currentTouchPoints);
Phil Weavera8918f22016-08-05 11:23:50 -0700549 gestureSteps.add(new GestureStep(timeSinceGestureStart, currentTouchPointSize,
550 currentTouchPoints));
Phil Weavera6b64f52015-12-04 15:21:35 -0800551
552 /* Move to next time slice */
553 nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
554 }
Phil Weavera8918f22016-08-05 11:23:50 -0700555 return gestureSteps;
556 }
557
Phil Weavera6b64f52015-12-04 15:21:35 -0800558 private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
559 if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
560 sCurrentTouchPoints = new TouchPoint[requiredCapacity];
561 for (int i = 0; i < requiredCapacity; i++) {
562 sCurrentTouchPoints[i] = new TouchPoint();
563 }
564 }
565 return sCurrentTouchPoints;
566 }
Phil Weavera6b64f52015-12-04 15:21:35 -0800567 }
568}