Use normal rotation while PiP is toggling or active

Seamless rotation of PiP has not been defined. To avoid ugly jump
cut when moving PiP with different orientation, the display is
still frozen to perform normal rotation animation.

Fixes: 156593887
Test: atest DisplayContentTests#testNoFixedRotationWithPip

Change-Id: I6830970ada96023fc5b93afb2f9a917988c93b59
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 00b2a58..7f03778 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -819,6 +819,22 @@
 
         mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
         mRootWindowContainer.resumeFocusedStacksTopActivities();
+
+        final boolean pinnedToFullscreen = currentMode == WINDOWING_MODE_PINNED
+                && windowingMode == WINDOWING_MODE_FULLSCREEN;
+        if (pinnedToFullscreen && topActivity != null && !isForceHidden()) {
+            mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(true);
+            try {
+                // Report orientation as soon as possible so that the display can freeze earlier if
+                // the display orientation will be changed. Because the surface bounds of activity
+                // may have been set to fullscreen but the activity hasn't redrawn its content yet,
+                // the rotation animation needs to capture snapshot earlier to avoid animating from
+                // an intermediate state.
+                topActivity.reportDescendantOrientationChangeIfNeeded();
+            } finally {
+                mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(false);
+            }
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8903776..59181a6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1481,6 +1481,10 @@
             // has it own policy for bounds, the activity bounds based on parent is unknown.
             return false;
         }
+        if (mPinnedStackControllerLocked.isPipActiveOrWindowingModeChanging()) {
+            // Use normal rotation animation because seamless PiP rotation is not supported yet.
+            return false;
+        }
 
         setFixedRotationLaunchingApp(r, rotation);
         return true;
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 56312aa..52ada47 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
@@ -33,8 +32,6 @@
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
-import com.android.server.UiThread;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -61,7 +58,6 @@
 
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
-    private final Handler mHandler = UiThread.getHandler();
 
     private IPinnedStackListener mPinnedStackListener;
     private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
@@ -69,6 +65,9 @@
 
     private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
 
+    /** Whether the PiP is entering or leaving. */
+    private boolean mIsPipWindowingModeChanging;
+
     private boolean mIsImeShowing;
     private int mImeHeight;
 
@@ -161,6 +160,20 @@
                 Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
     }
 
+    /** Returns {@code true} if the PiP is on screen or is changing windowing mode. */
+    boolean isPipActiveOrWindowingModeChanging() {
+        if (mIsPipWindowingModeChanging) {
+            return true;
+        }
+        final Task pinnedTask = mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask();
+        return pinnedTask != null && pinnedTask.hasChild();
+    }
+
+    /** Sets whether a visible stack is changing from or to pinned mode. */
+    void setPipWindowingModeChanging(boolean isPipWindowingModeChanging) {
+        mIsPipWindowingModeChanging = isPipWindowingModeChanging;
+    }
+
     /**
      * Activity is hidden (either stopped or removed), resets the last saved snap fraction
      * so that the default bounds will be returned for the next session.
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 7197ce9..4ad7dff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -73,6 +76,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 
 import android.annotation.SuppressLint;
@@ -1168,6 +1173,57 @@
     }
 
     @Test
+    public void testNoFixedRotationWithPip() {
+        mWm.mIsFixedRotationTransformEnabled = true;
+        // Make resume-top really update the activity state.
+        doReturn(false).when(mWm.mAtmService).isBooting();
+        doReturn(true).when(mWm.mAtmService).isBooted();
+        // Speed up the test by a few seconds.
+        mWm.mAtmService.deferWindowLayout();
+        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+
+        final DisplayContent displayContent = mWm.mRoot.getDefaultDisplay();
+        final Configuration displayConfig = displayContent.getConfiguration();
+        final ActivityRecord pinnedActivity = createActivityRecord(displayContent,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        final Task pinnedTask = pinnedActivity.getRootTask();
+        final ActivityRecord homeActivity = WindowTestUtils.createTestActivityRecord(
+                displayContent.getDefaultTaskDisplayArea().getOrCreateRootHomeTask());
+        if (displayConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
+            homeActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+            pinnedActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+        } else {
+            homeActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+            pinnedActivity.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
+        }
+        final int homeConfigOrientation = homeActivity.getRequestedConfigurationOrientation();
+        final int pinnedConfigOrientation = pinnedActivity.getRequestedConfigurationOrientation();
+
+        assertEquals(homeConfigOrientation, displayConfig.orientation);
+
+        clearInvocations(mWm);
+        // Leave PiP to fullscreen. The orientation can be updated from
+        // ActivityRecord#reportDescendantOrientationChangeIfNeeded.
+        pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        homeActivity.setState(ActivityStack.ActivityState.STOPPED, "test");
+
+        assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
+        verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+        assertEquals(pinnedConfigOrientation, displayConfig.orientation);
+        assertFalse(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging());
+
+        clearInvocations(mWm);
+        // Enter PiP from fullscreen. The orientation can be updated from
+        // ensure-visibility/resume-focused-stack -> ActivityRecord#makeActiveIfNeeded -> resume.
+        pinnedTask.setWindowingMode(WINDOWING_MODE_PINNED);
+
+        assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
+        verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
+        assertEquals(homeConfigOrientation, displayConfig.orientation);
+        assertTrue(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging());
+    }
+
+    @Test
     public void testRemoteRotation() {
         DisplayContent dc = createNewDisplay();