Give SystemUI a chance to participate in display rotation

This adds support for registering a single DisplayRotationController
to WMS. It gives a chance for the controller to suggest some
task changes to be executed along with a display rotation. There
is only one because it's a 2-way communication and there is only
intended to be one client for now.

This allows us to move Split and PiP presentation/layout logic out
of WM into systemui because WM no-longer needs to be the one
calculating the new bounds of everything during rotation.

This uses the windowcontainer transaction because all the
configuration changes and the display rotation need to happen
synchronously; otherwise, relayouts can occur after the display
is rotated, but before the configuration changes are applied.
When this happens, apps get incorrect bounds/insets which can
trigger erroneous lifecycle events in the app.

The flow is like this:
1. DisplayContent/Rotation freezes screen
2. DisplayRotationController is notified of a rotation and provided a
   callback.
3. The Controller then evaluates/queues some task changes in
   a transaction and, when done, fires the provided callback.
4. The callback applies the config change transaction and continues
   the rest of the rotation synchronously.

The DisplayWindowController is sys-ui piece that serves as an
interface between system-ui components and display-related wm
logic. For now it just facilitates the rotation calculation, but
in the future it will have more general utility for display logic
like inset/bounds calculation.

Bug: 124011688
Bug: 133381284
Test: Added some wmtests and coretests.
Change-Id: If10695f44fa076725ba17746842f6fbd64da5d20
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ab9a7ca..9f4143f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -26,6 +26,7 @@
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.Surface.ROTATION_90;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@@ -62,6 +63,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 
 import android.annotation.SuppressLint;
@@ -70,11 +72,14 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.metrics.LogMaker;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.view.DisplayCutout;
 import android.view.Gravity;
+import android.view.IDisplayWindowRotationCallback;
+import android.view.IDisplayWindowRotationController;
 import android.view.ISystemGestureExclusionListener;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -656,7 +661,7 @@
 
         portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
         assertFalse(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
-        portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
+        portraitDisplay.getDisplayRotation().setRotation(ROTATION_90);
         assertTrue(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
 
         final DisplayContent landscapeDisplay = createNewDisplay();
@@ -665,7 +670,7 @@
 
         landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
         assertTrue(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
-        landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_90);
+        landscapeDisplay.getDisplayRotation().setRotation(ROTATION_90);
         assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
     }
 
@@ -917,6 +922,45 @@
                 is(Configuration.ORIENTATION_PORTRAIT));
     }
 
+    @Test
+    public void testRemoteRotation() {
+        DisplayContent dc = createNewDisplay();
+
+        final DisplayRotation dr = dc.getDisplayRotation();
+        Mockito.doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
+        Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt());
+        final boolean[] continued = new boolean[1];
+        spyOn(dc.mActivityDisplay);
+        Mockito.doAnswer(
+                invocation -> {
+                    continued[0] = true;
+                    return true;
+                }).when(dc.mActivityDisplay).updateDisplayOverrideConfigurationLocked();
+        final boolean[] called = new boolean[1];
+        mWm.mDisplayRotationController =
+                new IDisplayWindowRotationController.Stub() {
+                    @Override
+                    public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
+                            IDisplayWindowRotationCallback callback) {
+                        called[0] = true;
+
+                        try {
+                            callback.continueRotateDisplay(toRotation, null);
+                        } catch (RemoteException e) {
+                            assertTrue(false);
+                        }
+                    }
+                };
+
+        // kill any existing rotation animation (vestigial from test setup).
+        dc.setRotationAnimation(null);
+
+        mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
+        assertTrue(called[0]);
+        waitUntilHandlersIdle();
+        assertTrue(continued[0]);
+    }
+
     private boolean isOptionsPanelAtRight(int displayId) {
         return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
     }