Extract event sending code into EventDispatcher.

Also combine event-sending code with what used to be the InjectedPointerTracker class because that class was used for sending events anyway.
Bug: 136131815:
Test: atest CtsAccessibilityTestCases  CtsAccessibilityServiceTestCases FrameworksServicesTests:TouchExplorerTest
Change-Id: I7fae3dc958642d8fae61b9120c732d27af16fca9
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
new file mode 100644
index 0000000..dc7a9aa
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -0,0 +1,309 @@
+/*
+ * 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.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
+import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.policy.WindowManagerPolicy;
+
+/**
+ * This class dispatches motion events and accessibility events relating to touch exploration and
+ * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is
+ * set correctly so that events go to the right place.
+ */
+class EventDispatcher {
+    private static final String LOG_TAG = "EventDispatcher";
+
+    private final AccessibilityManagerService mAms;
+    private Context mContext;
+    // The receiver of motion events.
+    private EventStreamTransformation mReceiver;
+    // Keep track of which pointers sent to the system are down.
+    private int mInjectedPointersDown;
+
+    // The time of the last injected down.
+    private long mLastInjectedDownEventTime;
+
+    // The last injected hover event.
+    private MotionEvent mLastInjectedHoverEvent;
+    private TouchState mState;
+
+    EventDispatcher(
+            Context context,
+            AccessibilityManagerService ams,
+            EventStreamTransformation receiver,
+            TouchState state) {
+        mContext = context;
+        mAms = ams;
+        mReceiver = receiver;
+        mState = state;
+    }
+
+    public void setReceiver(EventStreamTransformation receiver) {
+        mReceiver = receiver;
+    }
+
+    /**
+     * Sends an event.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param action The action of the event.
+     * @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) {
+        prototype.setAction(action);
+
+        MotionEvent event = null;
+        if (pointerIdBits == ALL_POINTER_ID_BITS) {
+            event = prototype;
+        } else {
+            try {
+                event = prototype.split(pointerIdBits);
+            } catch (IllegalArgumentException e) {
+                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
+                return;
+            }
+        }
+        if (action == MotionEvent.ACTION_DOWN) {
+            event.setDownTime(event.getEventTime());
+        } else {
+            event.setDownTime(getLastInjectedDownEventTime());
+        }
+        if (DEBUG) {
+            Slog.d(
+                    LOG_TAG,
+                    "Injecting event: "
+                            + event
+                            + ", policyFlags=0x"
+                            + Integer.toHexString(policyFlags));
+        }
+
+        // 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);
+        } else {
+            Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
+        }
+        updateState(event);
+
+        if (event != prototype) {
+            event.recycle();
+        }
+    }
+
+    /**
+     * Sends an accessibility event of the given type.
+     *
+     * @param type The event type.
+     */
+    void sendAccessibilityEvent(int type) {
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
+        if (accessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            event.setWindowId(mAms.getActiveWindowId());
+            accessibilityManager.sendAccessibilityEvent(event);
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
+            }
+        }
+        // Todo: get rid of this and have TouchState control the sending of events rather than react
+        // to it.
+        mState.onInjectedAccessibilityEvent(type);
+    }
+
+    /**
+     * Processes an injected {@link MotionEvent} event.
+     *
+     * @param event The event to process.
+     */
+    void updateState(MotionEvent event) {
+        final int action = event.getActionMasked();
+        final int pointerId = event.getPointerId(event.getActionIndex());
+        final int pointerFlag = (1 << pointerId);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mInjectedPointersDown |= pointerFlag;
+                mLastInjectedDownEventTime = event.getDownTime();
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                mInjectedPointersDown &= ~pointerFlag;
+                if (mInjectedPointersDown == 0) {
+                    mLastInjectedDownEventTime = 0;
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mLastInjectedHoverEvent != null) {
+                    mLastInjectedHoverEvent.recycle();
+                }
+                mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                break;
+        }
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
+        }
+    }
+
+    /** Clears the internals state. */
+    public void clear() {
+        mInjectedPointersDown = 0;
+    }
+
+    /** @return The time of the last injected down event. */
+    public long getLastInjectedDownEventTime() {
+        return mLastInjectedDownEventTime;
+    }
+
+    /** @return The number of down pointers injected to the view hierarchy. */
+    public int getInjectedPointerDownCount() {
+        return Integer.bitCount(mInjectedPointersDown);
+    }
+
+    /** @return The bits of the injected pointers that are down. */
+    public int getInjectedPointersDown() {
+        return mInjectedPointersDown;
+    }
+
+    /**
+     * Whether an injected pointer is down.
+     *
+     * @param pointerId The unique pointer id.
+     * @return True if the pointer is down.
+     */
+    public boolean isInjectedPointerDown(int pointerId) {
+        final int pointerFlag = (1 << pointerId);
+        return (mInjectedPointersDown & pointerFlag) != 0;
+    }
+
+    /** @return The the last injected hover event. */
+    public MotionEvent getLastInjectedHoverEvent() {
+        return mLastInjectedHoverEvent;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("=========================");
+        builder.append("\nDown pointers #");
+        builder.append(Integer.bitCount(mInjectedPointersDown));
+        builder.append(" [ ");
+        for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+            if ((mInjectedPointersDown & i) != 0) {
+                builder.append(i);
+                builder.append(" ");
+            }
+        }
+        builder.append("]");
+        builder.append("\n=========================");
+        return builder.toString();
+    }
+
+    /**
+     * Computes the action for an injected event based on a masked action and a pointer index.
+     *
+     * @param actionMasked The masked action.
+     * @param pointerIndex The index of the pointer which has changed.
+     * @return The action to be used for injection.
+     */
+    private int computeInjectionAction(int actionMasked, int pointerIndex) {
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 0) {
+                    return MotionEvent.ACTION_DOWN;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_DOWN;
+                }
+            case MotionEvent.ACTION_POINTER_UP:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 1) {
+                    return MotionEvent.ACTION_UP;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_UP;
+                }
+            default:
+                return actionMasked;
+        }
+    }
+    /**
+     * Sends down events to the view hierarchy for all pointers which are not already being
+     * delivered i.e. pointers that are not yet injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
+
+        // Inject the injected pointers.
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Do not send event for already delivered pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                pointerIdBits |= (1 << pointerId);
+                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+            }
+        }
+    }
+
+    /**
+     * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
+     * pointers that are injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Skip non injected down pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                continue;
+            }
+            pointerIdBits |= (1 << pointerId);
+            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
+            sendMotionEvent(prototype, action, 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 7044c4d..f4ac821 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -29,11 +29,11 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.BaseEventStreamTransformation;
+import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
@@ -61,7 +61,7 @@
 public class TouchExplorer extends BaseEventStreamTransformation
         implements AccessibilityGestureDetector.Listener {
 
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = false;
 
     // Tag for logging received events.
     private static final String LOG_TAG = "TouchExplorer";
@@ -109,8 +109,7 @@
     // Helper class to track received pointers.
     private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
 
-    // Helper class to track injected pointers.
-    private final TouchState.InjectedPointerTracker mInjectedPointerTracker;
+    private final EventDispatcher mDispatcher;
 
     // Handle to the accessibility manager service.
     private final AccessibilityManagerService mAms;
@@ -148,7 +147,7 @@
         mAms = service;
         mState = new TouchState();
         mReceivedPointerTracker = mState.getReceivedPointerTracker();
-        mInjectedPointerTracker = mState.getInjectedPointerTracker();
+        mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
@@ -197,10 +196,10 @@
         }  else if (mState.isDragging()) {
             mDraggingPointerId = INVALID_POINTER_ID;
             // Send exit to all pointers that we have delivered.
-            sendUpForInjectedDownPointers(event, policyFlags);
+            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         } else if (mState.isDelegating()) {
             // Send exit to all pointers that we have delivered.
-            sendUpForInjectedDownPointers(event, policyFlags);
+            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         } else if (mState.isGestureDetecting()) {
             // No state specific cleanup required.
         }
@@ -271,7 +270,8 @@
         if (mSendTouchExplorationEndDelayed.isPending()
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
                     mSendTouchExplorationEndDelayed.cancel();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+            mDispatcher.sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
         }
 
         // The event for touch interaction end should be strictly after the
@@ -279,7 +279,7 @@
         if (mSendTouchInteractionEndDelayed.isPending()
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
             mSendTouchInteractionEndDelayed.cancel();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
         }
         super.onAccessibilityEvent(event);
     }
@@ -318,7 +318,7 @@
         }
 
         // Announce the end of a new touch interaction.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
         // Try to use the standard accessibility API to click
         if (!mAms.performActionOnAccessibilityFocusedItem(
@@ -338,7 +338,7 @@
         mExitGestureDetectionModeDelayed.post();
         // Send accessibility event to announce the start
         // of gesture recognition.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
         return false;
     }
 
@@ -371,7 +371,8 @@
                 mSendHoverEnterAndMoveDelayed.addEvent(event);
                 mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                 mSendHoverExitDelayed.cancel();
-                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
                 return true;
             }
         }
@@ -417,7 +418,7 @@
         if (!mGestureDetector.firstTapDetected() && mState.isClear()) {
             mSendTouchExplorationEndDelayed.forceSendAndRemove();
             mSendTouchInteractionEndDelayed.forceSendAndRemove();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
         } else {
             // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event.
             mSendTouchInteractionEndDelayed.cancel();
@@ -536,18 +537,19 @@
                     mState.startDragging();
                     mDraggingPointerId = pointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
-                    sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
-                    sendDownForAllNotInjectedPointers(event, policyFlags);
+                    mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 }
                 break;
             default:
                 // More than two pointers are delegated to the view hierarchy.
                 mState.startDelegating();
                 event = MotionEvent.obtainNoHistory(event);
-                sendDownForAllNotInjectedPointers(event, policyFlags);
+                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 break;
         }
     }
@@ -585,7 +587,8 @@
             case 1:
             // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
                 break;
             case 2:
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -658,9 +661,10 @@
                 // goes down => delegate the three pointers to the view hierarchy
                 mState.startDelegating();
                 if (mDraggingPointerId != INVALID_POINTER_ID) {
-                    sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                 }
-                sendDownForAllNotInjectedPointers(event, policyFlags);
+                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
             } break;
             case MotionEvent.ACTION_MOVE: {
                 if (mDraggingPointerId == INVALID_POINTER_ID) {
@@ -672,21 +676,12 @@
                     } break;
                     case 2: {
                         if (isDraggingGesture(event)) {
-                            // Adjust event location to the middle location of the two pointers.
-                            final float firstPtrX = event.getX(0);
-                            final float firstPtrY = event.getY(0);
-                            final float secondPtrX = event.getX(1);
-                            final float secondPtrY = event.getY(1);
-                            final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-                            final float deltaX =
-                                    (pointerIndex == 0) ? (secondPtrX - firstPtrX)
-                                            : (firstPtrX - secondPtrX);
-                            final float deltaY =
-                                    (pointerIndex == 0) ? (secondPtrY - firstPtrY)
-                                            : (firstPtrY - secondPtrY);
-                            event.offsetLocation(deltaX / 2, deltaY / 2);
                             // If still dragging send a drag event.
-                            sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
+                            adjustEventLocationForDrag(event);
+                            mDispatcher.sendMotionEvent(
+                                    event,
+                                    MotionEvent.ACTION_MOVE,
+                                    pointerIdBits,
                                     policyFlags);
                         } else {
                             // The two pointers are moving either in different directions or
@@ -695,20 +690,20 @@
                             // Remove move history before send injected non-move events
                             event = MotionEvent.obtainNoHistory(event);
                             // Send an event to the end of the drag gesture.
-                            sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                            mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                     policyFlags);
                             // Deliver all pointers to the view hierarchy.
-                            sendDownForAllNotInjectedPointers(event, policyFlags);
+                            mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                         }
                     } break;
                     default: {
                         mState.startDelegating();
                         event = MotionEvent.obtainNoHistory(event);
                         // Send an event to the end of the drag gesture.
-                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                        mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                 policyFlags);
                         // Deliver all pointers to the view hierarchy.
-                        sendDownForAllNotInjectedPointers(event, policyFlags);
+                        mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                     }
                 }
             } break;
@@ -716,20 +711,22 @@
                  final int pointerId = event.getPointerId(event.getActionIndex());
                  if (pointerId == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
-                     // Send an event to the end of the drag gesture.
-                     sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                        // Send an event to the end of the drag gesture.
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                  }
             } break;
             case MotionEvent.ACTION_UP: {
                 mAms.onTouchInteractionEnd();
                 // Announce the end of a new touch interaction.
-                sendAccessibilityEvent(
+                mDispatcher.sendAccessibilityEvent(
                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
                 final int pointerId = event.getPointerId(event.getActionIndex());
                 if (pointerId == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
                     // Send an event to the end of the drag gesture.
-                    sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                 }
             } break;
         }
@@ -751,16 +748,18 @@
             }
             case MotionEvent.ACTION_UP: {
                 // Deliver the event.
-                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
 
                 // Announce the end of a the touch interaction.
                 mAms.onTouchInteractionEnd();
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+                mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
             } break;
             default: {
-                // Deliver the event.
-                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                    // Deliver the event.
+                mDispatcher.sendMotionEvent(
+                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
             }
         }
     }
@@ -769,57 +768,15 @@
         mAms.onTouchInteractionEnd();
 
         // Announce the end of the gesture recognition.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
         // Don't announce the end of a the touch interaction if users didn't lift their fingers.
         if (interactionEnd) {
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
         }
 
         mExitGestureDetectionModeDelayed.cancel();
     }
 
-    /**
-     * Sends an accessibility event of the given type.
-     *
-     * @param type The event type.
-     */
-    private void sendAccessibilityEvent(int type) {
-        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
-        if (accessibilityManager.isEnabled()) {
-            AccessibilityEvent event = AccessibilityEvent.obtain(type);
-            event.setWindowId(mAms.getActiveWindowId());
-            accessibilityManager.sendAccessibilityEvent(event);
-            if (DEBUG) {
-                Slog.d(
-                        LOG_TAG,
-                        "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
-            }
-        }
-        mState.onInjectedAccessibilityEvent(type);
-    }
-
-    /**
-     * Sends down events to the view hierarchy for all pointers which are
-     * not already being delivered i.e. pointers that are not yet injected.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
-
-        // Inject the injected pointers.
-        int pointerIdBits = 0;
-        final int pointerCount = prototype.getPointerCount();
-        for (int i = 0; i < pointerCount; i++) {
-            final int pointerId = prototype.getPointerId(i);
-            // Do not send event for already delivered pointers.
-            if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
-                pointerIdBits |= (1 << pointerId);
-                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
-                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
-            }
-        }
-    }
 
     /**
      * Sends the exit events if needed. Such events are hover exit and touch explore
@@ -828,13 +785,14 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
-        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
             if (!mSendTouchExplorationEndDelayed.isPending()) {
                 mSendTouchExplorationEndDelayed.post();
             }
-            sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+            mDispatcher.sendMotionEvent(
+                    event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
         }
     }
 
@@ -845,115 +803,14 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
-        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
-            sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
+            mDispatcher.sendMotionEvent(
+                    event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
         }
     }
 
-    /**
-     * Sends up events to the view hierarchy for all pointers which are
-     * already being delivered i.e. pointers that are injected.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
-        int pointerIdBits = 0;
-        final int pointerCount = prototype.getPointerCount();
-        for (int i = 0; i < pointerCount; i++) {
-            final int pointerId = prototype.getPointerId(i);
-            // Skip non injected down pointers.
-            if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
-                continue;
-            }
-            pointerIdBits |= (1 << pointerId);
-            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
-            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
-        }
-    }
-
-    /**
-     * Sends an event.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param action The action of the event.
-     * @param pointerIdBits The bits of the pointers to send.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
-            int policyFlags) {
-        prototype.setAction(action);
-
-        MotionEvent event = null;
-        if (pointerIdBits == ALL_POINTER_ID_BITS) {
-            event = prototype;
-        } else {
-            try {
-                event = prototype.split(pointerIdBits);
-            } catch (IllegalArgumentException e) {
-                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
-                return;
-            }
-        }
-        if (action == MotionEvent.ACTION_DOWN) {
-            event.setDownTime(event.getEventTime());
-        } else {
-            event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
-        }
-        if (DEBUG) {
-            Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
-                    + Integer.toHexString(policyFlags));
-        }
-
-        // 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.
-        super.onMotionEvent(event, null, policyFlags);
-
-        mInjectedPointerTracker.onMotionEvent(event);
-
-        if (event != prototype) {
-            event.recycle();
-        }
-    }
-
-    /**
-     * Computes the action for an injected event based on a masked action
-     * and a pointer index.
-     *
-     * @param actionMasked The masked action.
-     * @param pointerIndex The index of the pointer which has changed.
-     * @return The action to be used for injection.
-     */
-    private int computeInjectionAction(int actionMasked, int pointerIndex) {
-        switch (actionMasked) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                // Compute the action based on how many down pointers are injected.
-                if (mInjectedPointerTracker.getInjectedPointerDownCount() == 0) {
-                    return MotionEvent.ACTION_DOWN;
-                } else {
-                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
-                        | MotionEvent.ACTION_POINTER_DOWN;
-                }
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                // Compute the action based on how many down pointers are injected.
-                if (mInjectedPointerTracker.getInjectedPointerDownCount() == 1) {
-                    return MotionEvent.ACTION_UP;
-                } else {
-                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
-                        | MotionEvent.ACTION_POINTER_UP;
-                }
-            }
-            default:
-                return actionMasked;
-        }
-    }
 
     /**
      * Determines whether a two pointer gesture is a dragging one.
@@ -978,10 +835,34 @@
                 MAX_DRAGGING_ANGLE_COS);
     }
 
+    /**
+     * Adjust the location of an injected event when performing a drag The new location will be in
+     * between the two fingers touching the screen.
+     */
+    private void adjustEventLocationForDrag(MotionEvent event) {
+
+        final float firstPtrX = event.getX(0);
+        final float firstPtrY = event.getY(0);
+        final float secondPtrX = event.getX(1);
+        final float secondPtrY = event.getY(1);
+        final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+        final float deltaX =
+                (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
+        final float deltaY =
+                (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
+        event.offsetLocation(deltaX / 2, deltaY / 2);
+    }
+
     public TouchState getState() {
         return mState;
     }
 
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mDispatcher.setReceiver(next);
+        super.setNext(next);
+    }
+
     /**
      * Class for delayed exiting from gesture detecting mode.
      */
@@ -998,7 +879,7 @@
         @Override
         public void run() {
             // Announce the end of gesture recognition.
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
             clear();
         }
     }
@@ -1055,11 +936,12 @@
 
         public void run() {
             // Send an accessibility event to announce the touch exploration start.
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
+            mDispatcher.sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
 
             if (!mEvents.isEmpty()) {
                 // Deliver a down event.
-                sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
+                mDispatcher.sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
                         mPointerIdBits, mPolicyFlags);
                 if (DEBUG) {
                     Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1069,7 +951,7 @@
                 // Deliver move events.
                 final int eventCount = mEvents.size();
                 for (int i = 1; i < eventCount; i++) {
-                    sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
+                    mDispatcher.sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
                             mPointerIdBits, mPolicyFlags);
                     if (DEBUG) {
                         Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1129,7 +1011,7 @@
                 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:"
                         + " ACTION_HOVER_EXIT");
             }
-            sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
+            mDispatcher.sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
                     mPointerIdBits, mPolicyFlags);
             if (!mSendTouchExplorationEndDelayed.isPending()) {
                 mSendTouchExplorationEndDelayed.cancel();
@@ -1173,7 +1055,7 @@
 
         @Override
         public void run() {
-            sendAccessibilityEvent(mEventType);
+            mDispatcher.sendAccessibilityEvent(mEventType);
         }
     }
 
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 17e969a..49938fa 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -18,6 +18,8 @@
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
 import android.annotation.IntDef;
 import android.util.Slog;
 import android.view.MotionEvent;
@@ -29,14 +31,12 @@
  * dispatch.
  */
 public class TouchState {
-
-    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "TouchState";
     // Pointer-related constants
     // This constant captures the current implementation detail that
     // pointer IDs are between 0 and 31 inclusive (subject to change).
     // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
-    private static final int MAX_POINTER_COUNT = 32;
+    static final int MAX_POINTER_COUNT = 32;
     // Constant referring to the ids bits of all pointers.
     public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
 
@@ -71,13 +71,9 @@
     // Helper class to track received pointers.
     // Todo: collapse or hide this class so multiple classes don't modify it.
     private final ReceivedPointerTracker mReceivedPointerTracker;
-    // Helper class to track injected pointers.
-    // Todo: collapse or hide this class so multiple classes don't modify it.
-    private final InjectedPointerTracker mInjectedPointerTracker;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
-        mInjectedPointerTracker = new InjectedPointerTracker();
     }
 
     /** Clears the internal shared state. */
@@ -85,16 +81,6 @@
         setState(STATE_CLEAR);
         // Reset the pointer trackers.
         mReceivedPointerTracker.clear();
-        mInjectedPointerTracker.clear();
-    }
-
-    /**
-     * Updates the state in response to a hover event dispatched by TouchExplorer.
-     *
-     * @param event The event sent from TouchExplorer.
-     */
-    public void onInjectedMotionEvent(MotionEvent event) {
-        mInjectedPointerTracker.onMotionEvent(event);
     }
 
     /**
@@ -226,117 +212,10 @@
         }
     }
 
-    public InjectedPointerTracker getInjectedPointerTracker() {
-        return mInjectedPointerTracker;
-    }
-
     public ReceivedPointerTracker getReceivedPointerTracker() {
         return mReceivedPointerTracker;
     }
 
-    /** This class tracks the up/down state of each pointer. It does not track movement. */
-    class InjectedPointerTracker {
-        private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
-
-        // Keep track of which pointers sent to the system are down.
-        private int mInjectedPointersDown;
-
-        // The time of the last injected down.
-        private long mLastInjectedDownEventTime;
-
-        // The last injected hover event.
-        private MotionEvent mLastInjectedHoverEvent;
-
-        /**
-         * Processes an injected {@link MotionEvent} event.
-         *
-         * @param event The event to process.
-         */
-        public void onMotionEvent(MotionEvent event) {
-            final int action = event.getActionMasked();
-            final int pointerId = event.getPointerId(event.getActionIndex());
-            final int pointerFlag = (1 << pointerId);
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    mInjectedPointersDown |= pointerFlag;
-                    mLastInjectedDownEventTime = event.getDownTime();
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_POINTER_UP:
-                    mInjectedPointersDown &= ~pointerFlag;
-                    if (mInjectedPointersDown == 0) {
-                        mLastInjectedDownEventTime = 0;
-                    }
-                    break;
-                case MotionEvent.ACTION_HOVER_ENTER:
-                case MotionEvent.ACTION_HOVER_MOVE:
-                case MotionEvent.ACTION_HOVER_EXIT:
-                    if (mLastInjectedHoverEvent != null) {
-                        mLastInjectedHoverEvent.recycle();
-                    }
-                    mLastInjectedHoverEvent = MotionEvent.obtain(event);
-                    break;
-            }
-            if (DEBUG) {
-                Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
-            }
-        }
-
-        /** Clears the internals state. */
-        public void clear() {
-            mInjectedPointersDown = 0;
-        }
-
-        /** @return The time of the last injected down event. */
-        public long getLastInjectedDownEventTime() {
-            return mLastInjectedDownEventTime;
-        }
-
-        /** @return The number of down pointers injected to the view hierarchy. */
-        public int getInjectedPointerDownCount() {
-            return Integer.bitCount(mInjectedPointersDown);
-        }
-
-        /** @return The bits of the injected pointers that are down. */
-        public int getInjectedPointersDown() {
-            return mInjectedPointersDown;
-        }
-
-        /**
-         * Whether an injected pointer is down.
-         *
-         * @param pointerId The unique pointer id.
-         * @return True if the pointer is down.
-         */
-        public boolean isInjectedPointerDown(int pointerId) {
-            final int pointerFlag = (1 << pointerId);
-            return (mInjectedPointersDown & pointerFlag) != 0;
-        }
-
-        /** @return The the last injected hover event. */
-        public MotionEvent getLastInjectedHoverEvent() {
-            return mLastInjectedHoverEvent;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            builder.append("=========================");
-            builder.append("\nDown pointers #");
-            builder.append(Integer.bitCount(mInjectedPointersDown));
-            builder.append(" [ ");
-            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
-                if ((mInjectedPointersDown & i) != 0) {
-                    builder.append(i);
-                    builder.append(" ");
-                }
-            }
-            builder.append("]");
-            builder.append("\n=========================");
-            return builder.toString();
-        }
-    }
     /** 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";