Add mixed entering pip transition with display change

The case happens when a pip is entering in previous rotation and then
display updates. The transition type can be either TRANSIT_CHANGE or
TRANSIT_PIP:
 PipTask CHANGE
   sb=Rect(0, 0 - 1080, 2340)
   eb=Rect(0, 0 - 2340, 1080) r=0->3
 Display CHANGE
   sb=Rect(0, 0 - 1080, 2340)
   eb=Rect(0, 0 - 2340, 1080) r=0->3

The destination pip bounds should be calculated from (2340, 1080).
Because a display snapshot should have covered the screen, the pip
should go to the end state immediately and only animate the display
rotation animation.

This also merges [1] and [2].
[1]: I110d1c11f3d3fdcfb83698e5cf1ec4efb062bd10
[2]: Ia9c78105f4a9782c156744e6cb38681f265955a0

Bug: 340367710
Test: Enable auto rotation and home rotation.
      Swipe up (do not release touch) an auto pip activity in portrait.
      Rotate the device to landscape.
      Continue the swipe up to enter pip.
      The display should show a rotation animation and
      the pip can show on correct landscape position,

Merged-In: Ia1e5e8c7edb8c2a078a66b587d5d26a495166de2
Change-Id: Ia1e5e8c7edb8c2a078a66b587d5d26a495166de2
(cherry picked from commit 6373912c458efd15abceb9d029bf3efab97578d1)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
index ed42117..d5e4718 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDisplayLayoutState.java
@@ -115,6 +115,12 @@
         mDisplayLayout.rotateTo(mContext.getResources(), targetRotation);
     }
 
+    /** Returns the current display rotation of this layout state. */
+    @Surface.Rotation
+    public int getRotation() {
+        return mDisplayLayout.rotation();
+    }
+
     /** Get the current display id */
     public int getDisplayId() {
         return mDisplayId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 79b1079..bdfabaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -287,6 +287,12 @@
 
         // Entering PIP.
         if (isEnteringPip(info)) {
+            if (handleEnteringPipWithDisplayChange(transition, info, startTransaction,
+                    finishTransaction, finishCallback)) {
+                // The destination position is applied directly and let default transition handler
+                // run the display change animation.
+                return true;
+            }
             startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
             return true;
         }
@@ -301,6 +307,25 @@
         return false;
     }
 
+    private boolean handleEnteringPipWithDisplayChange(@NonNull IBinder transition,
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mFixedRotationState != FIXED_ROTATION_UNDEFINED
+                || !TransitionUtil.hasDisplayChange(info)) {
+            return false;
+        }
+        final TransitionInfo.Change pipChange = getPipChange(info);
+        if (pipChange == null) {
+            return false;
+        }
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: handle entering PiP with display change", TAG);
+        mMixedHandler.animateEnteringPipWithDisplayChange(transition, info, pipChange,
+                startT, finishT, finishCallback);
+        return true;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -825,8 +850,11 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TaskInfo taskInfo) {
         startTransaction.apply();
-        finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
-                mPipDisplayLayoutState.getDisplayBounds());
+        final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info);
+        if (pipChange == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "removePipImmediately is called without pip change");
+        }
         mPipOrganizer.onExitPipFinished(taskInfo);
         finishCallback.onTransitionFinished(null);
     }
@@ -873,19 +901,24 @@
         mEnterAnimationType = type;
     }
 
+    @Nullable
+    private static TransitionInfo.Change getPipChange(@NonNull TransitionInfo info) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getTaskInfo() != null
+                    && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
+                return change;
+            }
+        }
+        return null;
+    }
+
     private void startEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         // Search for an Enter PiP transition
-        TransitionInfo.Change enterPip = null;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            final TransitionInfo.Change change = info.getChanges().get(i);
-            if (change.getTaskInfo() != null
-                    && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
-                enterPip = change;
-            }
-        }
+        final TransitionInfo.Change enterPip = getPipChange(info);
         if (enterPip == null) {
             throw new IllegalStateException("Trying to start PiP animation without a pip"
                     + "participant");
@@ -959,8 +992,8 @@
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
         if (rotationDelta != Surface.ROTATION_0
-                && mFixedRotationState == FIXED_ROTATION_TRANSITION) {
-            // Need to get the bounds of new rotation in old rotation for fixed rotation,
+                && endRotation != mPipDisplayLayoutState.getRotation()) {
+            // Computes the destination bounds in new rotation.
             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
                     destinationBounds, sourceHintRect);
         }
@@ -1052,8 +1085,11 @@
 
         final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
         outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
-        // Transform the destination bounds to current display coordinates.
-        rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
+        if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+            // Transform the destination bounds to current display coordinates.
+            // With fixed rotation, the bounds of new rotation shows in old rotation.
+            rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
+        }
         // When entering PiP (from button navigation mode), adjust the source rect hint by
         // display cutout if applicable.
         if (outSourceHintRect != null && taskInfo.displayCutoutInsets != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 5584f23..a2b7b9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -49,6 +49,7 @@
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
@@ -68,6 +69,7 @@
     protected final Transitions mTransitions;
     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
     protected PipTaskOrganizer mPipOrganizer;
+    protected DefaultMixedHandler mMixedHandler;
 
     protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
             new PipAnimationController.PipAnimationCallback() {
@@ -173,6 +175,14 @@
         mPipOrganizer = pto;
     }
 
+    public void setMixedHandler(DefaultMixedHandler mixedHandler) {
+        mMixedHandler = mixedHandler;
+    }
+
+    public void applyTransaction(WindowContainerTransaction wct) {
+        mShellTaskOrganizer.applyTransaction(wct);
+    }
+
     /**
      * Registers {@link PipTransitionCallback} to receive transition callbacks.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 65f84ed..57c74eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -106,6 +106,9 @@
         /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
         static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
 
+        /** The display changes when pip is entering. */
+        static final int TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE = 11;
+
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
 
@@ -232,6 +235,7 @@
             // Add after dependencies because it is higher priority
             shellInit.addInitCallback(() -> {
                 mPipHandler = pipTransitionController;
+                pipTransitionController.setMixedHandler(this);
                 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
                 mPlayer.addHandler(this);
                 if (mSplitHandler != null) {
@@ -549,6 +553,47 @@
         return true;
     }
 
+    /**
+     * For example: pip is entering in rotation 0, and then the display changes to rotation 90
+     * before the pip transition is ready. So the info contains both the entering pip and display
+     * change. In this case, the pip can go to the end state in new rotation directly, and let the
+     * display level animation cover all changed participates.
+     */
+    public void animateEnteringPipWithDisplayChange(@NonNull IBinder transition,
+            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change pipChange,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // In order to play display level animation, force the type to CHANGE (it could be PIP).
+        final TransitionInfo changeInfo = info.getType() != TRANSIT_CHANGE
+                ? subCopy(info, TRANSIT_CHANGE, true /* withChanges */) : info;
+        final MixedTransition mixed = createDefaultMixedTransition(
+                MixedTransition.TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE, transition);
+        mActiveTransitions.add(mixed);
+        mixed.mInFlightSubAnimations = 2;
+        final Transitions.TransitionFinishCallback finishCB = wct -> {
+            --mixed.mInFlightSubAnimations;
+            mixed.joinFinishArgs(wct);
+            if (mixed.mInFlightSubAnimations > 0) return;
+            mActiveTransitions.remove(mixed);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
+        };
+        // Perform the display animation first.
+        mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, changeInfo,
+                startT, finishT, finishCB, mPipHandler);
+        // Use a standalone finish transaction for pip because it will apply immediately.
+        final SurfaceControl.Transaction pipFinishT = new SurfaceControl.Transaction();
+        mPipHandler.startEnterAnimation(pipChange, startT, pipFinishT, wct -> {
+            // Apply immediately to avoid potential flickering by bounds change at the end of
+            // display animation.
+            mPipHandler.applyTransaction(wct);
+            finishCB.onTransitionFinished(null /* wct */);
+        });
+        // Jump to the pip end state directly and make sure the real finishT have the latest state.
+        mPipHandler.end();
+        mPipHandler.syncPipSurfaceState(info, startT, finishT);
+    }
+
     private static boolean animateKeyguard(@NonNull final MixedTransition mixed,
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 0de9a36..2e05fc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -70,7 +70,7 @@
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         return switch (mType) {
-            case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE, TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE -> false;
             case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
                     animateEnterPipFromActivityEmbedding(
                             info, startTransaction, finishTransaction, finishCallback);
@@ -253,6 +253,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         switch (mType) {
             case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+            case TYPE_ENTER_PIP_WITH_DISPLAY_CHANGE:
                 // queue since no actual animation.
                 return;
             case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8b927a6..2500852 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5633,6 +5633,8 @@
                 } else if (mTransitionController.inFinishingTransition(this)) {
                     mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
                 }
+            } else {
+                mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
             }
             return;
         }