Merge "Add API to record touch interactions."
diff --git a/api/current.txt b/api/current.txt
index 498d470..ce82b5e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2851,6 +2851,7 @@
     method public int describeContents();
     method public int getDisplayId();
     method public int getGestureId();
+    method @NonNull public java.util.List<android.view.MotionEvent> getMotionEvents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR;
   }
@@ -2932,6 +2933,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7
     field public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; // 0xd
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
+    field public static final int GESTURE_UNKNOWN = 0; // 0x0
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; // 0xe
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11; // 0xb
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc
@@ -3047,6 +3049,7 @@
     field public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1024; // 0x400
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
     field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40
+    field public static final int FLAG_SEND_MOTION_EVENTS = 16384; // 0x4000
     field public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 2048; // 0x800
     field public int eventTypes;
     field public int feedbackType;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index e3139eb..d4713cb 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -46,6 +46,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_PASSTHROUGH;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -62,15 +63,21 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_TOUCH_EXPLORATION;
+import static android.accessibilityservice.AccessibilityService.GESTURE_UNKNOWN;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.MotionEvent;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This class describes the gesture event including gesture id and which display it happens
@@ -87,6 +94,8 @@
 
     /** @hide */
     @IntDef(prefix = { "GESTURE_" }, value = {
+          GESTURE_UNKNOWN,
+          GESTURE_TOUCH_EXPLORATION,
             GESTURE_2_FINGER_SINGLE_TAP,
             GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_DOUBLE_TAP,
@@ -139,17 +148,27 @@
     @GestureId
     private final int mGestureId;
     private final int mDisplayId;
+    private List<MotionEvent> mMotionEvents = new ArrayList<>();
+
+    /** @hide */
+    public AccessibilityGestureEvent(
+            int gestureId, int displayId, @NonNull List<MotionEvent> motionEvents) {
+        mGestureId = gestureId;
+        mDisplayId = displayId;
+        mMotionEvents.addAll(motionEvents);
+    }
 
     /** @hide */
     @TestApi
     public AccessibilityGestureEvent(int gestureId, int displayId) {
-        mGestureId = gestureId;
-        mDisplayId = displayId;
+        this(gestureId, displayId, new ArrayList<MotionEvent>());
     }
 
     private AccessibilityGestureEvent(@NonNull Parcel parcel) {
         mGestureId = parcel.readInt();
         mDisplayId = parcel.readInt();
+        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader());
+        mMotionEvents = slice.getList();
     }
 
     /**
@@ -172,6 +191,15 @@
         return mGestureId;
     }
 
+    /**
+     * Returns the motion events that lead to this gesture.
+     *
+     */
+    @NonNull
+    public List<MotionEvent> getMotionEvents() {
+        return mMotionEvents;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -179,12 +207,26 @@
         stringBuilder.append("gestureId: ").append(eventTypeToString(mGestureId));
         stringBuilder.append(", ");
         stringBuilder.append("displayId: ").append(mDisplayId);
+        stringBuilder.append(", ");
+        stringBuilder.append("Motion Events: [");
+        for (int i = 0; i < mMotionEvents.size(); ++i) {
+            String action = MotionEvent.actionToString(mMotionEvents.get(i).getActionMasked());
+            stringBuilder.append(action);
+            if (i < (mMotionEvents.size() - 1)) {
+                stringBuilder.append(", ");
+            } else {
+                stringBuilder.append("]");
+            }
+        }
         stringBuilder.append(']');
         return stringBuilder.toString();
     }
 
     private static String eventTypeToString(int eventType) {
         switch (eventType) {
+            case GESTURE_UNKNOWN: return "GESTURE_UNKNOWN";
+            case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH";
+            case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION";
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
             case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
                 return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
@@ -252,6 +294,7 @@
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeInt(mGestureId);
         parcel.writeInt(mDisplayId);
+        parcel.writeParcelable(new ParceledListSlice<MotionEvent>(mMotionEvents), 0);
     }
 
     /**
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 7c6d448..0ad9e44 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -235,6 +235,29 @@
 public abstract class AccessibilityService extends Service {
 
     /**
+     * The user has performed a touch-exploration gesture on the touch screen without ever
+     * triggering gesture detection. This gesture is only dispatched when {@link
+     * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+     *
+     * @hide
+     */
+    public static final int GESTURE_TOUCH_EXPLORATION = -2;
+
+    /**
+     * The user has performed a passthrough gesture on the touch screen without ever triggering
+     * gesture detection. This gesture is only dispatched when {@link
+     * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+     * @hide
+     */
+    public static final int GESTURE_PASSTHROUGH = -1;
+
+    /**
+     * The user has performed an unrecognized gesture on the touch screen. This gesture is only
+     * dispatched when {@link AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set.
+     */
+    public static final int GESTURE_UNKNOWN = 0;
+
+    /**
      * The user has performed a swipe up gesture on the touch screen.
      */
     public static final int GESTURE_SWIPE_UP = 1;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 769d006..f953da4 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -376,6 +376,20 @@
      */
     public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000;
 
+    /**
+     * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, a
+     * service will receive the motion events for each successfully-detected gesture. The service
+     * will also receive an AccessibilityGestureEvent of type GESTURE_INVALID for each cancelled
+     * gesture along with its motion events. A service will receive a gesture of type
+     * GESTURE_PASSTHROUGH and accompanying motion events for every passthrough gesture that does
+     * not start gesture detection. This information can be used to collect instances of improper
+     * gesture detection behavior and relay that information to framework developers. If {@link
+     * #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     */
+    public static final int FLAG_SEND_MOTION_EVENTS = 0x0004000;
+
     /** {@hide} */
     public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
 
@@ -1276,6 +1290,8 @@
                 return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
             case FLAG_REQUEST_2_FINGER_PASSTHROUGH:
                 return "FLAG_REQUEST_2_FINGER_PASSTHROUGH";
+            case FLAG_SEND_MOTION_EVENTS:
+                return "FLAG_SEND_MOTION_EVENTS";
             case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                 return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case FLAG_REPORT_VIEW_IDS:
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 64de32c..b74f96d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3748,6 +3748,7 @@
             <flag name="flagServiceHandlesDoubleTap" value="0x00000800" />
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. -->
             <flag name="flagRequestMultiFingerGestures" value="0x00001000" />
+            <flag name="flagSendMotionEvents" value="0x0004000" />
         </attr>
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. This setting cannot be changed at runtime. -->
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index babd54d..225a2e7 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -2851,6 +2851,7 @@
     method public int describeContents();
     method public int getDisplayId();
     method public int getGestureId();
+    method @NonNull public java.util.List<android.view.MotionEvent> getMotionEvents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR;
   }
@@ -2932,6 +2933,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7
     field public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; // 0xd
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
+    field public static final int GESTURE_UNKNOWN = 0; // 0x0
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; // 0xe
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11; // 0xb
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc
@@ -3047,6 +3049,7 @@
     field public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1024; // 0x400
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
     field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40
+    field public static final int FLAG_SEND_MOTION_EVENTS = 16384; // 0x4000
     field public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 2048; // 0x800
     field public int eventTypes;
     field public int feedbackType;
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index a167ab1..d6d4e4f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -150,6 +150,8 @@
 
     private boolean mRequestTwoFingerPassthrough;
 
+    private boolean mSendMotionEvents;
+
     boolean mRequestFilterKeyEvents;
 
     boolean mRetrieveInteractiveWindows;
@@ -329,6 +331,8 @@
                 & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0;
         mRequestTwoFingerPassthrough =
                 (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0;
+        mSendMotionEvents =
+                (info.flags & AccessibilityServiceInfo.FLAG_SEND_MOTION_EVENTS) != 0;
         mRequestFilterKeyEvents =
                 (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
         mRetrieveInteractiveWindows = (info.flags
@@ -1780,6 +1784,10 @@
         return mRequestTwoFingerPassthrough;
     }
 
+    public boolean isSendMotionEventsEnabled() {
+        return mSendMotionEvents;
+    }
+
     @Override
     public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
         mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index cd9ab8d..857ac6a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -119,12 +119,19 @@
     static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100;
 
     /**
-     * Flag for enabling multi-finger gestures.
+     * Flag for enabling two-finger passthrough when multi-finger gestures are enabled.
      *
      * @see #setUserAndEnabledFeatures(int, int)
      */
     static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200;
 
+    /**
+     * Flag for including motion events when dispatching a gesture.
+     *
+     * @see #setUserAndEnabledFeatures(int, int)
+     */
+    static final int FLAG_SEND_MOTION_EVENTS = 0x00000400;
+
     static final int FEATURES_AFFECTING_MOTION_EVENTS =
             FLAG_FEATURE_INJECT_MOTION_EVENTS
                     | FLAG_FEATURE_AUTOCLICK
@@ -432,6 +439,9 @@
                 if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
                     explorer.setTwoFingerPassthroughEnabled(true);
                 }
+                if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
+                    explorer.setSendMotionEventsEnabled(true);
+                }
                 addFirstEventHandler(displayId, explorer);
                 mTouchExplorer.put(displayId, explorer);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e1c4993..35481a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1867,6 +1867,10 @@
             if (userState.isFilterKeyEventsEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
             }
+            if (userState.isSendMotionEventsEnabled()) {
+                flags |= AccessibilityInputFilter.FLAG_SEND_MOTION_EVENTS;
+            }
+
             if (userState.isAutoclickEnabledLocked()) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
             }
@@ -2147,6 +2151,7 @@
         boolean serviceHandlesDoubleTapEnabled = false;
         boolean requestMultiFingerGestures = false;
         boolean requestTwoFingerPassthrough = false;
+        boolean sendMotionEvents = false;
         final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
             AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -2155,6 +2160,7 @@
                 serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled();
                 requestMultiFingerGestures = service.isMultiFingerGesturesEnabled();
                 requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled();
+                sendMotionEvents = service.isSendMotionEventsEnabled();
                 break;
             }
         }
@@ -2172,6 +2178,7 @@
         userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
         userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
         userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
+        userState.setSendMotionEventsEnabled(sendMotionEvents);
     }
 
     private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 4c9e444..240c7ff 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -111,6 +111,7 @@
     private boolean mServiceHandlesDoubleTap;
     private boolean mRequestMultiFingerGestures;
     private boolean mRequestTwoFingerPassthrough;
+    private boolean mSendMotionEventsEnabled;
     private int mUserInteractiveUiTimeout;
     private int mUserNonInteractiveUiTimeout;
     private int mNonInteractiveUiTimeout = 0;
@@ -171,6 +172,7 @@
         mServiceHandlesDoubleTap = false;
         mRequestMultiFingerGestures = false;
         mRequestTwoFingerPassthrough = false;
+        mSendMotionEventsEnabled = false;
         mIsDisplayMagnificationEnabled = false;
         mIsAutoclickEnabled = false;
         mUserNonInteractiveUiTimeout = 0;
@@ -460,6 +462,7 @@
                 .append(String.valueOf(mRequestMultiFingerGestures));
         pw.append(", requestTwoFingerPassthrough=")
                 .append(String.valueOf(mRequestTwoFingerPassthrough));
+        pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled));
         pw.append(", displayMagnificationEnabled=").append(String.valueOf(
                 mIsDisplayMagnificationEnabled));
         pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
@@ -802,6 +805,13 @@
         mRequestTwoFingerPassthrough = enabled;
     }
 
+    public boolean isSendMotionEventsEnabled() {
+        return mSendMotionEventsEnabled;
+    }
+
+    public void setSendMotionEventsEnabled(boolean mode) {
+        mSendMotionEventsEnabled = mode;
+    }
 
     public int getUserInteractiveUiTimeoutLocked() {
         return mUserInteractiveUiTimeout;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index 14af8c6..2c38dc3 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -99,11 +99,15 @@
     boolean mMultiFingerGesturesEnabled;
     // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled.
     private boolean mTwoFingerPassthroughEnabled;
+    // Whether to send the motion events during gesture dispatch.
+    private boolean mSendMotionEventsEnabled = false;
     // A list of all the multi-finger gestures, for easy adding and removal.
     private final List<GestureMatcher> mMultiFingerGestures = new ArrayList<>();
     // A list of two-finger swipes, for easy adding and removal when turning on or off two-finger
     // passthrough.
     private final List<GestureMatcher> mTwoFingerSwipes = new ArrayList<>();
+    // The list of motion events for the current gesture.
+    private List<MotionEvent> mEvents = new ArrayList<>();
     // Shared state information.
     private TouchState mState;
 
@@ -230,6 +234,9 @@
                 return false;
             }
         }
+        if (mSendMotionEventsEnabled) {
+            mEvents.add(MotionEvent.obtainNoHistory(rawEvent));
+        }
         for (GestureMatcher matcher : mGestures) {
             if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
                 if (DEBUG) {
@@ -240,9 +247,8 @@
                     Slog.d(LOG_TAG, matcher.toString());
                 }
                 if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
-                    // Here we just clear and return. The actual gesture dispatch is done in
+                    // Here we just return. The actual gesture dispatch is done in
                     // onStateChanged().
-                    clear();
                     // No need to process this event any further.
                     return true;
                 }
@@ -255,6 +261,11 @@
         for (GestureMatcher matcher : mGestures) {
             matcher.clear();
         }
+        if (mEvents != null) {
+            while (mEvents.size() > 0) {
+                mEvents.remove(0).recycle();
+            }
+        }
     }
 
     /**
@@ -340,29 +351,28 @@
             case GESTURE_DOUBLE_TAP:
                 if (mServiceHandlesDoubleTap) {
                     AccessibilityGestureEvent gestureEvent =
-                            new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+                            new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents);
                     mListener.onGestureCompleted(gestureEvent);
                 } else {
                     mListener.onDoubleTap(event, rawEvent, policyFlags);
                 }
-                clear();
                 break;
             case GESTURE_DOUBLE_TAP_AND_HOLD:
                 if (mServiceHandlesDoubleTap) {
                     AccessibilityGestureEvent gestureEvent =
-                            new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+                            new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents);
                     mListener.onGestureCompleted(gestureEvent);
                 } else {
                     mListener.onDoubleTapAndHold(event, rawEvent, policyFlags);
                 }
-                clear();
                 break;
             default:
                 AccessibilityGestureEvent gestureEvent =
-                        new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+                        new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents);
                 mListener.onGestureCompleted(gestureEvent);
                 break;
         }
+        clear();
     }
 
     public boolean isMultiFingerGesturesEnabled() {
@@ -406,4 +416,25 @@
     public boolean isServiceHandlesDoubleTapEnabled() {
         return mServiceHandlesDoubleTap;
     }
+
+    public void setSendMotionEventsEnabled(boolean mode) {
+        mSendMotionEventsEnabled = mode;
+        if (!mode) {
+            while (mEvents.size() > 0) {
+                mEvents.remove(0).recycle();
+            }
+        }
+    }
+
+    public boolean isSendMotionEventsEnabled() {
+        return mSendMotionEventsEnabled;
+    }
+
+    /**
+     * Returns the current list of motion events. It is the caller's responsibility to copy the list
+     * if they want it to persist after a call to clear().
+     */
+    public List<MotionEvent> getMotionEvents() {
+        return mEvents;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index d8c692b8..5460e80 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -37,6 +37,7 @@
 import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
 
 import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Region;
@@ -340,6 +341,14 @@
     public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+            if (isSendMotionEventsEnabled()) {
+                AccessibilityGestureEvent gestureEvent =
+                        new AccessibilityGestureEvent(
+                                AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD,
+                                event.getDisplayId(),
+                                mGestureDetector.getMotionEvents());
+                mAms.onGesture(gestureEvent);
+            }
             mState.startDelegating();
         }
     }
@@ -350,7 +359,14 @@
         // Remove pending event deliveries.
         mSendHoverEnterAndMoveDelayed.cancel();
         mSendHoverExitDelayed.cancel();
-
+        if (isSendMotionEventsEnabled()) {
+            AccessibilityGestureEvent gestureEvent =
+                    new AccessibilityGestureEvent(
+                            AccessibilityService.GESTURE_DOUBLE_TAP,
+                            event.getDisplayId(),
+                            mGestureDetector.getMotionEvents());
+            mAms.onGesture(gestureEvent);
+        }
         if (mSendTouchExplorationEndDelayed.isPending()) {
             mSendTouchExplorationEndDelayed.forceSendAndRemove();
         }
@@ -385,6 +401,9 @@
 
     @Override
     public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
+        if (DEBUG) {
+            Slog.d(LOG_TAG, "Dispatching gesture event:" + gestureEvent.toString());
+        }
         endGestureDetection(true);
         mSendTouchInteractionEndDelayed.cancel();
         mAms.onGesture(gestureEvent);
@@ -411,12 +430,24 @@
                 mDispatcher.sendMotionEvent(
                         event,
                         ACTION_HOVER_MOVE,
-                        mState.getLastReceivedEvent(),
+                        event,
                         pointerIdBits,
                         policyFlags);
                 return true;
             }
         }
+        if (isSendMotionEventsEnabled()) {
+            // Send a gesture with motion events to represent the cancelled gesture.
+            AccessibilityGestureEvent gestureEvent =
+                    new AccessibilityGestureEvent(
+                            AccessibilityService.GESTURE_UNKNOWN,
+                            event.getDisplayId(),
+                            mGestureDetector.getMotionEvents());
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "Dispatching gesture event:" + gestureEvent.toString());
+            }
+            mAms.onGesture(gestureEvent);
+        }
         return false;
     }
 
@@ -620,6 +651,14 @@
                 if (isDraggingGesture(event)) {
                     // Two pointers moving in the same direction within
                     // a given distance perform a drag.
+                    if (isSendMotionEventsEnabled()) {
+                        AccessibilityGestureEvent gestureEvent =
+                                new AccessibilityGestureEvent(
+                                        AccessibilityService.GESTURE_PASSTHROUGH,
+                                        event.getDisplayId(),
+                                        mGestureDetector.getMotionEvents());
+                        mAms.onGesture(gestureEvent);
+                    }
                     computeDraggingPointerIdIfNeeded(event);
                     pointerIdBits = 1 << mDraggingPointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
@@ -636,6 +675,14 @@
                     mState.startDragging();
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
+                    if (isSendMotionEventsEnabled()) {
+                        AccessibilityGestureEvent gestureEvent =
+                                new AccessibilityGestureEvent(
+                                        AccessibilityService.GESTURE_PASSTHROUGH,
+                                        event.getDisplayId(),
+                                        mGestureDetector.getMotionEvents());
+                        mAms.onGesture(gestureEvent);
+                    }
                     mState.startDelegating();
                     mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 }
@@ -650,6 +697,14 @@
                                 if (DEBUG) {
                                     Slog.d(LOG_TAG, "Three-finger edge swipe detected.");
                                 }
+                                if (isSendMotionEventsEnabled()) {
+                                    AccessibilityGestureEvent gestureEvent =
+                                            new AccessibilityGestureEvent(
+                                                    AccessibilityService.GESTURE_PASSTHROUGH,
+                                                    event.getDisplayId(),
+                                                    mGestureDetector.getMotionEvents());
+                                    mAms.onGesture(gestureEvent);
+                                }
                                 mState.startDelegating();
                                 if (mState.isTouchExploring()) {
                                     mDispatcher.sendDownForAllNotInjectedPointers(event,
@@ -663,6 +718,14 @@
                     }
                 } else {
                     // More than two pointers are delegated to the view hierarchy.
+                    if (isSendMotionEventsEnabled()) {
+                        AccessibilityGestureEvent gestureEvent =
+                                new AccessibilityGestureEvent(
+                                        AccessibilityService.GESTURE_PASSTHROUGH,
+                                        event.getDisplayId(),
+                                        mGestureDetector.getMotionEvents());
+                        mAms.onGesture(gestureEvent);
+                    }
                     mState.startDelegating();
                     event = MotionEvent.obtainNoHistory(event);
                     mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
@@ -1109,6 +1172,7 @@
     public void setTwoFingerPassthroughEnabled(boolean enabled) {
         mGestureDetector.setTwoFingerPassthroughEnabled(enabled);
     }
+
     public void setGestureDetectionPassthroughRegion(Region region) {
         mGestureDetectionPassthroughRegion = region;
     }
@@ -1117,6 +1181,17 @@
         mTouchExplorationPassthroughRegion = region;
     }
 
+    /**
+     * Whether to send the motion events that make up each gesture to the accessibility service.
+     */
+    public void setSendMotionEventsEnabled(boolean mode) {
+        mGestureDetector.setSendMotionEventsEnabled(mode);
+    }
+
+    public boolean isSendMotionEventsEnabled() {
+        return mGestureDetector.isSendMotionEventsEnabled();
+    }
+
     private boolean shouldPerformGestureDetection(MotionEvent event) {
         if (mState.isDelegating()) {
             return false;
@@ -1213,7 +1288,14 @@
         public void run() {
             // Send an accessibility event to announce the touch exploration start.
             mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
-
+            if (isSendMotionEventsEnabled()) {
+                AccessibilityGestureEvent gestureEvent =
+                        new AccessibilityGestureEvent(
+                                AccessibilityService.GESTURE_TOUCH_EXPLORATION,
+                                mState.getLastReceivedEvent().getDisplayId(),
+                                mGestureDetector.getMotionEvents());
+                mAms.onGesture(gestureEvent);
+            }
             if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) {
                 // Deliver a down event.
                 mDispatcher.sendMotionEvent(mEvents.get(0), ACTION_HOVER_ENTER,