• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.accessibilityservice;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.graphics.Path;
22 import android.graphics.PathMeasure;
23 import android.graphics.RectF;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.view.InputDevice;
27 import android.view.MotionEvent;
28 import android.view.MotionEvent.PointerCoords;
29 import android.view.MotionEvent.PointerProperties;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Accessibility services with the
36  * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
37  * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
38  * Gestures are immutable once built.
39  * <p>
40  * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
41  */
42 public final class GestureDescription {
43     /** Gestures may contain no more than this many strokes */
44     private static final int MAX_STROKE_COUNT = 10;
45 
46     /**
47      * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
48      */
49     private static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
50 
51     private final List<StrokeDescription> mStrokes = new ArrayList<>();
52     private final float[] mTempPos = new float[2];
53 
54     /**
55      * Get the upper limit for the number of strokes a gesture may contain.
56      *
57      * @return The maximum number of strokes.
58      */
getMaxStrokeCount()59     public static int getMaxStrokeCount() {
60         return MAX_STROKE_COUNT;
61     }
62 
63     /**
64      * Get the upper limit on a gesture's duration.
65      *
66      * @return The maximum duration in milliseconds.
67      */
getMaxGestureDuration()68     public static long getMaxGestureDuration() {
69         return MAX_GESTURE_DURATION_MS;
70     }
71 
GestureDescription()72     private GestureDescription() {}
73 
GestureDescription(List<StrokeDescription> strokes)74     private GestureDescription(List<StrokeDescription> strokes) {
75         mStrokes.addAll(strokes);
76     }
77 
78     /**
79      * Get the number of stroke in the gesture.
80      *
81      * @return the number of strokes in this gesture
82      */
getStrokeCount()83     public int getStrokeCount() {
84         return mStrokes.size();
85     }
86 
87     /**
88      * Read a stroke from the gesture
89      *
90      * @param index the index of the stroke
91      *
92      * @return A description of the stroke.
93      */
getStroke(@ntRangefrom = 0) int index)94     public StrokeDescription getStroke(@IntRange(from = 0) int index) {
95         return mStrokes.get(index);
96     }
97 
98     /**
99      * Return the smallest key point (where a path starts or ends) that is at least a specified
100      * offset
101      * @param offset the minimum start time
102      * @return The next key time that is at least the offset or -1 if one can't be found
103      */
getNextKeyPointAtLeast(long offset)104     private long getNextKeyPointAtLeast(long offset) {
105         long nextKeyPoint = Long.MAX_VALUE;
106         for (int i = 0; i < mStrokes.size(); i++) {
107             long thisStartTime = mStrokes.get(i).mStartTime;
108             if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
109                 nextKeyPoint = thisStartTime;
110             }
111             long thisEndTime = mStrokes.get(i).mEndTime;
112             if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
113                 nextKeyPoint = thisEndTime;
114             }
115         }
116         return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
117     }
118 
119     /**
120      * Get the points that correspond to a particular moment in time.
121      * @param time The time of interest
122      * @param touchPoints An array to hold the current touch points. Must be preallocated to at
123      * least the number of paths in the gesture to prevent going out of bounds
124      * @return The number of points found, and thus the number of elements set in each array
125      */
getPointsForTime(long time, TouchPoint[] touchPoints)126     private int getPointsForTime(long time, TouchPoint[] touchPoints) {
127         int numPointsFound = 0;
128         for (int i = 0; i < mStrokes.size(); i++) {
129             StrokeDescription strokeDescription = mStrokes.get(i);
130             if (strokeDescription.hasPointForTime(time)) {
131                 touchPoints[numPointsFound].mPathIndex = i;
132                 touchPoints[numPointsFound].mIsStartOfPath = (time == strokeDescription.mStartTime);
133                 touchPoints[numPointsFound].mIsEndOfPath = (time == strokeDescription.mEndTime);
134                 strokeDescription.getPosForTime(time, mTempPos);
135                 touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
136                 touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
137                 numPointsFound++;
138             }
139         }
140         return numPointsFound;
141     }
142 
143     // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
144     // counts against total duration
getTotalDuration(List<StrokeDescription> paths)145     private static long getTotalDuration(List<StrokeDescription> paths) {
146         long latestEnd = Long.MIN_VALUE;
147         for (int i = 0; i < paths.size(); i++) {
148             StrokeDescription path = paths.get(i);
149             latestEnd = Math.max(latestEnd, path.mEndTime);
150         }
151         return Math.max(latestEnd, 0);
152     }
153 
154     /**
155      * Builder for a {@code GestureDescription}
156      */
157     public static class Builder {
158 
159         private final List<StrokeDescription> mStrokes = new ArrayList<>();
160 
161         /**
162          * Add a stroke to the gesture description. Up to
163          * {@link GestureDescription#getMaxStrokeCount()} paths may be
164          * added to a gesture, and the total gesture duration (earliest path start time to latest
165          * path end time) may not exceed {@link GestureDescription#getMaxGestureDuration()}.
166          *
167          * @param strokeDescription the stroke to add.
168          *
169          * @return this
170          */
addStroke(@onNull StrokeDescription strokeDescription)171         public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
172             if (mStrokes.size() >= MAX_STROKE_COUNT) {
173                 throw new IllegalStateException(
174                         "Attempting to add too many strokes to a gesture");
175             }
176 
177             mStrokes.add(strokeDescription);
178 
179             if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
180                 mStrokes.remove(strokeDescription);
181                 throw new IllegalStateException(
182                         "Gesture would exceed maximum duration with new stroke");
183             }
184             return this;
185         }
186 
build()187         public GestureDescription build() {
188             if (mStrokes.size() == 0) {
189                 throw new IllegalStateException("Gestures must have at least one stroke");
190             }
191             return new GestureDescription(mStrokes);
192         }
193     }
194 
195     /**
196      * Immutable description of stroke that can be part of a gesture.
197      */
198     public static class StrokeDescription {
199         Path mPath;
200         long mStartTime;
201         long mEndTime;
202         private float mTimeToLengthConversion;
203         private PathMeasure mPathMeasure;
204         // The tap location is only set for zero-length paths
205         float[] mTapLocation;
206 
207         /**
208          * @param path The path to follow. Must have exactly one contour. The bounds of the path
209          * must not be negative. The path must not be empty. If the path has zero length
210          * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
211          * @param startTime The time, in milliseconds, from the time the gesture starts to the
212          * time the stroke should start. Must not be negative.
213          * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
214          * Must not be negative.
215          */
StrokeDescription(@onNull Path path, @IntRange(from = 0) long startTime, @IntRange(from = 0) long duration)216         public StrokeDescription(@NonNull Path path,
217                 @IntRange(from = 0) long startTime,
218                 @IntRange(from = 0) long duration) {
219             if (duration <= 0) {
220                 throw new IllegalArgumentException("Duration must be positive");
221             }
222             if (startTime < 0) {
223                 throw new IllegalArgumentException("Start time must not be negative");
224             }
225             RectF bounds = new RectF();
226             path.computeBounds(bounds, false /* unused */);
227             if ((bounds.bottom < 0) || (bounds.top < 0) || (bounds.right < 0)
228                     || (bounds.left < 0)) {
229                 throw new IllegalArgumentException("Path bounds must not be negative");
230             }
231             if (path.isEmpty()) {
232                 throw new IllegalArgumentException("Path is empty");
233             }
234             mPath = new Path(path);
235             mPathMeasure = new PathMeasure(path, false);
236             if (mPathMeasure.getLength() == 0) {
237                 // Treat zero-length paths as taps
238                 Path tempPath = new Path(path);
239                 tempPath.lineTo(-1, -1);
240                 mTapLocation = new float[2];
241                 PathMeasure pathMeasure = new PathMeasure(tempPath, false);
242                 pathMeasure.getPosTan(0, mTapLocation, null);
243             }
244             if (mPathMeasure.nextContour()) {
245                 throw new IllegalArgumentException("Path has more than one contour");
246             }
247             /*
248              * Calling nextContour has moved mPathMeasure off the first contour, which is the only
249              * one we care about. Set the path again to go back to the first contour.
250              */
251             mPathMeasure.setPath(mPath, false);
252             mStartTime = startTime;
253             mEndTime = startTime + duration;
254             mTimeToLengthConversion = getLength() / duration;
255         }
256 
257         /**
258          * Retrieve a copy of the path for this stroke
259          *
260          * @return A copy of the path
261          */
getPath()262         public Path getPath() {
263             return new Path(mPath);
264         }
265 
266         /**
267          * Get the stroke's start time
268          *
269          * @return the start time for this stroke.
270          */
getStartTime()271         public long getStartTime() {
272             return mStartTime;
273         }
274 
275         /**
276          * Get the stroke's duration
277          *
278          * @return the duration for this stroke
279          */
getDuration()280         public long getDuration() {
281             return mEndTime - mStartTime;
282         }
283 
getLength()284         float getLength() {
285             return mPathMeasure.getLength();
286         }
287 
288         /* Assumes hasPointForTime returns true */
getPosForTime(long time, float[] pos)289         boolean getPosForTime(long time, float[] pos) {
290             if (mTapLocation != null) {
291                 pos[0] = mTapLocation[0];
292                 pos[1] = mTapLocation[1];
293                 return true;
294             }
295             if (time == mEndTime) {
296                 // Close to the end time, roundoff can be a problem
297                 return mPathMeasure.getPosTan(getLength(), pos, null);
298             }
299             float length = mTimeToLengthConversion * ((float) (time - mStartTime));
300             return mPathMeasure.getPosTan(length, pos, null);
301         }
302 
hasPointForTime(long time)303         boolean hasPointForTime(long time) {
304             return ((time >= mStartTime) && (time <= mEndTime));
305         }
306     }
307 
308     /**
309      * The location of a finger for gesture dispatch
310      *
311      * @hide
312      */
313     public static class TouchPoint implements Parcelable {
314         private static final int FLAG_IS_START_OF_PATH = 0x01;
315         private static final int FLAG_IS_END_OF_PATH = 0x02;
316 
317         int mPathIndex;
318         boolean mIsStartOfPath;
319         boolean mIsEndOfPath;
320         float mX;
321         float mY;
322 
TouchPoint()323         public TouchPoint() {
324         }
325 
TouchPoint(TouchPoint pointToCopy)326         public TouchPoint(TouchPoint pointToCopy) {
327             copyFrom(pointToCopy);
328         }
329 
TouchPoint(Parcel parcel)330         public TouchPoint(Parcel parcel) {
331             mPathIndex = parcel.readInt();
332             int startEnd = parcel.readInt();
333             mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
334             mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
335             mX = parcel.readFloat();
336             mY = parcel.readFloat();
337         }
338 
copyFrom(TouchPoint other)339         void copyFrom(TouchPoint other) {
340             mPathIndex = other.mPathIndex;
341             mIsStartOfPath = other.mIsStartOfPath;
342             mIsEndOfPath = other.mIsEndOfPath;
343             mX = other.mX;
344             mY = other.mY;
345         }
346 
347         @Override
describeContents()348         public int describeContents() {
349             return 0;
350         }
351 
352         @Override
writeToParcel(Parcel dest, int flags)353         public void writeToParcel(Parcel dest, int flags) {
354             dest.writeInt(mPathIndex);
355             int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
356             startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
357             dest.writeInt(startEnd);
358             dest.writeFloat(mX);
359             dest.writeFloat(mY);
360         }
361 
362         public static final Parcelable.Creator<TouchPoint> CREATOR
363                 = new Parcelable.Creator<TouchPoint>() {
364             public TouchPoint createFromParcel(Parcel in) {
365                 return new TouchPoint(in);
366             }
367 
368             public TouchPoint[] newArray(int size) {
369                 return new TouchPoint[size];
370             }
371         };
372     }
373 
374     /**
375      * A step along a gesture. Contains all of the touch points at a particular time
376      *
377      * @hide
378      */
379     public static class GestureStep implements Parcelable {
380         public long timeSinceGestureStart;
381         public int numTouchPoints;
382         public TouchPoint[] touchPoints;
383 
GestureStep(long timeSinceGestureStart, int numTouchPoints, TouchPoint[] touchPointsToCopy)384         public GestureStep(long timeSinceGestureStart, int numTouchPoints,
385                 TouchPoint[] touchPointsToCopy) {
386             this.timeSinceGestureStart = timeSinceGestureStart;
387             this.numTouchPoints = numTouchPoints;
388             this.touchPoints = new TouchPoint[numTouchPoints];
389             for (int i = 0; i < numTouchPoints; i++) {
390                 this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
391             }
392         }
393 
GestureStep(Parcel parcel)394         public GestureStep(Parcel parcel) {
395             timeSinceGestureStart = parcel.readLong();
396             Parcelable[] parcelables =
397                     parcel.readParcelableArray(TouchPoint.class.getClassLoader());
398             numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
399             touchPoints = new TouchPoint[numTouchPoints];
400             for (int i = 0; i < numTouchPoints; i++) {
401                 touchPoints[i] = (TouchPoint) parcelables[i];
402             }
403         }
404 
405         @Override
describeContents()406         public int describeContents() {
407             return 0;
408         }
409 
410         @Override
writeToParcel(Parcel dest, int flags)411         public void writeToParcel(Parcel dest, int flags) {
412             dest.writeLong(timeSinceGestureStart);
413             dest.writeParcelableArray(touchPoints, flags);
414         }
415 
416         public static final Parcelable.Creator<GestureStep> CREATOR
417                 = new Parcelable.Creator<GestureStep>() {
418             public GestureStep createFromParcel(Parcel in) {
419                 return new GestureStep(in);
420             }
421 
422             public GestureStep[] newArray(int size) {
423                 return new GestureStep[size];
424             }
425         };
426     }
427 
428     /**
429      * Class to convert a GestureDescription to a series of MotionEvents.
430      *
431      * @hide
432      */
433     public static class MotionEventGenerator {
434         /**
435          * Constants used to initialize all MotionEvents
436          */
437         private static final int EVENT_META_STATE = 0;
438         private static final int EVENT_BUTTON_STATE = 0;
439         private static final int EVENT_DEVICE_ID = 0;
440         private static final int EVENT_EDGE_FLAGS = 0;
441         private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
442         private static final int EVENT_FLAGS = 0;
443         private static final float EVENT_X_PRECISION = 1;
444         private static final float EVENT_Y_PRECISION = 1;
445 
446         /* Lazily-created scratch memory for processing touches */
447         private static TouchPoint[] sCurrentTouchPoints;
448         private static TouchPoint[] sLastTouchPoints;
449         private static PointerCoords[] sPointerCoords;
450         private static PointerProperties[] sPointerProps;
451 
getGestureStepsFromGestureDescription( GestureDescription description, int sampleTimeMs)452         static List<GestureStep> getGestureStepsFromGestureDescription(
453                 GestureDescription description, int sampleTimeMs) {
454             final List<GestureStep> gestureSteps = new ArrayList<>();
455 
456             // Point data at each time we generate an event for
457             final TouchPoint[] currentTouchPoints =
458                     getCurrentTouchPoints(description.getStrokeCount());
459             int currentTouchPointSize = 0;
460             /* Loop through each time slice where there are touch points */
461             long timeSinceGestureStart = 0;
462             long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
463             while (nextKeyPointTime >= 0) {
464                 timeSinceGestureStart = (currentTouchPointSize == 0) ? nextKeyPointTime
465                         : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
466                 currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
467                         currentTouchPoints);
468                 gestureSteps.add(new GestureStep(timeSinceGestureStart, currentTouchPointSize,
469                         currentTouchPoints));
470 
471                 /* Move to next time slice */
472                 nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
473             }
474             return gestureSteps;
475         }
476 
getMotionEventsFromGestureSteps(List<GestureStep> steps)477         public static List<MotionEvent> getMotionEventsFromGestureSteps(List<GestureStep> steps) {
478             final List<MotionEvent> motionEvents = new ArrayList<>();
479 
480             // Number of points in last touch event
481             int lastTouchPointSize = 0;
482             TouchPoint[] lastTouchPoints;
483 
484             for (int i = 0; i < steps.size(); i++) {
485                 GestureStep step = steps.get(i);
486                 int currentTouchPointSize = step.numTouchPoints;
487                 lastTouchPoints = getLastTouchPoints(
488                         Math.max(lastTouchPointSize, currentTouchPointSize));
489 
490                 appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
491                         step.touchPoints, currentTouchPointSize, step.timeSinceGestureStart);
492                 lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
493                         lastTouchPointSize, step.touchPoints, currentTouchPointSize,
494                         step.timeSinceGestureStart);
495                 lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
496                         lastTouchPointSize, step.touchPoints, currentTouchPointSize,
497                         step.timeSinceGestureStart);
498             }
499             return motionEvents;
500         }
501 
getCurrentTouchPoints(int requiredCapacity)502         private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
503             if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
504                 sCurrentTouchPoints = new TouchPoint[requiredCapacity];
505                 for (int i = 0; i < requiredCapacity; i++) {
506                     sCurrentTouchPoints[i] = new TouchPoint();
507                 }
508             }
509             return sCurrentTouchPoints;
510         }
511 
getLastTouchPoints(int requiredCapacity)512         private static TouchPoint[] getLastTouchPoints(int requiredCapacity) {
513             if ((sLastTouchPoints == null) || (sLastTouchPoints.length < requiredCapacity)) {
514                 sLastTouchPoints = new TouchPoint[requiredCapacity];
515                 for (int i = 0; i < requiredCapacity; i++) {
516                     sLastTouchPoints[i] = new TouchPoint();
517                 }
518             }
519             return sLastTouchPoints;
520         }
521 
getPointerCoords(int requiredCapacity)522         private static PointerCoords[] getPointerCoords(int requiredCapacity) {
523             if ((sPointerCoords == null) || (sPointerCoords.length < requiredCapacity)) {
524                 sPointerCoords = new PointerCoords[requiredCapacity];
525                 for (int i = 0; i < requiredCapacity; i++) {
526                     sPointerCoords[i] = new PointerCoords();
527                 }
528             }
529             return sPointerCoords;
530         }
531 
getPointerProps(int requiredCapacity)532         private static PointerProperties[] getPointerProps(int requiredCapacity) {
533             if ((sPointerProps == null) || (sPointerProps.length < requiredCapacity)) {
534                 sPointerProps = new PointerProperties[requiredCapacity];
535                 for (int i = 0; i < requiredCapacity; i++) {
536                     sPointerProps[i] = new PointerProperties();
537                 }
538             }
539             return sPointerProps;
540         }
541 
appendMoveEventIfNeeded(List<MotionEvent> motionEvents, TouchPoint[] lastTouchPoints, int lastTouchPointsSize, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)542         private static void appendMoveEventIfNeeded(List<MotionEvent> motionEvents,
543                 TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
544                 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
545             /* Look for pointers that have moved */
546             boolean moveFound = false;
547             for (int i = 0; i < currentTouchPointsSize; i++) {
548                 int lastPointsIndex = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
549                         currentTouchPoints[i].mPathIndex);
550                 if (lastPointsIndex >= 0) {
551                     moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX)
552                             || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY);
553                     lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]);
554                 }
555             }
556 
557             if (moveFound) {
558                 long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
559                 motionEvents.add(obtainMotionEvent(downTime, currentTime, MotionEvent.ACTION_MOVE,
560                         lastTouchPoints, lastTouchPointsSize));
561             }
562         }
563 
appendUpEvents(List<MotionEvent> motionEvents, TouchPoint[] lastTouchPoints, int lastTouchPointsSize, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)564         private static int appendUpEvents(List<MotionEvent> motionEvents,
565                 TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
566                 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
567             /* Look for a pointer at the end of its path */
568             for (int i = 0; i < currentTouchPointsSize; i++) {
569                 if (currentTouchPoints[i].mIsEndOfPath) {
570                     int indexOfUpEvent = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
571                             currentTouchPoints[i].mPathIndex);
572                     if (indexOfUpEvent < 0) {
573                         continue; // Should not happen
574                     }
575                     long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
576                     int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_UP
577                             : MotionEvent.ACTION_POINTER_UP;
578                     action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
579                     motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
580                             lastTouchPoints, lastTouchPointsSize));
581                     /* Remove this point from lastTouchPoints */
582                     for (int j = indexOfUpEvent; j < lastTouchPointsSize - 1; j++) {
583                         lastTouchPoints[j].copyFrom(lastTouchPoints[j+1]);
584                     }
585                     lastTouchPointsSize--;
586                 }
587             }
588             return lastTouchPointsSize;
589         }
590 
appendDownEvents(List<MotionEvent> motionEvents, TouchPoint[] lastTouchPoints, int lastTouchPointsSize, TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime)591         private static int appendDownEvents(List<MotionEvent> motionEvents,
592                 TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
593                 TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
594             /* Look for a pointer that is just starting */
595             for (int i = 0; i < currentTouchPointsSize; i++) {
596                 if (currentTouchPoints[i].mIsStartOfPath) {
597                     /* Add the point to last coords and use the new array to generate the event */
598                     lastTouchPoints[lastTouchPointsSize++].copyFrom(currentTouchPoints[i]);
599                     int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_DOWN
600                             : MotionEvent.ACTION_POINTER_DOWN;
601                     long downTime = (action == MotionEvent.ACTION_DOWN) ? currentTime :
602                             motionEvents.get(motionEvents.size() - 1).getDownTime();
603                     action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
604                     motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
605                             lastTouchPoints, lastTouchPointsSize));
606                 }
607             }
608             return lastTouchPointsSize;
609         }
610 
obtainMotionEvent(long downTime, long eventTime, int action, TouchPoint[] touchPoints, int touchPointsSize)611         private static MotionEvent obtainMotionEvent(long downTime, long eventTime, int action,
612                 TouchPoint[] touchPoints, int touchPointsSize) {
613             PointerCoords[] pointerCoords = getPointerCoords(touchPointsSize);
614             PointerProperties[] pointerProperties = getPointerProps(touchPointsSize);
615             for (int i = 0; i < touchPointsSize; i++) {
616                 pointerProperties[i].id = touchPoints[i].mPathIndex;
617                 pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
618                 pointerCoords[i].clear();
619                 pointerCoords[i].pressure = 1.0f;
620                 pointerCoords[i].size = 1.0f;
621                 pointerCoords[i].x = touchPoints[i].mX;
622                 pointerCoords[i].y = touchPoints[i].mY;
623             }
624             return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize,
625                     pointerProperties, pointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE,
626                     EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS,
627                     EVENT_SOURCE, EVENT_FLAGS);
628         }
629 
findPointByPathIndex(TouchPoint[] touchPoints, int touchPointsSize, int pathIndex)630         private static int findPointByPathIndex(TouchPoint[] touchPoints, int touchPointsSize,
631                 int pathIndex) {
632             for (int i = 0; i < touchPointsSize; i++) {
633                 if (touchPoints[i].mPathIndex == pathIndex) {
634                     return i;
635                 }
636             }
637             return -1;
638         }
639     }
640 }
641