Dispose WMS.DragInputEventReceiver on the correct thread

WindowManagerService.DragInputEventReceiver should be disposed
on the same handler where input is being handled. This is required
to avoid races between nativeFinishInputEvent and native dispose.

Bug: 26927018
Change-Id: Ib476dc0e8a8825e1df81f1557d48b619b7f9dd8c
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9d0fb61..d0167bb 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -94,10 +94,7 @@
     float mOriginalX, mOriginalY;
     float mCurrentX, mCurrentY;
     float mThumbOffsetX, mThumbOffsetY;
-    InputChannel mServerChannel, mClientChannel;
-    DragInputEventReceiver mInputEventReceiver;
-    InputApplicationHandle mDragApplicationHandle;
-    InputWindowHandle mDragWindowHandle;
+    InputInterceptor mInputInterceptor;
     WindowState mTargetWindow;
     ArrayList<WindowState> mNotifiedWindows;
     boolean mDragInProgress;
@@ -130,16 +127,13 @@
         mNotifiedWindows = null;
     }
 
-    /**
-     * @param display The Display that the window being dragged is on.
-     */
-    void register(Display display) {
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
-        if (mClientChannel != null) {
-            Slog.e(TAG_WM, "Duplicate register of drag input channel");
-        } else {
-            mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId());
+    class InputInterceptor {
+        InputChannel mServerChannel, mClientChannel;
+        DragInputEventReceiver mInputEventReceiver;
+        InputApplicationHandle mDragApplicationHandle;
+        InputWindowHandle mDragWindowHandle;
 
+        InputInterceptor(Display display) {
             InputChannel[] channels = InputChannel.openInputChannelPair("drag");
             mServerChannel = channels[0];
             mClientChannel = channels[1];
@@ -188,13 +182,8 @@
             }
             mService.pauseRotationLocked();
         }
-    }
 
-    void unregister() {
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
-        if (mClientChannel == null) {
-            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
-        } else {
+        void tearDown() {
             mService.mInputManager.unregisterInputChannel(mServerChannel);
             mInputEventReceiver.dispose();
             mInputEventReceiver = null;
@@ -214,6 +203,40 @@
         }
     }
 
+    InputChannel getInputChannel() {
+        return mInputInterceptor == null ? null : mInputInterceptor.mServerChannel;
+    }
+
+    InputWindowHandle getInputWindowHandle() {
+        return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
+    }
+
+    /**
+     * @param display The Display that the window being dragged is on.
+     */
+    void register(Display display) {
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
+        if (mInputInterceptor != null) {
+            Slog.e(TAG_WM, "Duplicate register of drag input channel");
+        } else {
+            mInputInterceptor = new InputInterceptor(display);
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+    }
+
+    void unregister() {
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
+        if (mInputInterceptor == null) {
+            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
+        } else {
+            // Input channel should be disposed on the thread where the input is being handled.
+            mService.mH.obtainMessage(
+                    H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
+            mInputInterceptor = null;
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+    }
+
     int getDragLayerLw() {
         return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
@@ -397,8 +420,6 @@
         // free our resources and drop all the object references
         reset();
         mService.mDragState = null;
-
-        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
     }
 
     void notifyMoveLw(float x, float y) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b9c55a5..535ce9d 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -249,7 +249,7 @@
             if (DEBUG_DRAG) {
                 Log.d(TAG_WM, "Inserting drag window");
             }
-            final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle;
+            final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle();
             if (dragWindowHandle != null) {
                 addInputWindowHandleLw(dragWindowHandle);
             } else {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 08c0a4b..cb99461 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,16 +324,16 @@
             }
             Display display = displayContent.getDisplay();
             mService.mDragState.register(display);
-            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
             if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                    mService.mDragState.mServerChannel)) {
+                    mService.mDragState.getInputChannel())) {
                 Slog.e(TAG_WM, "Unable to transfer touch focus");
                 mService.mDragState.unregister();
+                mService.mDragState.reset();
                 mService.mDragState = null;
-                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
                 return false;
             }
 
+            mService.mDragState.mDisplayContent = displayContent;
             mService.mDragState.mData = data;
             mService.mDragState.broadcastDragStartedLw(touchX, touchY);
             mService.mDragState.overridePointerIconLw(touchSource);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8f7896e..1bcab99 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -771,9 +771,16 @@
         public void onInputEvent(InputEvent event) {
             boolean handled = false;
             try {
+                if (mDragState == null) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    handled = true;
+                    return;
+                }
                 if (event instanceof MotionEvent
                         && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
-                        && mDragState != null && !mMuteInput) {
+                        && !mMuteInput) {
                     final MotionEvent motionEvent = (MotionEvent)event;
                     boolean endDrag = false;
                     final float newX = motionEvent.getRawX();
@@ -833,6 +840,8 @@
                         if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
                         // tell all the windows that the drag has ended
                         synchronized (mWindowMap) {
+                            // endDragLw will post back to looper to dispose the receiver
+                            // since we still need the receiver for the last finishInputEvent.
                             mDragState.endDragLw();
                         }
                         mStylusButtonDownAtStart = false;
@@ -7099,6 +7108,7 @@
 
         public static final int RESIZE_STACK = 42;
         public static final int RESIZE_TASK = 43;
+        public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
 
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
 
@@ -7511,7 +7521,6 @@
                         // !!! TODO: ANR the app that has failed to start the drag in time
                         if (mDragState != null) {
                             mDragState.unregister();
-                            mInputMonitor.updateInputWindowsLw(true /*force*/);
                             mDragState.reset();
                             mDragState = null;
                         }
@@ -7534,6 +7543,17 @@
                     break;
                 }
 
+                case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+                    DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
+                    if (interceptor != null) {
+                        synchronized (mWindowMap) {
+                            interceptor.tearDown();
+                        }
+                    }
+                }
+                break;
+
                 case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
                     notifyHardKeyboardStatusChange();
                     break;