Handle focused tasks on multiple displays

When the focused task changes displays, make sure to mark the whole
previous display as not part of a focused task.

This change also adds an adb am command to change the task focus.

Test: android.server.cts.ActivityManagerDisplayTests
Test: #testStackFocusSwitchOnTouchEvent
Bug: 35214007
Change-Id: I9cb7372c21a0b592abb6f6d910077ff5097dd6cf
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a06fa1b..80df26b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2031,6 +2031,8 @@
             return runTaskDragTaskTest(pw);
         } else if (op.equals("size-task-test")) {
             return runTaskSizeTaskTest(pw);
+        } else if (op.equals("focus")) {
+            return runTaskFocus(pw);
         } else {
             getErrPrintWriter().println("Error: unknown command '" + op + "'");
             return -1;
@@ -2322,6 +2324,13 @@
         return 0;
     }
 
+    int runTaskFocus(PrintWriter pw) throws RemoteException {
+        final int taskId = Integer.parseInt(getNextArgRequired());
+        pw.println("Setting focus to task " + taskId);
+        mInterface.setFocusedTask(taskId);
+        return 0;
+    }
+
     int runWrite(PrintWriter pw) {
         mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c45136c..2c315445 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1072,19 +1072,25 @@
     }
 
     void setTouchExcludeRegion(Task focusedTask) {
-        mTouchExcludeRegion.set(mBaseDisplayRect);
-        final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
-        mTmpRect2.setEmpty();
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
-            stack.setTouchExcludeRegion(
-                    focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
-        }
-        // If we removed the focused task above, add it back and only leave its
-        // outside touch area in the exclusion. TapDectector is not interested in
-        // any touch inside the focused task itself.
-        if (!mTmpRect2.isEmpty()) {
-            mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+        // The provided task is the task on this display with focus, so if WindowManagerService's
+        // focused app is not on this display, focusedTask will be null.
+        if (focusedTask == null) {
+            mTouchExcludeRegion.setEmpty();
+        } else {
+            mTouchExcludeRegion.set(mBaseDisplayRect);
+            final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+            mTmpRect2.setEmpty();
+            for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
+                final TaskStack stack = mTaskStackContainers.get(stackNdx);
+                stack.setTouchExcludeRegion(
+                        focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
+            }
+            // If we removed the focused task above, add it back and only leave its
+            // outside touch area in the exclusion. TapDectector is not interested in
+            // any touch inside the focused task itself.
+            if (!mTmpRect2.isEmpty()) {
+                mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
+            }
         }
         final WindowState inputMethod = mService.mInputMethodWindow;
         if (inputMethod != null && inputMethod.isVisibleLw()) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 80e6655..126e080 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -784,7 +784,7 @@
         if (updateInputWindowsNeeded) {
             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
         }
-        mService.setFocusTaskRegionLocked();
+        mService.setFocusTaskRegionLocked(null);
 
         // Check to see if we are now in a state where the screen should
         // be enabled, because the window obscured flags have changed.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 597e8b6..a2946db 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2574,13 +2574,18 @@
         }
     }
 
-    void setFocusTaskRegionLocked() {
+    void setFocusTaskRegionLocked(AppWindowToken previousFocus) {
         final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
-        if (focusedTask != null) {
-            final DisplayContent displayContent = focusedTask.getDisplayContent();
-            if (displayContent != null) {
-                displayContent.setTouchExcludeRegion(focusedTask);
-            }
+        final Task previousTask = previousFocus != null ? previousFocus.mTask : null;
+        final DisplayContent focusedDisplayContent =
+                focusedTask != null ? focusedTask.getDisplayContent() : null;
+        final DisplayContent previousDisplayContent =
+                previousTask != null ? previousTask.getDisplayContent() : null;
+        if (previousDisplayContent != null && previousDisplayContent != focusedDisplayContent) {
+            previousDisplayContent.setTouchExcludeRegion(null);
+        }
+        if (focusedDisplayContent != null) {
+            focusedDisplayContent.setTouchExcludeRegion(focusedTask);
         }
     }
 
@@ -2606,9 +2611,10 @@
 
             final boolean changed = mFocusedApp != newFocus;
             if (changed) {
+                AppWindowToken prev = mFocusedApp;
                 mFocusedApp = newFocus;
                 mInputMonitor.setFocusedAppLw(newFocus);
-                setFocusTaskRegionLocked();
+                setFocusTaskRegionLocked(prev);
             }
 
             if (moveFocusNow && changed) {
@@ -7439,7 +7445,7 @@
         synchronized (mWindowMap) {
             getDefaultDisplayContentLocked().getDockedDividerController()
                     .setTouchRegion(touchRegion);
-            setFocusTaskRegionLocked();
+            setFocusTaskRegionLocked(null);
         }
     }