Limit capabilities of a11y gesture dispatch.

Changing the service side to accept descriptions of
motion events, not motion events themselves, so we can
control their creation.

Bug: 30647115
Change-Id: Ia6772a1fc05df91818e3f88959d1e2b4a35fe0cc
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index ae78e218..c4eaccc 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -628,8 +628,8 @@
         if (connection == null) {
             return false;
         }
-        List<MotionEvent> events = MotionEventGenerator.getMotionEventsFromGestureDescription(
-                gesture, 100);
+        List<GestureDescription.GestureStep> steps =
+                MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 100);
         try {
             synchronized (mLock) {
                 mGestureStatusCallbackSequence++;
@@ -641,8 +641,8 @@
                             callback, handler);
                     mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
                 }
-                connection.sendMotionEvents(mGestureStatusCallbackSequence,
-                        new ParceledListSlice<>(events));
+                connection.sendGesture(mGestureStatusCallbackSequence,
+                        new ParceledListSlice<>(steps));
             }
         } catch (RemoteException re) {
             throw new RuntimeException(re);
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index fc9581e..d9b03fa 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -21,6 +21,8 @@
 import android.graphics.Path;
 import android.graphics.PathMeasure;
 import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
@@ -303,13 +305,37 @@
         }
     }
 
-    private static class TouchPoint {
+    /**
+     * The location of a finger for gesture dispatch
+     *
+     * @hide
+     */
+    public static class TouchPoint implements Parcelable {
+        private static final int FLAG_IS_START_OF_PATH = 0x01;
+        private static final int FLAG_IS_END_OF_PATH = 0x02;
+
         int mPathIndex;
         boolean mIsStartOfPath;
         boolean mIsEndOfPath;
         float mX;
         float mY;
 
+        public TouchPoint() {
+        }
+
+        public TouchPoint(TouchPoint pointToCopy) {
+            copyFrom(pointToCopy);
+        }
+
+        public TouchPoint(Parcel parcel) {
+            mPathIndex = parcel.readInt();
+            int startEnd = parcel.readInt();
+            mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
+            mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
+            mX = parcel.readFloat();
+            mY = parcel.readFloat();
+        }
+
         void copyFrom(TouchPoint other) {
             mPathIndex = other.mPathIndex;
             mIsStartOfPath = other.mIsStartOfPath;
@@ -317,12 +343,94 @@
             mX = other.mX;
             mY = other.mY;
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mPathIndex);
+            int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
+            startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
+            dest.writeInt(startEnd);
+            dest.writeFloat(mX);
+            dest.writeFloat(mY);
+        }
+
+        public static final Parcelable.Creator<TouchPoint> CREATOR
+                = new Parcelable.Creator<TouchPoint>() {
+            public TouchPoint createFromParcel(Parcel in) {
+                return new TouchPoint(in);
+            }
+
+            public TouchPoint[] newArray(int size) {
+                return new TouchPoint[size];
+            }
+        };
+    }
+
+    /**
+     * A step along a gesture. Contains all of the touch points at a particular time
+     *
+     * @hide
+     */
+    public static class GestureStep implements Parcelable {
+        public long timeSinceGestureStart;
+        public int numTouchPoints;
+        public TouchPoint[] touchPoints;
+
+        public GestureStep(long timeSinceGestureStart, int numTouchPoints,
+                TouchPoint[] touchPointsToCopy) {
+            this.timeSinceGestureStart = timeSinceGestureStart;
+            this.numTouchPoints = numTouchPoints;
+            this.touchPoints = new TouchPoint[numTouchPoints];
+            for (int i = 0; i < numTouchPoints; i++) {
+                this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
+            }
+        }
+
+        public GestureStep(Parcel parcel) {
+            timeSinceGestureStart = parcel.readLong();
+            Parcelable[] parcelables =
+                    parcel.readParcelableArray(TouchPoint.class.getClassLoader());
+            numTouchPoints = (parcelables == null) ? 0 : parcelables.length;
+            touchPoints = new TouchPoint[numTouchPoints];
+            for (int i = 0; i < numTouchPoints; i++) {
+                touchPoints[i] = (TouchPoint) parcelables[i];
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(timeSinceGestureStart);
+            dest.writeParcelableArray(touchPoints, flags);
+        }
+
+        public static final Parcelable.Creator<GestureStep> CREATOR
+                = new Parcelable.Creator<GestureStep>() {
+            public GestureStep createFromParcel(Parcel in) {
+                return new GestureStep(in);
+            }
+
+            public GestureStep[] newArray(int size) {
+                return new GestureStep[size];
+            }
+        };
     }
 
     /**
      * Class to convert a GestureDescription to a series of MotionEvents.
+     *
+     * @hide
      */
-    static class MotionEventGenerator {
+    public static class MotionEventGenerator {
         /**
          * Constants used to initialize all MotionEvents
          */
@@ -341,39 +449,53 @@
         private static PointerCoords[] sPointerCoords;
         private static PointerProperties[] sPointerProps;
 
-        static List<MotionEvent> getMotionEventsFromGestureDescription(
+        static List<GestureStep> getGestureStepsFromGestureDescription(
                 GestureDescription description, int sampleTimeMs) {
-            final List<MotionEvent> motionEvents = new ArrayList<>();
+            final List<GestureStep> gestureSteps = new ArrayList<>();
 
             // Point data at each time we generate an event for
             final TouchPoint[] currentTouchPoints =
                     getCurrentTouchPoints(description.getStrokeCount());
-            // Point data sent in last touch event
-            int lastTouchPointSize = 0;
-            final TouchPoint[] lastTouchPoints =
-                    getLastTouchPoints(description.getStrokeCount());
-
+            int currentTouchPointSize = 0;
             /* Loop through each time slice where there are touch points */
             long timeSinceGestureStart = 0;
             long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
             while (nextKeyPointTime >= 0) {
-                timeSinceGestureStart = (lastTouchPointSize == 0) ? nextKeyPointTime
+                timeSinceGestureStart = (currentTouchPointSize == 0) ? nextKeyPointTime
                         : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
-                int currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
+                currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
                         currentTouchPoints);
-
-                appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
-                        currentTouchPoints, currentTouchPointSize, timeSinceGestureStart);
-                lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
-                        lastTouchPointSize, currentTouchPoints, currentTouchPointSize,
-                        timeSinceGestureStart);
-                lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
-                        lastTouchPointSize, currentTouchPoints, currentTouchPointSize,
-                        timeSinceGestureStart);
+                gestureSteps.add(new GestureStep(timeSinceGestureStart, currentTouchPointSize,
+                        currentTouchPoints));
 
                 /* Move to next time slice */
                 nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
             }
+            return gestureSteps;
+        }
+
+        public static List<MotionEvent> getMotionEventsFromGestureSteps(List<GestureStep> steps) {
+            final List<MotionEvent> motionEvents = new ArrayList<>();
+
+            // Number of points in last touch event
+            int lastTouchPointSize = 0;
+            TouchPoint[] lastTouchPoints;
+
+            for (int i = 0; i < steps.size(); i++) {
+                GestureStep step = steps.get(i);
+                int currentTouchPointSize = step.numTouchPoints;
+                lastTouchPoints = getLastTouchPoints(
+                        Math.max(lastTouchPointSize, currentTouchPointSize));
+
+                appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
+                        step.touchPoints, currentTouchPointSize, step.timeSinceGestureStart);
+                lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
+                        lastTouchPointSize, step.touchPoints, currentTouchPointSize,
+                        step.timeSinceGestureStart);
+                lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
+                        lastTouchPointSize, step.touchPoints, currentTouchPointSize,
+                        step.timeSinceGestureStart);
+            }
             return motionEvents;
         }
 
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 7a55079..81cddba 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -88,5 +88,5 @@
 
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
-    void sendMotionEvents(int sequence, in ParceledListSlice events);
+    void sendGesture(int sequence, in ParceledListSlice gestureSteps);
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7ebc150..da89a7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -21,6 +21,7 @@
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
@@ -2747,7 +2748,7 @@
         }
 
         @Override
-        public void sendMotionEvents(int sequence, ParceledListSlice events) {
+        public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
             synchronized (mLock) {
                 if (mSecurityPolicy.canPerformGestures(this)) {
                     final long endMillis =
@@ -2761,9 +2762,16 @@
                         }
                     }
                     if (mMotionEventInjector != null) {
-                        mMotionEventInjector.injectEvents((List<MotionEvent>) events.getList(),
-                                mServiceInterface, sequence);
-                        return;
+                        List<GestureDescription.GestureStep> steps = gestureSteps.getList();
+                        List<MotionEvent> events = GestureDescription.MotionEventGenerator
+                                .getMotionEventsFromGestureSteps(steps);
+                        // Confirm that the motion events end with an UP event.
+                        if (events.get(events.size() - 1).getAction() == MotionEvent.ACTION_UP) {
+                            mMotionEventInjector.injectEvents(events, mServiceInterface, sequence);
+                            return;
+                        } else {
+                            Slog.e(LOG_TAG, "Gesture is not well-formed");
+                        }
                     } else {
                         Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
                     }