Fixed rotation: home and recents animation

Add support for home and recents animations using the fixed
orientation transform.

When the recent animation is started, the target ActivityRecord is
rotated before being displayed.

Bug: 143053092, 146416687, 147469164
Test: atest WmTests:RecentsAnimationControllerTest
                #testRecentViewInFixedPortraitWhenTopAppInLandscape

Change-Id: I194673d621e8c0e8222306915a60f69e4de53b75
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 840abb1..1b21117 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
@@ -84,9 +83,6 @@
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
-import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
-import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
-import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -105,12 +101,15 @@
 import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER;
 import static com.android.server.wm.DisplayContentProto.DPI;
 import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
+import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
 import static com.android.server.wm.DisplayContentProto.ID;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
 import static com.android.server.wm.DisplayContentProto.TASKS;
 import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -1553,6 +1552,20 @@
     }
 
     /**
+     * If the provided {@link ActivityRecord} can be displayed in an orientation different from the
+     * display's, it will be rotated to match its requested orientation.
+     *
+     * @see #rotationForActivityInDifferentOrientation(ActivityRecord).
+     * @see WindowToken#applyFixedRotationTransform(DisplayInfo, DisplayFrames, Configuration)
+     */
+    void rotateInDifferentOrientationIfNeeded(ActivityRecord activityRecord) {
+        int rotation = rotationForActivityInDifferentOrientation(activityRecord);
+        if (rotation != NO_ROTATION) {
+            startFixedRotationTransform(activityRecord, rotation);
+        }
+    }
+
+    /**
      * Update rotation of the display.
      *
      * @return {@code true} if the rotation has been changed.  In this case YOU MUST CALL
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 251d0f1..e594466 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -423,6 +423,11 @@
 
         mService.mWindowPlacerLocked.performSurfacePlacement();
 
+        // If the target activity has a fixed orientation which is different from the current top
+        // activity, it will be rotated before being shown so we avoid a screen rotation
+        // animation when showing the Recents view.
+        mDisplayContent.rotateInDifferentOrientationIfNeeded(mTargetActivityRecord);
+
         // Notify that the animation has started
         if (mStatusBar != null) {
             mStatusBar.onRecentsAnimationStateChanged(true /* running */);
@@ -695,6 +700,9 @@
                 mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(
                         mTargetActivityRecord.token);
             }
+            if (mTargetActivityRecord.hasFixedRotationTransform()) {
+                mTargetActivityRecord.clearFixedRotationTransform();
+            }
         }
 
         // Notify that the animation has ended
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 7753a32..66180a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -53,6 +53,8 @@
 import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.TaskSnapshot;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.IInterface;
@@ -330,6 +332,44 @@
         assertTrue(activity.shouldAnimate(TRANSIT_ACTIVITY_CLOSE));
     }
 
+    @Test
+    public void testRecentViewInFixedPortraitWhenTopAppInLandscape() {
+        mWm.mIsFixedRotationTransformEnabled = true;
+        mWm.setRecentsAnimationController(mController);
+
+        final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final ActivityRecord homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build();
+        final ActivityRecord appWindow = createActivityRecord(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win0 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win0);
+
+        final ActivityRecord landActivity = createActivityRecord(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        landActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, landActivity, "win1");
+        landActivity.addWindow(win1);
+
+        assertEquals(landActivity.getTask().getTopVisibleActivity(), landActivity);
+        assertEquals(landActivity.findMainWindow(), win1);
+
+        // Ensure that the display is in Landscape
+        landActivity.onDescendantOrientationChanged(landActivity.token, landActivity);
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE,
+                mDisplayContent.getConfiguration().orientation);
+
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeAppWindow);
+
+        // Check that the home app is in portrait
+        assertEquals(Configuration.ORIENTATION_PORTRAIT,
+                homeAppWindow.getConfiguration().orientation);
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);