Support rotation on secondary displays (1/N)

- Allow to apply rotation animation on non-default display.
- Separate by display:
    Rotation related timeout.
    Pause/resume rotation.
- Able to get/watch non-default display orientation/rotation.

Bug: 111361251
Test: atest FrameworksServicesTests:DisplayContentTests
Test: go/wm-smoke
Change-Id: I9533f1b90b9969d455b6dc235c5318e39f63ab12
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0a77269..75b3556 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2165,6 +2165,24 @@
         }
 
         @Override
+        public boolean screenshot(int displayId, Surface outSurface) {
+            synchronized (mSyncRoot) {
+                final LogicalDisplay display = mLogicalDisplays.get(displayId);
+                if (display != null) {
+                    final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+                    if (device != null) {
+                        final IBinder token = device.getDisplayTokenLocked();
+                        if (token != null) {
+                            SurfaceControl.screenshot(token, outSurface);
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+
+        @Override
         public DisplayInfo getDisplayInfo(int displayId) {
             return getDisplayInfoInternal(displayId, Process.myUid());
         }
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 1977e12..fff1fa4 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -56,6 +56,7 @@
                     .setParent(null) // TODO: Work-around for b/69259549
                     .build();
 
+            transaction.setLayerStack(surface, dc.getDisplayId());
             transaction.setAlpha(surface, 1);
             transaction.setLayer(surface, layer);
             transaction.show(surface);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0ca8e1a..efe4d6f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -303,6 +303,7 @@
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
     int pendingLayoutChanges;
+    int mDeferredRotationPauseCount;
     // TODO(multi-display): remove some of the usages.
     boolean isDefaultDisplay;
     /**
@@ -942,6 +943,36 @@
     }
 
     /**
+     * Temporarily pauses rotation changes until resumed.
+     *
+     * This can be used to prevent rotation changes from occurring while the user is
+     * performing certain operations, such as drag and drop.
+     *
+     * This call nests and must be matched by an equal number of calls to
+     * {@link #resumeRotationLocked}.
+     */
+    void pauseRotationLocked() {
+        mDeferredRotationPauseCount++;
+    }
+
+    /**
+     * Resumes normal rotation changes after being paused.
+     */
+    void resumeRotationLocked() {
+        if (mDeferredRotationPauseCount <= 0) {
+            return;
+        }
+
+        mDeferredRotationPauseCount--;
+        if (mDeferredRotationPauseCount == 0) {
+            final boolean changed = updateRotationUnchecked();
+            if (changed) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, mDisplayId).sendToTarget();
+            }
+        }
+    }
+
+    /**
      * Update rotation of the display.
      *
      * @return {@code true} if the rotation has been changed.  In this case YOU MUST CALL
@@ -964,7 +995,7 @@
     boolean updateRotationUnchecked(boolean forceUpdate) {
         ScreenRotationAnimation screenRotationAnimation;
         if (!forceUpdate) {
-            if (mService.mDeferredRotationPauseCount > 0) {
+            if (mDeferredRotationPauseCount > 0) {
                 // Rotation updates have been paused temporarily.  Defer the update until
                 // updates have been resumed.
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused.");
@@ -1065,9 +1096,8 @@
         }
 
         mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
-        mService.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT);
-        mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
-                WINDOW_FREEZE_TIMEOUT_DURATION);
+        mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
+                this, WINDOW_FREEZE_TIMEOUT_DURATION);
 
         setLayoutNeeded();
         final int[] anim = new int[2];
@@ -1124,9 +1154,8 @@
         }, true /* traverseTopToBottom */);
 
         if (rotateSeamlessly) {
-            mService.mH.removeMessages(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT);
-            mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT,
-                    SEAMLESS_ROTATION_TIMEOUT_DURATION);
+            mService.mH.sendNewMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT,
+                    this, SEAMLESS_ROTATION_TIMEOUT_DURATION);
         }
 
         for (int i = mService.mRotationWatchers.size() - 1; i >= 0; i--) {
@@ -2282,6 +2311,8 @@
 
         pw.println();
         pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
+        pw.print(prefix);
+        pw.print("mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
 
         pw.println();
         pw.println(prefix + "Application tokens in top down Z order:");
@@ -2876,7 +2907,7 @@
                 mWallpaperController.adjustWallpaperWindows(this);
             }
 
-            if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
+            if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
                 if (mService.updateOrientationFromAppTokensLocked(mDisplayId)) {
                     setLayoutNeeded();
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index fc370d9..f42e979 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -151,6 +151,7 @@
                     mDragState.mUid = callerUid;
                     mDragState.mOriginalAlpha = alpha;
                     mDragState.mToken = dragToken;
+                    mDragState.mDisplayContent = displayContent;
 
                     final Display display = displayContent.getDisplay();
                     if (!mCallback.get().registerInputChannel(
@@ -160,7 +161,6 @@
                         return null;
                     }
 
-                    mDragState.mDisplayContent = displayContent;
                     mDragState.mData = data;
                     mDragState.broadcastDragStartedLocked(touchX, touchY);
                     mDragState.overridePointerIconLocked(touchSource);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 1ac9b88..d4046e9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -255,7 +255,7 @@
             if (DEBUG_ORIENTATION) {
                 Slog.d(TAG_WM, "Pausing rotation during drag");
             }
-            mService.pauseRotationLocked();
+            mDisplayContent.pauseRotationLocked();
         }
 
         void tearDown() {
@@ -274,7 +274,7 @@
             if (DEBUG_ORIENTATION) {
                 Slog.d(TAG_WM, "Resuming rotation after drag");
             }
-            mService.resumeRotationLocked();
+            mDisplayContent.resumeRotationLocked();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a709c55..2be4001 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -746,22 +746,7 @@
 
         if (mUpdateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
-            // TODO(multi-display): Update rotation for different displays separately.
-            final int displayId = defaultDisplay.getDisplayId();
-            if (defaultDisplay.updateRotationUnchecked()) {
-                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
-            } else {
-                mUpdateRotation = false;
-            }
-            // Update rotation of VR virtual display separately. Currently this is the only kind of
-            // secondary display that can be rotated because of the single-display limitations in
-            // PhoneWindowManager.
-            final DisplayContent vrDisplay = mService.mVr2dDisplayId != INVALID_DISPLAY
-                    ? getDisplayContent(mService.mVr2dDisplayId) : null;
-            if (vrDisplay != null && vrDisplay.updateRotationUnchecked()) {
-                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, mService.mVr2dDisplayId)
-                        .sendToTarget();
-            }
+            mUpdateRotation = updateRotationUnchecked();
         }
 
         if (mService.mWaitingForDrawnCallback != null ||
@@ -958,6 +943,19 @@
         return displayHasContent;
     }
 
+    boolean updateRotationUnchecked() {
+        boolean changed = false;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent displayContent = mChildren.get(i);
+            if (displayContent.updateRotationUnchecked()) {
+                changed = true;
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
+                        .sendToTarget();
+            }
+        }
+        return changed;
+    }
+
     boolean copyAnimToLayoutParams() {
         boolean doRequest = false;
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index fa8a5c6..2f189a6 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -268,24 +268,18 @@
                     .setSecure(isSecure)
                     .build();
 
-            // capture a screenshot into the surface we just created
-            // TODO(multidisplay): we should use the proper display
-            final int displayId = SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN;
-            final IBinder displayHandle = SurfaceControl.getBuiltInDisplay(displayId);
-            // This null check below is to guard a race condition where WMS didn't have a chance to
-            // respond to display disconnection before handling rotation , that surfaceflinger may
-            // return a null handle here because it doesn't think that display is valid anymore.
-            if (displayHandle != null) {
-                Surface sur = new Surface();
-                sur.copyFrom(mSurfaceControl);
-                SurfaceControl.screenshot(displayHandle, sur);
+            // Capture a screenshot into the surface we just created.
+            final int displayId = display.getDisplayId();
+            final Surface surface = new Surface();
+            surface.copyFrom(mSurfaceControl);
+            if (mService.mDisplayManagerInternal.screenshot(displayId, surface)) {
                 t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
                 t.setAlpha(mSurfaceControl, 0);
                 t.show(mSurfaceControl);
-                sur.destroy();
             } else {
-                Slog.w(TAG, "Built-in display " + displayId + " is null.");
+                Slog.w(TAG, "Unable to take screenshot of display " + displayId);
             }
+            surface.destroy();
         } catch (OutOfResourcesException e) {
             Slog.w(TAG, "Unable to allocate freeze surface", e);
         }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 30f46a0..bd7e61c 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -97,7 +97,7 @@
     private final WindowManagerService mService;
     private final IActivityTaskManager mActivityManager;
     private WindowPositionerEventReceiver mInputEventReceiver;
-    private Display mDisplay;
+    private DisplayContent mDisplayContent;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private Rect mTmpRect = new Rect();
     private int mSideMargin;
@@ -250,8 +250,8 @@
             return;
         }
 
-        mDisplay = display;
-        mDisplay.getMetrics(mDisplayMetrics);
+        mDisplayContent = displayContent;
+        display.getMetrics(mDisplayMetrics);
         final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
         mServerChannel = channels[0];
         mClientChannel = channels[1];
@@ -267,7 +267,7 @@
                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
-                mDisplay.getDisplayId());
+                display.getDisplayId());
         mDragWindowHandle.name = TAG;
         mDragWindowHandle.inputChannel = mServerChannel;
         mDragWindowHandle.layer = mService.getDragLayerLocked();
@@ -292,7 +292,7 @@
         mDragWindowHandle.frameLeft = 0;
         mDragWindowHandle.frameTop = 0;
         final Point p = new Point();
-        mDisplay.getRealSize(p);
+        display.getRealSize(p);
         mDragWindowHandle.frameRight = p.x;
         mDragWindowHandle.frameBottom = p.y;
 
@@ -300,12 +300,12 @@
         if (DEBUG_ORIENTATION) {
             Slog.d(TAG, "Pausing rotation during re-position");
         }
-        mService.pauseRotationLocked();
+        mDisplayContent.pauseRotationLocked();
 
         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
-        mDisplay.getRealSize(mMaxVisibleSize);
+        display.getRealSize(mMaxVisibleSize);
 
         mDragEnded = false;
     }
@@ -331,14 +331,14 @@
 
         mDragWindowHandle = null;
         mDragApplicationHandle = null;
-        mDisplay = null;
         mDragEnded = true;
 
         // Resume rotations after a drag.
         if (DEBUG_ORIENTATION) {
             Slog.d(TAG, "Resuming rotation after re-position");
         }
-        mService.resumeRotationLocked();
+        mDisplayContent.resumeRotationLocked();
+        mDisplayContent = null;
     }
 
     void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54703b3..f09ef86 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -583,7 +583,6 @@
     }
 
     ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>();
-    int mDeferredRotationPauseCount;
     final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
             new WallpaperVisibilityListeners();
 
@@ -3820,37 +3819,6 @@
         updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
     }
 
-    /**
-     * Temporarily pauses rotation changes until resumed.
-     *
-     * This can be used to prevent rotation changes from occurring while the user is
-     * performing certain operations, such as drag and drop.
-     *
-     * This call nests and must be matched by an equal number of calls to
-     * {@link #resumeRotationLocked}.
-     */
-    void pauseRotationLocked() {
-        mDeferredRotationPauseCount += 1;
-    }
-
-    /**
-     * Resumes normal rotation changes after being paused.
-     */
-    void resumeRotationLocked() {
-        if (mDeferredRotationPauseCount > 0) {
-            mDeferredRotationPauseCount -= 1;
-            if (mDeferredRotationPauseCount == 0) {
-                // TODO(multi-display): Update rotation for different displays separately.
-                final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                final boolean changed = displayContent.updateRotationUnchecked();
-                if (changed) {
-                    mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId())
-                            .sendToTarget();
-                }
-            }
-        }
-    }
-
     private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
         if(DEBUG_ORIENTATION) Slog.v(TAG_WM, "updateRotationUnchecked:"
                 + " alwaysSendConfiguration=" + alwaysSendConfiguration
@@ -3904,6 +3872,15 @@
 
     @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
+        final DisplayContent displayContent;
+        synchronized (mWindowMap) {
+            displayContent = mRoot.getDisplayContent(displayId);
+        }
+        if (displayContent == null) {
+            throw new IllegalArgumentException("Trying to register rotation event "
+                    + "for invalid display: " + displayId);
+        }
+
         final IBinder watcherBinder = watcher.asBinder();
         IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
             @Override
@@ -3931,7 +3908,7 @@
                 // Client died, no cleanup needed.
             }
 
-            return getDefaultDisplayRotation();
+            return displayContent.getRotation();
         }
     }
 
@@ -4716,9 +4693,9 @@
                 } break;
 
                 case WINDOW_FREEZE_TIMEOUT: {
-                    // TODO(multidisplay): Can non-default displays rotate?
+                    final DisplayContent displayContent = (DisplayContent) msg.obj;
                     synchronized (mWindowMap) {
-                        getDefaultDisplayContentLocked().onWindowFreezeTimeout();
+                        displayContent.onWindowFreezeTimeout();
                     }
                     break;
                 }
@@ -5031,11 +5008,9 @@
                 }
                 break;
                 case SEAMLESS_ROTATION_TIMEOUT: {
-                    // Rotation only supported on primary display.
-                    // TODO(multi-display)
-                    synchronized(mWindowMap) {
-                        final DisplayContent dc = getDefaultDisplayContentLocked();
-                        dc.onSeamlessRotationTimeout();
+                    final DisplayContent displayContent = (DisplayContent) msg.obj;
+                    synchronized (mWindowMap) {
+                        displayContent.onSeamlessRotationTimeout();
                     }
                 }
                 break;
@@ -5075,6 +5050,12 @@
                 Slog.v(TAG_WM, "handleMessage: exit");
             }
         }
+
+        /** Remove the previous messages with the same 'what' and 'obj' then send the new one. */
+        void sendNewMessageDelayed(int what, Object obj, long delayMillis) {
+            removeMessages(what, obj);
+            sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+        }
     }
 
     void destroyPreservedSurfaceLocked() {
@@ -5538,8 +5519,7 @@
                 mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
                 // XXX should probably keep timeout from
                 // when we first froze the display.
-                mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
-                mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
+                mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
                         WINDOW_FREEZE_TIMEOUT_DURATION);
             }
         }
@@ -5786,8 +5766,7 @@
         }
 
         mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
-        // TODO(multidisplay): rotation on non-default displays
-        if (CUSTOM_SCREEN_ROTATION && displayContent.isDefaultDisplay) {
+        if (CUSTOM_SCREEN_ROTATION) {
             mExitAnimId = exitAnim;
             mEnterAnimId = enterAnim;
             ScreenRotationAnimation screenRotationAnimation =
@@ -6472,7 +6451,6 @@
                     pw.print(defaultDisplayContent.getLastWindowForcedOrientation());
                     pw.print(" mLastOrientation=");
                             pw.println(defaultDisplayContent.getLastOrientation());
-            pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
             pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
                     pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
                     pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);