Merge "Fix for ANRs due to InputMonitor" into tm-qpr-dev
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e40db4e..e7036c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -75,10 +75,10 @@
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
     private DesktopModeController mDesktopModeController;
-    private EventReceiver mEventReceiver;
-    private InputMonitor mInputMonitor;
     private boolean mTransitionDragActive;
 
+    private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
     private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
@@ -150,8 +150,15 @@
     @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
         if (decoration == null) return;
 
+        int oldDisplayId = decoration.mDisplay.getDisplayId();
+        if (taskInfo.displayId != oldDisplayId) {
+            removeTaskFromEventReceiver(oldDisplayId);
+            incrementEventReceiverTasks(taskInfo.displayId);
+        }
+
         decoration.relayout(taskInfo);
     }
 
@@ -195,6 +202,11 @@
         if (decoration == null) return;
 
         decoration.close();
+        int displayId = taskInfo.displayId;
+        if (mEventReceiversByDisplay.contains(displayId)) {
+            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+            removeTaskFromEventReceiver(displayId);
+        }
     }
 
     private class CaptionTouchEventListener implements
@@ -329,8 +341,12 @@
 
     // InputEventReceiver to listen for touch input outside of caption bounds
     class EventReceiver extends InputEventReceiver {
-        EventReceiver(InputChannel channel, Looper looper) {
+        private InputMonitor mInputMonitor;
+        private int mTasksOnDisplay;
+        EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
             super(channel, looper);
+            mInputMonitor = inputMonitor;
+            mTasksOnDisplay = 1;
         }
 
         @Override
@@ -338,15 +354,62 @@
             boolean handled = false;
             if (event instanceof MotionEvent) {
                 handled = true;
-                CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
+                CaptionWindowDecorViewModel.this
+                        .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
             }
             finishInputEvent(event, handled);
         }
+
+        @Override
+        public void dispose() {
+            if (mInputMonitor != null) {
+                mInputMonitor.dispose();
+                mInputMonitor = null;
+            }
+            super.dispose();
+        }
+
+        private void incrementTaskNumber() {
+            mTasksOnDisplay++;
+        }
+
+        private void decrementTaskNumber() {
+            mTasksOnDisplay--;
+        }
+
+        private int getTasksOnDisplay() {
+            return mTasksOnDisplay;
+        }
+    }
+
+    /**
+     * Check if an EventReceiver exists on a particular display.
+     * If it does, increment its task count. Otherwise, create one for that display.
+     * @param displayId the display to check against
+     */
+    private void incrementEventReceiverTasks(int displayId) {
+        if (mEventReceiversByDisplay.contains(displayId)) {
+            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+            eventReceiver.incrementTaskNumber();
+        } else {
+            createInputChannel(displayId);
+        }
+    }
+
+    // If all tasks on this display are gone, we don't need to monitor its input.
+    private void removeTaskFromEventReceiver(int displayId) {
+        if (!mEventReceiversByDisplay.contains(displayId)) return;
+        EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+        if (eventReceiver == null) return;
+        eventReceiver.decrementTaskNumber();
+        if (eventReceiver.getTasksOnDisplay() == 0) {
+            disposeInputChannel(displayId);
+        }
     }
 
     class EventReceiverFactory {
-        EventReceiver create(InputChannel channel, Looper looper) {
-            return new EventReceiver(channel, looper);
+        EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+            return new EventReceiver(inputMonitor, channel, looper);
         }
     }
 
@@ -355,14 +418,14 @@
      *
      * @param ev the {@link MotionEvent} received by {@link EventReceiver}
      */
-    private void handleReceivedMotionEvent(MotionEvent ev) {
+    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
         if (!DesktopModeStatus.isActive(mContext)) {
             handleCaptionThroughStatusBar(ev);
         }
         handleEventOutsideFocusedCaption(ev);
         // Prevent status bar from reacting to a caption drag.
         if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
-            mInputMonitor.pilferPointers();
+            inputMonitor.pilferPointers();
         }
     }
 
@@ -381,6 +444,7 @@
         }
     }
 
+
     /**
      * Perform caption actions if not able to through normal means.
      * Turn on desktop mode if handle is dragged below status bar.
@@ -434,6 +498,22 @@
         return focusedDecor;
     }
 
+    private void createInputChannel(int displayId) {
+        InputManager inputManager = mInputManagerSupplier.get();
+        InputMonitor inputMonitor =
+                inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+        EventReceiver eventReceiver = mEventReceiverFactory.create(
+                inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+        mEventReceiversByDisplay.put(displayId, eventReceiver);
+    }
+
+    private void disposeInputChannel(int displayId) {
+        EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+        if (eventReceiver != null) {
+            eventReceiver.dispose();
+        }
+    }
+
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.IS_SUPPORTED
@@ -472,14 +552,7 @@
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
         windowDecoration.relayout(taskInfo, startT, finishT);
-        if (mInputMonitor == null) {
-            InputManager inputManager = mInputManagerSupplier.get();
-            mInputMonitor =
-                    inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
-            mEventReceiver =
-                    mEventReceiverFactory.create(
-                            mInputMonitor.getInputChannel(), Looper.myLooper());
-        }
+        incrementEventReceiverTasks(taskInfo.displayId);
     }
 
     private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 8b134ed..9b37b97 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -111,7 +111,7 @@
             .create(any(), any(), any(), any(), any(), any(), any(), any());
 
         when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
-        when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+        when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
         when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
     }