Move the display targeted by a key event to top

In a single-focus multi-display system, if a key event targeting a
non-focused display, we move the display to top. And then send the
key event to the display when it become top-focused.

This change also updates the window manager lock.

Change-Id: I15e99fe09fe0912cdfa0295f7ae5835d2dd6072d
Fix: 121057974
Test: atest WindowFocusTests
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2060aef..c5434ea 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -565,6 +565,10 @@
     private boolean mScreenshotChordPowerKeyTriggered;
     private long mScreenshotChordPowerKeyTime;
 
+    private static final long MOVING_DISPLAY_TO_TOP_DURATION_MILLIS = 10;
+    private volatile boolean mMovingDisplayToTopKeyTriggered;
+    private volatile long mMovingDisplayToTopKeyTime;
+
     // Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up
     private int mRingerToggleChord = VOLUME_HUSH_OFF;
 
@@ -604,7 +608,7 @@
     private boolean mAodShowing;
 
     private boolean mPerDisplayFocusEnabled = false;
-    private int mTopFocusedDisplayId = INVALID_DISPLAY;
+    private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
 
     private static final int MSG_ENABLE_POINTER_LOCATION = 1;
     private static final int MSG_DISABLE_POINTER_LOCATION = 2;
@@ -632,6 +636,7 @@
     private static final int MSG_POWER_VERY_LONG_PRESS = 25;
     private static final int MSG_NOTIFY_USER_ACTIVITY = 26;
     private static final int MSG_RINGER_TOGGLE_CHORD = 27;
+    private static final int MSG_MOVE_DISPLAY_TO_TOP = 28;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -727,6 +732,10 @@
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
                     break;
+                case MSG_MOVE_DISPLAY_TO_TOP:
+                    mWindowManagerFuncs.moveDisplayToTop(msg.arg1);
+                    mMovingDisplayToTopKeyTriggered = false;
+                    break;
             }
         }
     }
@@ -2558,12 +2567,25 @@
         final int eventDisplayId = event.getDisplayId();
         if (result == 0 && !mPerDisplayFocusEnabled
                 && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
-            // Someone tries to send a key event to a display which doesn't have a focused window.
-            // We drop the event here, or it will cause ANR.
-            // TODO (b/121057974): The user may be confused about why the key doesn't work, so we
-            // may need to deal with this problem.
-            Slog.i(TAG, "Dropping this event targeting display #" + eventDisplayId
-                    + " because the focus is on display #" + mTopFocusedDisplayId);
+            // An event is targeting a non-focused display. Try to move the display to top so that
+            // it can become the focused display to interact with the user.
+            final long eventDownTime = event.getDownTime();
+            if (mMovingDisplayToTopKeyTime < eventDownTime) {
+                // We have not handled this event yet. Move the display to top, and then tell
+                // dispatcher to try again later.
+                mMovingDisplayToTopKeyTime = eventDownTime;
+                mMovingDisplayToTopKeyTriggered = true;
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(MSG_MOVE_DISPLAY_TO_TOP, eventDisplayId, 0));
+                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
+            } else if (mMovingDisplayToTopKeyTriggered) {
+                // The message has not been handled yet. Tell dispatcher to try again later.
+                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
+            }
+            // The target display is still not the top focused display. Drop the event because the
+            // display may not contain any window which can receive keys.
+            Slog.w(TAG, "Dropping key targeting non-focused display #" + eventDisplayId
+                    + " keyCode=" + KeyEvent.keyCodeToString(event.getKeyCode()));
             return -1;
         }
         return result;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3da325c..c37254b 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -634,6 +634,12 @@
          * Notifies window manager that user is switched.
          */
         void onUserSwitched();
+
+        /**
+         * Hint to window manager that the user is interacting with a display that should be treated
+         * as the top display.
+         */
+        void moveDisplayToTop(int displayId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 937c9d9..58cf73a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -47,10 +47,9 @@
 import android.view.IWindowSession;
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
-import android.view.Surface;
+import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
-import android.view.InsetsState;
 import android.view.WindowManager;
 
 import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -432,7 +431,7 @@
 
     @Override
     public void insetsModified(IWindow window, InsetsState state) {
-        synchronized (mService.mWindowMap) {
+        synchronized (mService.mGlobalLock) {
             final WindowState windowState = mService.windowForClientLocked(this, window,
                     false /* throwOnError */);
             if (windowState != null) {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index b219419..9057870 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -67,7 +67,7 @@
                     return;
                 }
                 WindowContainer parent = mDisplayContent.getParent();
-                if (parent != null) {
+                if (parent != null && parent.getTopChild() != mDisplayContent) {
                     parent.positionChildAt(WindowContainer.POSITION_TOP, mDisplayContent,
                             true /* includingParents */);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 25e61f8..1905877 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -437,6 +437,7 @@
                 if (mChildren.peekLast() != child) {
                     mChildren.remove(child);
                     mChildren.add(child);
+                    onChildPositionChanged();
                 }
                 if (includingParents && getParent() != null) {
                     getParent().positionChildAt(POSITION_TOP, this /* child */,
@@ -447,6 +448,7 @@
                 if (mChildren.peekFirst() != child) {
                     mChildren.remove(child);
                     mChildren.addFirst(child);
+                    onChildPositionChanged();
                 }
                 if (includingParents && getParent() != null) {
                     getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
@@ -460,8 +462,8 @@
                 //       doing this adjustment here and remove any adjustments in the callers.
                 mChildren.remove(child);
                 mChildren.add(position, child);
+                onChildPositionChanged();
         }
-        onChildPositionChanged();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 91aac7e..ea9a3e6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -723,7 +723,7 @@
 
         void updateSystemUiSettings() {
             boolean changed;
-            synchronized (mWindowMap) {
+            synchronized (mGlobalLock) {
                 changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext)
                         || PolicyControl.reloadFromSetting(mContext);
             }
@@ -2629,12 +2629,23 @@
     @Override
     public void onUserSwitched() {
         mSettingsObserver.updateSystemUiSettings();
-        synchronized (mWindowMap) {
+        synchronized (mGlobalLock) {
             // force a re-application of focused window sysui visibility on each display.
             mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemUiVisibilityLw);
         }
     }
 
+    @Override
+    public void moveDisplayToTop(int displayId) {
+        synchronized (mGlobalLock) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent != null && mRoot.getTopChild() != displayContent) {
+                mRoot.positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+                        true /* includingParents */);
+            }
+        }
+    }
+
     /**
      * Starts deferring layout passes. Useful when doing multiple changes but to optimize
      * performance, only one layout pass should be done. This can be called multiple times, and
@@ -6412,7 +6423,7 @@
     }
 
     void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
-        synchronized (mWindowMap) {
+        synchronized (mGlobalLock) {
             mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
         }
     }