TouchExplorer: pass raw events when sending events.

Right now we just send null. This was a todo to be fixed and will mean that the event stream operates consistently.
Bug: 136131815
Test: atest CtsAccessibilityTestCases  CtsAccessibilityServiceTestCases
Test: atest FrameworksServicesTests:TouchExplorerTest FrameworksServicesTests:MotionEventInjectorTest
Change-Id: I4578ef4ffe6d3c3e4fc2aa2d06fc0caaa8bb9713
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
index dc7a9aa..5ac3b69 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -72,10 +72,16 @@
      *
      * @param prototype The prototype from which to create the injected events.
      * @param action The action of the event.
+     * @param rawEvent The original event prior to magnification or other transformations.
      * @param pointerIdBits The bits of the pointers to send.
      * @param policyFlags The policy flags associated with the event.
      */
-    void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, int policyFlags) {
+    void sendMotionEvent(
+            MotionEvent prototype,
+            int action,
+            MotionEvent rawEvent,
+            int pointerIdBits,
+            int policyFlags) {
         prototype.setAction(action);
 
         MotionEvent event = null;
@@ -105,11 +111,8 @@
 
         // Make sure that the user will see the event.
         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-        // TODO: For now pass null for the raw event since the touch
-        //       explorer is the last event transformation and it does
-        //       not care about the raw event.
         if (mReceiver != null) {
-            mReceiver.onMotionEvent(event, null, policyFlags);
+            mReceiver.onMotionEvent(event, rawEvent, policyFlags);
         } else {
             Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
         }
@@ -280,7 +283,12 @@
             if (!isInjectedPointerDown(pointerId)) {
                 pointerIdBits |= (1 << pointerId);
                 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
-                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+                sendMotionEvent(
+                        prototype,
+                        action,
+                        mState.getLastReceivedEvent(),
+                        pointerIdBits,
+                        policyFlags);
             }
         }
     }
@@ -303,7 +311,8 @@
             }
             pointerIdBits |= (1 << pointerId);
             final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
-            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+            sendMotionEvent(
+                    prototype, action, mState.getLastReceivedEvent(), pointerIdBits, policyFlags);
         }
     }
 }
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 f4ac821..c60e35e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -183,9 +183,9 @@
     private void clear() {
         // If we have not received an event then we are in initial
         // state. Therefore, there is not need to clean anything.
-        MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
+        MotionEvent event = mState.getLastReceivedEvent();
         if (event != null) {
-            clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
+            clear(event, WindowManagerPolicy.FLAG_TRUSTED);
         }
     }
 
@@ -229,7 +229,7 @@
             Slog.d(LOG_TAG, mState.toString());
         }
 
-        mReceivedPointerTracker.onMotionEvent(rawEvent);
+        mState.onReceivedMotionEvent(rawEvent);
 
         if (mGestureDetector.onMotionEvent(event, rawEvent, policyFlags)) {
             // Event was handled by the gesture detector.
@@ -250,9 +250,9 @@
         } else if (mState.isTouchExploring()) {
             handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
         } else if (mState.isDragging()) {
-            handleMotionEventStateDragging(event, policyFlags);
+            handleMotionEventStateDragging(event, rawEvent, policyFlags);
         } else if (mState.isDelegating()) {
-            handleMotionEventStateDelegating(event, policyFlags);
+            handleMotionEventStateDelegating(event, rawEvent, policyFlags);
         } else if (mState.isGestureDetecting()) {
             // Already handled.
         } else {
@@ -292,7 +292,7 @@
         }
 
         // Pointers should not be zero when running this command.
-        if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) {
+        if (mState.getLastReceivedEvent().getPointerCount() == 0) {
             return;
         }
         // Try to use the standard accessibility API to long click
@@ -368,11 +368,15 @@
 
                 // We have just decided that the user is touch,
                 // exploring so start sending events.
-                mSendHoverEnterAndMoveDelayed.addEvent(event);
+                mSendHoverEnterAndMoveDelayed.addEvent(event, mState.getLastReceivedEvent());
                 mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                 mSendHoverExitDelayed.cancel();
                 mDispatcher.sendMotionEvent(
-                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                        event,
+                        MotionEvent.ACTION_HOVER_MOVE,
+                        mState.getLastReceivedEvent(),
+                        pointerIdBits,
+                        policyFlags);
                 return true;
             }
         }
@@ -387,7 +391,7 @@
         switch (event.getActionMasked()) {
             // The only way to leave the clear state is for a pointer to go down.
             case MotionEvent.ACTION_DOWN:
-                handleActionDown(event, policyFlags);
+                handleActionDown(event, rawEvent, policyFlags);
                 break;
             default:
                 // Some other nonsensical event.
@@ -399,7 +403,7 @@
      * Handles ACTION_DOWN while in the clear or touch interacting states. This event represents the
      * first finger touching the screen.
      */
-    private void handleActionDown(MotionEvent event, int policyFlags) {
+    private void handleActionDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         mAms.onTouchInteractionStart();
 
         // If we still have not notified the user for the last
@@ -432,10 +436,10 @@
                 // The idea is to avoid getting stuck in STATE_TOUCH_INTERACTING
                 final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
                 final int pointerIdBits = (1 << pointerId);
-                mSendHoverEnterAndMoveDelayed.post(event, pointerIdBits, policyFlags);
+                mSendHoverEnterAndMoveDelayed.post(event, rawEvent, pointerIdBits, policyFlags);
             } else {
                 // Cache the event until we discern exploration from gesturing.
-                mSendHoverEnterAndMoveDelayed.addEvent(event);
+                mSendHoverEnterAndMoveDelayed.addEvent(event, rawEvent);
             }
         }
     }
@@ -453,7 +457,7 @@
             case MotionEvent.ACTION_DOWN:
                 // Continue the previous interaction.
                 mSendTouchInteractionEndDelayed.cancel();
-                handleActionDown(event, policyFlags);
+                handleActionDown(event, rawEvent, policyFlags);
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
                 handleActionPointerDown();
@@ -462,7 +466,7 @@
                 handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
                 break;
             case MotionEvent.ACTION_UP:
-                handleActionUp(event, policyFlags);
+                handleActionUp(event, rawEvent, policyFlags);
                 break;
         }
     }
@@ -487,7 +491,7 @@
                 handleActionMoveStateTouchExploring(event, rawEvent, policyFlags);
                 break;
             case MotionEvent.ACTION_UP:
-                handleActionUp(event, policyFlags);
+                handleActionUp(event, rawEvent, policyFlags);
                 break;
             default:
                 break;
@@ -520,7 +524,7 @@
                 // figure out what the user is doing.
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
                     // Cache the event until we discern exploration from gesturing.
-                    mSendHoverEnterAndMoveDelayed.addEvent(event);
+                    mSendHoverEnterAndMoveDelayed.addEvent(event, rawEvent);
                 }
                 break;
             case 2:
@@ -538,7 +542,7 @@
                     mDraggingPointerId = pointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
                     mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
+                            event, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
@@ -558,13 +562,13 @@
      * Handles ACTION_UP while in the touch interacting state. This event represents all fingers
      * being lifted from the screen.
      */
-    private void handleActionUp(MotionEvent event, int policyFlags) {
+    private void handleActionUp(MotionEvent event,  MotionEvent rawEvent, int policyFlags) {
         mAms.onTouchInteractionEnd();
         final int pointerId = event.getPointerId(event.getActionIndex());
         final int pointerIdBits = (1 << pointerId);
         if (mSendHoverEnterAndMoveDelayed.isPending()) {
             // If we have not delivered the enter schedule an exit.
-            mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
+            mSendHoverExitDelayed.post(event, rawEvent, pointerIdBits, policyFlags);
         } else {
             // The user is touch exploring so we send events for end.
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
@@ -588,7 +592,7 @@
             // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
                 mDispatcher.sendMotionEvent(
-                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                        event, MotionEvent.ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
                 break;
             case 2:
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -638,7 +642,8 @@
      * @param event The event to be handled.
      * @param policyFlags The policy flags associated with the event.
      */
-    private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
+    private void handleMotionEventStateDragging(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         int pointerIdBits = 0;
         // Clear the dragging pointer id if it's no longer valid.
         if (event.findPointerIndex(mDraggingPointerId) == -1) {
@@ -662,7 +667,7 @@
                 mState.startDelegating();
                 if (mDraggingPointerId != INVALID_POINTER_ID) {
                     mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                            event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
                 }
                 mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
             } break;
@@ -681,6 +686,7 @@
                             mDispatcher.sendMotionEvent(
                                     event,
                                     MotionEvent.ACTION_MOVE,
+                                    rawEvent,
                                     pointerIdBits,
                                     policyFlags);
                         } else {
@@ -690,7 +696,11 @@
                             // Remove move history before send injected non-move events
                             event = MotionEvent.obtainNoHistory(event);
                             // Send an event to the end of the drag gesture.
-                            mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                            mDispatcher.sendMotionEvent(
+                                    event,
+                                    MotionEvent.ACTION_UP,
+                                    rawEvent,
+                                    pointerIdBits,
                                     policyFlags);
                             // Deliver all pointers to the view hierarchy.
                             mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
@@ -700,7 +710,11 @@
                         mState.startDelegating();
                         event = MotionEvent.obtainNoHistory(event);
                         // Send an event to the end of the drag gesture.
-                        mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                        mDispatcher.sendMotionEvent(
+                                event,
+                                MotionEvent.ACTION_UP,
+                                rawEvent,
+                                pointerIdBits,
                                 policyFlags);
                         // Deliver all pointers to the view hierarchy.
                         mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
@@ -713,7 +727,7 @@
                     mDraggingPointerId = INVALID_POINTER_ID;
                         // Send an event to the end of the drag gesture.
                     mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                            event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
                  }
             } break;
             case MotionEvent.ACTION_UP: {
@@ -726,7 +740,7 @@
                     mDraggingPointerId = INVALID_POINTER_ID;
                     // Send an event to the end of the drag gesture.
                     mDispatcher.sendMotionEvent(
-                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                            event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
                 }
             } break;
         }
@@ -738,7 +752,8 @@
      * @param event The event to be handled.
      * @param policyFlags The policy flags associated with the event.
      */
-    private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
+    private void handleMotionEventStateDelegating(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN: {
                 Slog.e(LOG_TAG, "Delegating state can only be reached if "
@@ -749,7 +764,7 @@
             case MotionEvent.ACTION_UP: {
                 // Deliver the event.
                 mDispatcher.sendMotionEvent(
-                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                        event, event.getAction(), rawEvent, ALL_POINTER_ID_BITS, policyFlags);
 
                 // Announce the end of a the touch interaction.
                 mAms.onTouchInteractionEnd();
@@ -759,7 +774,7 @@
             default: {
                     // Deliver the event.
                 mDispatcher.sendMotionEvent(
-                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                        event, event.getAction(), rawEvent, ALL_POINTER_ID_BITS, policyFlags);
             }
         }
     }
@@ -792,7 +807,11 @@
                 mSendTouchExplorationEndDelayed.post();
             }
             mDispatcher.sendMotionEvent(
-                    event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+                    event,
+                    MotionEvent.ACTION_HOVER_EXIT,
+                    mState.getLastReceivedEvent(),
+                    pointerIdBits,
+                    policyFlags);
         }
     }
 
@@ -807,7 +826,11 @@
         if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
             mDispatcher.sendMotionEvent(
-                    event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
+                    event,
+                    MotionEvent.ACTION_HOVER_ENTER,
+                    mState.getLastReceivedEvent(),
+                    pointerIdBits,
+                    policyFlags);
         }
     }
 
@@ -891,20 +914,23 @@
         private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverEnterAndMoveDelayed";
 
         private final List<MotionEvent> mEvents = new ArrayList<MotionEvent>();
+        private final List<MotionEvent> mRawEvents = new ArrayList<MotionEvent>();
 
         private int mPointerIdBits;
         private int mPolicyFlags;
 
-        public void post(MotionEvent event, int pointerIdBits, int policyFlags) {
+        public void post(
+                MotionEvent event, MotionEvent rawEvent, int pointerIdBits, int policyFlags) {
             cancel();
-            addEvent(event);
+            addEvent(event, rawEvent);
             mPointerIdBits = pointerIdBits;
             mPolicyFlags = policyFlags;
             mHandler.postDelayed(this, mDetermineUserIntentTimeout);
         }
 
-        public void addEvent(MotionEvent event) {
+        public void addEvent(MotionEvent event, MotionEvent rawEvent) {
             mEvents.add(MotionEvent.obtain(event));
+            mRawEvents.add(MotionEvent.obtain(rawEvent));
         }
 
         public void cancel() {
@@ -925,6 +951,10 @@
             for (int i = eventCount - 1; i >= 0; i--) {
                 mEvents.remove(i).recycle();
             }
+            final int rawEventcount = mRawEvents.size();
+            for (int i = rawEventcount - 1; i >= 0; i--) {
+                mRawEvents.remove(i).recycle();
+            }
         }
 
         public void forceSendAndRemove() {
@@ -939,10 +969,10 @@
             mDispatcher.sendAccessibilityEvent(
                     AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
 
-            if (!mEvents.isEmpty()) {
+            if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) {
                 // Deliver a down event.
                 mDispatcher.sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
-                        mPointerIdBits, mPolicyFlags);
+                        mRawEvents.get(0), mPointerIdBits, mPolicyFlags);
                 if (DEBUG) {
                     Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
                             "Injecting motion event: ACTION_HOVER_ENTER");
@@ -952,7 +982,7 @@
                 final int eventCount = mEvents.size();
                 for (int i = 1; i < eventCount; i++) {
                     mDispatcher.sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
-                            mPointerIdBits, mPolicyFlags);
+                            mRawEvents.get(i), mPointerIdBits, mPolicyFlags);
                     if (DEBUG) {
                         Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
                                 "Injecting motion event: ACTION_HOVER_MOVE");
@@ -970,12 +1000,15 @@
         private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverExitDelayed";
 
         private MotionEvent mPrototype;
+        private MotionEvent mRawEvent;
         private int mPointerIdBits;
         private int mPolicyFlags;
 
-        public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) {
+        public void post(
+                MotionEvent prototype, MotionEvent rawEvent, int pointerIdBits, int policyFlags) {
             cancel();
             mPrototype = MotionEvent.obtain(prototype);
+            mRawEvent = MotionEvent.obtain(rawEvent);
             mPointerIdBits = pointerIdBits;
             mPolicyFlags = policyFlags;
             mHandler.postDelayed(this, mDetermineUserIntentTimeout);
@@ -993,8 +1026,14 @@
         }
 
         private void clear() {
-            mPrototype.recycle();
+            if (mPrototype != null) {
+                mPrototype.recycle();
+            }
+            if (mRawEvent != null) {
+                mRawEvent.recycle();
+            }
             mPrototype = null;
+            mRawEvent = null;
             mPointerIdBits = -1;
             mPolicyFlags = 0;
         }
@@ -1011,8 +1050,12 @@
                 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:"
                         + " ACTION_HOVER_EXIT");
             }
-            mDispatcher.sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
-                    mPointerIdBits, mPolicyFlags);
+            mDispatcher.sendMotionEvent(
+                    mPrototype,
+                    MotionEvent.ACTION_HOVER_EXIT,
+                    mRawEvent,
+                    mPointerIdBits,
+                    mPolicyFlags);
             if (!mSendTouchExplorationEndDelayed.isPending()) {
                 mSendTouchExplorationEndDelayed.cancel();
                 mSendTouchExplorationEndDelayed.post();
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index 49938fa..f463260 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,6 +71,7 @@
     // Helper class to track received pointers.
     // Todo: collapse or hide this class so multiple classes don't modify it.
     private final ReceivedPointerTracker mReceivedPointerTracker;
+    private MotionEvent mLastReceivedEvent;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -80,6 +81,10 @@
     public void clear() {
         setState(STATE_CLEAR);
         // Reset the pointer trackers.
+        if (mLastReceivedEvent != null) {
+            mLastReceivedEvent.recycle();
+            mLastReceivedEvent = null;
+        }
         mReceivedPointerTracker.clear();
     }
 
@@ -89,6 +94,10 @@
      * @param rawEvent The raw touch event.
      */
     public void onReceivedMotionEvent(MotionEvent rawEvent) {
+        if (mLastReceivedEvent != null) {
+            mLastReceivedEvent.recycle();
+        }
+        mLastReceivedEvent = MotionEvent.obtain(rawEvent);
         mReceivedPointerTracker.onMotionEvent(rawEvent);
     }
 
@@ -216,6 +225,11 @@
         return mReceivedPointerTracker;
     }
 
+    /** @return The last received event. */
+    public MotionEvent getLastReceivedEvent() {
+        return mLastReceivedEvent;
+    }
+
     /** This class tracks where and when a pointer went down. It does not track its movement. */
     class ReceivedPointerTracker {
         private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
@@ -232,8 +246,6 @@
         // or if it goes up the next one that most recently went down.
         private int mPrimaryPointerId;
 
-        // Keep track of the last up pointer data.
-        private MotionEvent mLastReceivedEvent;
 
         ReceivedPointerTracker() {
             clear();
@@ -254,11 +266,6 @@
          * @param event The event to process.
          */
         public void onMotionEvent(MotionEvent event) {
-            if (mLastReceivedEvent != null) {
-                mLastReceivedEvent.recycle();
-            }
-            mLastReceivedEvent = MotionEvent.obtain(event);
-
             final int action = event.getActionMasked();
             switch (action) {
                 case MotionEvent.ACTION_DOWN:
@@ -279,11 +286,6 @@
             }
         }
 
-        /** @return The last received event. */
-        public MotionEvent getLastReceivedEvent() {
-            return mLastReceivedEvent;
-        }
-
         /** @return The number of received pointers that are down. */
         public int getReceivedPointerDownCount() {
             return Integer.bitCount(mReceivedPointersDown);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index f1142fd..36e854c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -46,7 +46,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
-import android.util.Log;
 import android.view.Display;
 import android.view.InputDevice;
 import android.view.KeyEvent;
@@ -55,6 +54,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.utils.MotionEventMatcher;
+
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -761,56 +762,6 @@
         return next;
     }
 
-    static class MotionEventMatcher extends TypeSafeMatcher<MotionEvent> {
-        long mDownTime;
-        long mEventTime;
-        long mActionMasked;
-        int mX;
-        int mY;
-
-        MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y) {
-            mDownTime = downTime;
-            mEventTime = eventTime;
-            mActionMasked = actionMasked;
-            mX = x;
-            mY = y;
-        }
-
-        MotionEventMatcher(MotionEvent event) {
-            this(event.getDownTime(), event.getEventTime(), event.getActionMasked(),
-                    (int) event.getX(), (int) event.getY());
-        }
-
-        void offsetTimesBy(long timeOffset) {
-            mDownTime += timeOffset;
-            mEventTime += timeOffset;
-        }
-
-        @Override
-        public boolean matchesSafely(MotionEvent event) {
-            if ((event.getDownTime() == mDownTime) && (event.getEventTime() == mEventTime)
-                    && (event.getActionMasked() == mActionMasked) && ((int) event.getX() == mX)
-                    && ((int) event.getY() == mY)) {
-                return true;
-            }
-            Log.e(LOG_TAG, "MotionEvent match failed");
-            Log.e(LOG_TAG, "event.getDownTime() = " + event.getDownTime()
-                    + ", expected " + mDownTime);
-            Log.e(LOG_TAG, "event.getEventTime() = " + event.getEventTime()
-                    + ", expected " + mEventTime);
-            Log.e(LOG_TAG, "event.getActionMasked() = " + event.getActionMasked()
-                    + ", expected " + mActionMasked);
-            Log.e(LOG_TAG, "event.getX() = " + event.getX() + ", expected " + mX);
-            Log.e(LOG_TAG, "event.getY() = " + event.getY() + ", expected " + mY);
-            return false;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("Motion event matcher");
-        }
-    }
-
     private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
         int mAction;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 104aacb..4b1ec6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.accessibility.gestures.TouchState.STATE_DRAGGING;
 import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
 
@@ -36,6 +37,7 @@
 
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.utils.MotionEventMatcher;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -49,6 +51,7 @@
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
+    private static final String LOG_TAG = "TouchExplorerTest";
     private static final int FLAG_1FINGER = 0x8000;
     private static final int FLAG_2FINGERS = 0x0100;
     private static final int FLAG_3FINGERS = 0x0200;
@@ -86,7 +89,9 @@
 
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            MotionEventMatcher lastEventMatcher = new MotionEventMatcher(mLastEvent);
             mEvents.add(0, event.copy());
+            assertThat(rawEvent, lastEventMatcher);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/utils/MotionEventMatcher.java b/services/tests/servicestests/src/com/android/server/accessibility/utils/MotionEventMatcher.java
new file mode 100644
index 0000000..2b6d385
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/utils/MotionEventMatcher.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.utils;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+
+/**
+ * This class compares two motion events using a subset of their attributes: actionMasked, downTime,
+ * eventTime, and location. If two events match they are considered to be effectively equal.
+ */
+public class MotionEventMatcher extends TypeSafeMatcher<MotionEvent> {
+    private static final String LOG_TAG = "MotionEventMatcher";
+    long mDownTime;
+    long mEventTime;
+    long mActionMasked;
+    int mX;
+    int mY;
+
+    MotionEventMatcher(long downTime, long eventTime, int actionMasked, int x, int y) {
+        mDownTime = downTime;
+        mEventTime = eventTime;
+        mActionMasked = actionMasked;
+        mX = x;
+        mY = y;
+    }
+
+    public MotionEventMatcher(MotionEvent event) {
+        this(
+                event.getDownTime(),
+                event.getEventTime(),
+                event.getActionMasked(),
+                (int) event.getX(),
+                (int) event.getY());
+    }
+
+    void offsetTimesBy(long timeOffset) {
+        mDownTime += timeOffset;
+        mEventTime += timeOffset;
+    }
+
+    @Override
+    public boolean matchesSafely(MotionEvent event) {
+        if ((event.getDownTime() == mDownTime)
+                && (event.getEventTime() == mEventTime)
+                && (event.getActionMasked() == mActionMasked)
+                && ((int) event.getX() == mX)
+                && ((int) event.getY() == mY)) {
+            return true;
+        }
+        Log.e(LOG_TAG, "MotionEvent match failed");
+        Log.e(LOG_TAG, "event.getDownTime() = " + event.getDownTime() + ", expected " + mDownTime);
+        Log.e(
+                LOG_TAG,
+                "event.getEventTime() = " + event.getEventTime() + ", expected " + mEventTime);
+        Log.e(
+                LOG_TAG,
+                "event.getActionMasked() = "
+                        + event.getActionMasked()
+                        + ", expected "
+                        + mActionMasked);
+        Log.e(LOG_TAG, "event.getX() = " + event.getX() + ", expected " + mX);
+        Log.e(LOG_TAG, "event.getY() = " + event.getY() + ", expected " + mY);
+        return false;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("Motion event matcher");
+    }
+}