Split MixedTransition into Recents and Default.

4/4 to split the the Recents functionality from the rest of
DefaultMixedHandler logic.

Bug: 314971702
Flag: NA
Test: https://android-build.corp.google.com/builds/abtd/run/L79000030000829051
Change-Id: I52885c355e3a561c361c1c3993188b6a1a1f848f
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 bf783e6..8c2203e 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
@@ -21,17 +21,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.view.WindowManager.TRANSIT_PIP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
 
 import android.annotation.NonNull;
@@ -56,7 +48,6 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellInit;
@@ -84,7 +75,7 @@
     private UnfoldTransitionHandler mUnfoldHandler;
     private ActivityEmbeddingController mActivityEmbeddingController;
 
-    private static class MixedTransition {
+    abstract static class MixedTransition {
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
 
         /** Both the display and split-state (enter/exit) is changing */
@@ -124,15 +115,11 @@
         int mAnimType = ANIM_TYPE_DEFAULT;
         final IBinder mTransition;
 
-        private final Transitions mPlayer;
-        private final DefaultMixedHandler mMixedHandler;
-        private final PipTransitionController mPipHandler;
-        private final RecentsTransitionHandler mRecentsHandler;
-        private final StageCoordinator mSplitHandler;
-        private final KeyguardTransitionHandler mKeyguardHandler;
-        private final DesktopTasksController mDesktopTasksController;
-        private final UnfoldTransitionHandler mUnfoldHandler;
-        private final ActivityEmbeddingController mActivityEmbeddingController;
+        protected final Transitions mPlayer;
+        protected final DefaultMixedHandler mMixedHandler;
+        protected final PipTransitionController mPipHandler;
+        protected final StageCoordinator mSplitHandler;
+        protected final KeyguardTransitionHandler mKeyguardHandler;
 
         Transitions.TransitionHandler mLeftoversHandler = null;
         TransitionInfo mInfo = null;
@@ -156,409 +143,33 @@
 
         MixedTransition(int type, IBinder transition, Transitions player,
                 DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
-                RecentsTransitionHandler recentsHandler, StageCoordinator splitHandler,
-                KeyguardTransitionHandler keyguardHandler,
-                DesktopTasksController desktopTasksController,
-                UnfoldTransitionHandler unfoldHandler,
-                ActivityEmbeddingController activityEmbeddingController) {
+                StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler) {
             mType = type;
             mTransition = transition;
             mPlayer = player;
             mMixedHandler = mixedHandler;
             mPipHandler = pipHandler;
-            mRecentsHandler = recentsHandler;
             mSplitHandler = splitHandler;
             mKeyguardHandler = keyguardHandler;
-            mDesktopTasksController = desktopTasksController;
-            mUnfoldHandler = unfoldHandler;
-            mActivityEmbeddingController = activityEmbeddingController;
-
-            switch (type) {
-                case TYPE_RECENTS_DURING_DESKTOP:
-                case TYPE_RECENTS_DURING_KEYGUARD:
-                case TYPE_RECENTS_DURING_SPLIT:
-                    mLeftoversHandler = mRecentsHandler;
-                    break;
-                case TYPE_UNFOLD:
-                    mLeftoversHandler = mUnfoldHandler;
-                    break;
-                case TYPE_DISPLAY_AND_SPLIT_CHANGE:
-                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
-                case TYPE_ENTER_PIP_FROM_SPLIT:
-                case TYPE_KEYGUARD:
-                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
-                default:
-                    break;
-            }
         }
 
-        boolean startAnimation(
+        abstract boolean startAnimation(
                 @NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction startTransaction,
                 @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            switch (mType) {
-                case TYPE_ENTER_PIP_FROM_SPLIT:
-                    return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
-                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
-                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
-                    return animateEnterPipFromActivityEmbedding(
-                            info, startTransaction, finishTransaction, finishCallback);
-                case TYPE_DISPLAY_AND_SPLIT_CHANGE:
-                    return false;
-                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
-                    final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
-                            info, startTransaction, finishTransaction, finishCallback);
-                    // Consume the transition on remote handler if the leftover handler already
-                    // handle this transition. And if it cannot, the transition will be handled by
-                    // remote handler, so don't consume here.
-                    // Need to check leftOverHandler as it may change in
-                    // #animateOpenIntentWithRemoteAndPip
-                    if (handledToPip && mHasRequestToRemote
-                            && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
-                        mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
-                                transition, false, null);
-                    }
-                    return handledToPip;
-                case TYPE_RECENTS_DURING_SPLIT:
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        final TransitionInfo.Change change = info.getChanges().get(i);
-                        // Pip auto-entering info might be appended to recent transition like
-                        // pressing home-key in 3-button navigation. This offers split handler the
-                        // opportunity to handle split to pip animation.
-                        if (mPipHandler.isEnteringPip(change, info.getType())
-                                && mSplitHandler.getSplitItemPosition(change.getLastParent())
-                                != SPLIT_POSITION_UNDEFINED) {
-                            return animateEnterPipFromSplit(
-                                    this, info, startTransaction, finishTransaction, finishCallback,
-                                    mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
-                        }
-                    }
+                @NonNull Transitions.TransitionFinishCallback finishCallback);
 
-                    return animateRecentsDuringSplit(
-                            info, startTransaction, finishTransaction, finishCallback);
-                case TYPE_KEYGUARD:
-                    return animateKeyguard(this, info, startTransaction, finishTransaction,
-                            finishCallback, mKeyguardHandler, mPipHandler);
-                case TYPE_RECENTS_DURING_KEYGUARD:
-                    return animateRecentsDuringKeyguard(
-                            info, startTransaction, finishTransaction, finishCallback);
-                case TYPE_RECENTS_DURING_DESKTOP:
-                    return animateRecentsDuringDesktop(
-                            info, startTransaction, finishTransaction, finishCallback);
-                case TYPE_UNFOLD:
-                    return animateUnfold(
-                            info, startTransaction, finishTransaction, finishCallback);
-                default:
-                    throw new IllegalStateException(
-                            "Starting mixed animation without a known mixed type? " + mType);
-            }
-        }
-
-        private boolean animateEnterPipFromActivityEmbedding(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
-                    + "entering PIP from an Activity Embedding window");
-            // Split into two transitions (wct)
-            TransitionInfo.Change pipChange = null;
-            final TransitionInfo everythingElse =
-                    subCopy(info, TRANSIT_TO_BACK, true /* changes */);
-            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                TransitionInfo.Change change = info.getChanges().get(i);
-                if (mPipHandler.isEnteringPip(change, info.getType())) {
-                    if (pipChange != null) {
-                        throw new IllegalStateException("More than 1 pip-entering changes in one"
-                                + " transition? " + info);
-                    }
-                    pipChange = change;
-                    // going backwards, so remove-by-index is fine.
-                    everythingElse.getChanges().remove(i);
-                }
-            }
-
-            final Transitions.TransitionFinishCallback finishCB = (wct) -> {
-                --mInFlightSubAnimations;
-                joinFinishArgs(wct);
-                if (mInFlightSubAnimations > 0) return;
-                finishCallback.onTransitionFinished(mFinishWCT);
-            };
-
-            if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
-                // Fallback to dispatching to other handlers.
-                return false;
-            }
-
-            // PIP window should always be on the highest Z order.
-            if (pipChange != null) {
-                mInFlightSubAnimations = 2;
-                mPipHandler.startEnterAnimation(
-                        pipChange,
-                        startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
-                        finishTransaction,
-                        finishCB);
-            } else {
-                mInFlightSubAnimations = 1;
-            }
-
-            mActivityEmbeddingController.startAnimation(mTransition, everythingElse,
-                    startTransaction, finishTransaction, finishCB);
-            return true;
-        }
-
-        private boolean animateOpenIntentWithRemoteAndPip(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            TransitionInfo.Change pipChange = null;
-            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                TransitionInfo.Change change = info.getChanges().get(i);
-                if (mPipHandler.isEnteringPip(change, info.getType())) {
-                    if (pipChange != null) {
-                        throw new IllegalStateException("More than 1 pip-entering changes in one"
-                                + " transition? " + info);
-                    }
-                    pipChange = change;
-                    info.getChanges().remove(i);
-                }
-            }
-            Transitions.TransitionFinishCallback finishCB = (wct) -> {
-                --mInFlightSubAnimations;
-                joinFinishArgs(wct);
-                if (mInFlightSubAnimations > 0) return;
-                finishCallback.onTransitionFinished(mFinishWCT);
-            };
-            if (pipChange == null) {
-                if (mLeftoversHandler != null) {
-                    mInFlightSubAnimations = 1;
-                    if (mLeftoversHandler.startAnimation(
-                            mTransition, info, startTransaction, finishTransaction, finishCB)) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
-                    + " animation because remote-animation likely doesn't support it");
-            // Split the transition into 2 parts: the pip part and the rest.
-            mInFlightSubAnimations = 2;
-            // make a new startTransaction because pip's startEnterAnimation "consumes" it so
-            // we need a separate one to send over to launcher.
-            SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-
-            mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
-
-            // Dispatch the rest of the transition normally.
-            if (mLeftoversHandler != null
-                    && mLeftoversHandler.startAnimation(
-                            mTransition, info, startTransaction, finishTransaction, finishCB)) {
-                return true;
-            }
-            mLeftoversHandler = mPlayer.dispatchTransition(mTransition, info,
-                    startTransaction, finishTransaction, finishCB, mMixedHandler);
-            return true;
-        }
-
-        private boolean animateRecentsDuringSplit(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            // Split-screen is only interested in the recents transition finishing (and merging), so
-            // just wrap finish and start recents animation directly.
-            Transitions.TransitionFinishCallback finishCB = (wct) -> {
-                mInFlightSubAnimations = 0;
-                // If pair-to-pair switching, the post-recents clean-up isn't needed.
-                wct = wct != null ? wct : new WindowContainerTransaction();
-                if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
-                    mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
-                } else {
-                    // notify pair-to-pair recents animation finish
-                    mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
-                }
-                mSplitHandler.onTransitionAnimationComplete();
-                finishCallback.onTransitionFinished(wct);
-            };
-            mInFlightSubAnimations = 1;
-            mSplitHandler.onRecentsInSplitAnimationStart(info);
-            final boolean handled = mLeftoversHandler.startAnimation(mTransition, info,
-                    startTransaction, finishTransaction, finishCB);
-            if (!handled) {
-                mSplitHandler.onRecentsInSplitAnimationCanceled();
-            }
-            return handled;
-        }
-
-        private boolean animateRecentsDuringKeyguard(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (mInfo == null) {
-                mInfo = info;
-                mFinishT = finishTransaction;
-                mFinishCB = finishCallback;
-            }
-            return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
-        }
-
-        private boolean animateRecentsDuringDesktop(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            Transitions.TransitionFinishCallback finishCB = wct -> {
-                mInFlightSubAnimations--;
-                if (mInFlightSubAnimations == 0) {
-                    finishCallback.onTransitionFinished(wct);
-                }
-            };
-
-            mInFlightSubAnimations++;
-            boolean consumed = mRecentsHandler.startAnimation(
-                    mTransition, info, startTransaction, finishTransaction, finishCB);
-            if (!consumed) {
-                mInFlightSubAnimations--;
-                return false;
-            }
-            if (mDesktopTasksController != null) {
-                mDesktopTasksController.syncSurfaceState(info, finishTransaction);
-                return true;
-            }
-
-            return false;
-        }
-
-        private boolean animateUnfold(
-                @NonNull TransitionInfo info,
-                @NonNull SurfaceControl.Transaction startTransaction,
-                @NonNull SurfaceControl.Transaction finishTransaction,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            final Transitions.TransitionFinishCallback finishCB = (wct) -> {
-                mInFlightSubAnimations--;
-                if (mInFlightSubAnimations > 0) return;
-                finishCallback.onTransitionFinished(wct);
-            };
-            mInFlightSubAnimations = 1;
-            // Sync pip state.
-            if (mPipHandler != null) {
-                mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
-            }
-            if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
-                mSplitHandler.updateSurfaces(startTransaction);
-            }
-            return mUnfoldHandler.startAnimation(
-                    mTransition, info, startTransaction, finishTransaction, finishCB);
-        }
-
-        void mergeAnimation(
+        abstract void mergeAnimation(
                 @NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
-                @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            switch (mType) {
-                case TYPE_DISPLAY_AND_SPLIT_CHANGE:
-                    // queue since no actual animation.
-                    break;
-                case TYPE_ENTER_PIP_FROM_SPLIT:
-                    if (mAnimType == ANIM_TYPE_GOING_HOME) {
-                        boolean ended = mSplitHandler.end();
-                        // If split couldn't end (because it is remote), then don't end everything
-                        // else since we have to play out the animation anyways.
-                        if (!ended) return;
-                        mPipHandler.end();
-                        if (mLeftoversHandler != null) {
-                            mLeftoversHandler.mergeAnimation(
-                                    transition, info, t, mergeTarget, finishCallback);
-                        }
-                    } else {
-                        mPipHandler.end();
-                    }
-                    break;
-                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
-                    mPipHandler.end();
-                    mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
-                            finishCallback);
-                    break;
-                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
-                    mPipHandler.end();
-                    if (mLeftoversHandler != null) {
-                        mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
-                                finishCallback);
-                    }
-                    break;
-                case TYPE_RECENTS_DURING_SPLIT:
-                    if (mSplitHandler.isPendingEnter(transition)) {
-                        // Recents -> enter-split means that we are switching from one pair to
-                        // another pair.
-                        mAnimType = ANIM_TYPE_PAIR_TO_PAIR;
-                    }
-                    mLeftoversHandler.mergeAnimation(
-                            transition, info, t, mergeTarget, finishCallback);
-                    break;
-                case TYPE_KEYGUARD:
-                    mKeyguardHandler.mergeAnimation(
-                            transition, info, t, mergeTarget, finishCallback);
-                    break;
-                case TYPE_RECENTS_DURING_KEYGUARD:
-                    if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
-                        DefaultMixedHandler.handoverTransitionLeashes(mInfo, info, t, mFinishT);
-                        if (animateKeyguard(this, info, t, mFinishT, mFinishCB, mKeyguardHandler,
-                                mPipHandler)) {
-                            finishCallback.onTransitionFinished(null);
-                        }
-                    }
-                    mLeftoversHandler.mergeAnimation(
-                            transition, info, t, mergeTarget, finishCallback);
-                    break;
-                case TYPE_RECENTS_DURING_DESKTOP:
-                    mLeftoversHandler.mergeAnimation(
-                            transition, info, t, mergeTarget, finishCallback);
-                    break;
-                case TYPE_UNFOLD:
-                    mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
-                    break;
-                default:
-                    throw new IllegalStateException(
-                            "Playing a mixed transition with unknown type? " + mType);
-            }
-        }
+                @NonNull Transitions.TransitionFinishCallback finishCallback);
 
-        void onTransitionConsumed(
+        abstract void onTransitionConsumed(
                 @NonNull IBinder transition, boolean aborted,
-                @Nullable SurfaceControl.Transaction finishT) {
-            switch (mType) {
-                case TYPE_ENTER_PIP_FROM_SPLIT:
-                    mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-                    break;
-                case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
-                    mPipHandler.onTransitionConsumed(transition, aborted, finishT);
-                    mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
-                    break;
-                case TYPE_RECENTS_DURING_SPLIT:
-                case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
-                case TYPE_RECENTS_DURING_DESKTOP:
-                    mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
-                    break;
-                case TYPE_KEYGUARD:
-                    mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
-                    break;
-                case TYPE_UNFOLD:
-                    mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
-                    break;
-                default:
-                    break;
-            }
+                @Nullable SurfaceControl.Transaction finishT);
 
-            if (mHasRequestToRemote) {
-                mPlayer.getRemoteTransitionHandler().onTransitionConsumed(
-                        transition, aborted, finishT);
-            }
-        }
-
-        boolean startSubAnimation(Transitions.TransitionHandler handler, TransitionInfo info,
+        protected boolean startSubAnimation(
+                Transitions.TransitionHandler handler, TransitionInfo info,
                 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) {
             if (mInfo != null) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -573,7 +184,7 @@
             return true;
         }
 
-        void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
+        private void onSubAnimationFinished(TransitionInfo info, WindowContainerTransaction wct) {
             mInFlightSubAnimations--;
             if (mInfo != null) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -644,7 +255,7 @@
                 throw new IllegalStateException("Unexpected remote transition in"
                         + "pip-enter-from-split request");
             }
-            mActiveTransitions.add(createMixedTransition(
+            mActiveTransitions.add(createDefaultMixedTransition(
                     MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition));
 
             WindowContainerTransaction out = new WindowContainerTransaction();
@@ -656,7 +267,7 @@
                 mActivityEmbeddingController != null)) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     " Got a PiP-enter request from an Activity Embedding split");
-            mActiveTransitions.add(createMixedTransition(
+            mActiveTransitions.add(createDefaultMixedTransition(
                     MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
             // Postpone transition splitting to later.
             WindowContainerTransaction out = new WindowContainerTransaction();
@@ -675,7 +286,7 @@
             if (handler == null) {
                 return null;
             }
-            final MixedTransition mixed = createMixedTransition(
+            final MixedTransition mixed = createDefaultMixedTransition(
                     MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
@@ -701,7 +312,7 @@
                         mPlayer.getRemoteTransitionHandler(),
                         new WindowContainerTransaction());
             }
-            final MixedTransition mixed = createMixedTransition(
+            final MixedTransition mixed = createRecentsMixedTransition(
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = handler.first;
             mActiveTransitions.add(mixed);
@@ -710,7 +321,7 @@
             final WindowContainerTransaction wct =
                     mUnfoldHandler.handleRequest(transition, request);
             if (wct != null) {
-                mActiveTransitions.add(createMixedTransition(
+                mActiveTransitions.add(createDefaultMixedTransition(
                         MixedTransition.TYPE_UNFOLD, transition));
             }
             return wct;
@@ -718,6 +329,12 @@
         return null;
     }
 
+    private DefaultMixedTransition createDefaultMixedTransition(int type, IBinder transition) {
+        return new DefaultMixedTransition(
+                type, transition, mPlayer, this, mPipHandler, mSplitHandler, mKeyguardHandler,
+                mUnfoldHandler, mActivityEmbeddingController);
+    }
+
     @Override
     public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) {
         if (mRecentsHandler != null) {
@@ -737,31 +354,30 @@
     private void setRecentsTransitionDuringSplit(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "Split-Screen is foreground, so treat it as Mixed.");
-        mActiveTransitions.add(createMixedTransition(
+        mActiveTransitions.add(createRecentsMixedTransition(
                 MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition));
     }
 
     private void setRecentsTransitionDuringKeyguard(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "keyguard is visible, so treat it as Mixed.");
-        mActiveTransitions.add(createMixedTransition(
+        mActiveTransitions.add(createRecentsMixedTransition(
                 MixedTransition.TYPE_RECENTS_DURING_KEYGUARD, transition));
     }
 
     private void setRecentsTransitionDuringDesktop(IBinder transition) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
                 + "desktop mode is active, so treat it as Mixed.");
-        mActiveTransitions.add(createMixedTransition(
+        mActiveTransitions.add(createRecentsMixedTransition(
                 MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition));
     }
 
-    private MixedTransition createMixedTransition(int type, IBinder transition) {
-        return new MixedTransition(type, transition, mPlayer, this, mPipHandler, mRecentsHandler,
-                mSplitHandler, mKeyguardHandler, mDesktopTasksController, mUnfoldHandler,
-                mActivityEmbeddingController);
+    private MixedTransition createRecentsMixedTransition(int type, IBinder transition) {
+        return new RecentsMixedTransition(type, transition, mPlayer, this, mPipHandler,
+                mSplitHandler, mKeyguardHandler, mRecentsHandler, mDesktopTasksController);
     }
 
-    private static TransitionInfo subCopy(@NonNull TransitionInfo info,
+    static TransitionInfo subCopy(@NonNull TransitionInfo info,
             @WindowManager.TransitionType int newType, boolean withChanges) {
         final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
         out.setTrack(info.getTrack());
@@ -778,15 +394,6 @@
         return out;
     }
 
-    private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
-        return change.getTaskInfo() != null
-                && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
-    }
-
-    private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
-        return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
-    }
-
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -805,7 +412,7 @@
         if (KeyguardTransitionHandler.handles(info)) {
             if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
                 final MixedTransition keyguardMixed =
-                        createMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+                        createDefaultMixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
                 mActiveTransitions.add(keyguardMixed);
                 Transitions.TransitionFinishCallback callback = wct -> {
                     mActiveTransitions.remove(keyguardMixed);
@@ -845,117 +452,6 @@
         return handled;
     }
 
-    private static boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
-            @NonNull TransitionInfo info,
-            @NonNull SurfaceControl.Transaction startTransaction,
-            @NonNull SurfaceControl.Transaction finishTransaction,
-            @NonNull Transitions.TransitionFinishCallback finishCallback,
-            @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
-            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
-                + "entering PIP while Split-Screen is foreground.");
-        TransitionInfo.Change pipChange = null;
-        TransitionInfo.Change wallpaper = null;
-        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
-        boolean homeIsOpening = false;
-        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-            TransitionInfo.Change change = info.getChanges().get(i);
-            if (pipHandler.isEnteringPip(change, info.getType())) {
-                if (pipChange != null) {
-                    throw new IllegalStateException("More than 1 pip-entering changes in one"
-                            + " transition? " + info);
-                }
-                pipChange = change;
-                // going backwards, so remove-by-index is fine.
-                everythingElse.getChanges().remove(i);
-            } else if (isHomeOpening(change)) {
-                homeIsOpening = true;
-            } else if (isWallpaper(change)) {
-                wallpaper = change;
-            }
-        }
-        if (pipChange == null) {
-            // um, something probably went wrong.
-            return false;
-        }
-        final boolean isGoingHome = homeIsOpening;
-        Transitions.TransitionFinishCallback finishCB = (wct) -> {
-            --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct);
-            if (mixed.mInFlightSubAnimations > 0) return;
-            if (isGoingHome) {
-                splitHandler.onTransitionAnimationComplete();
-            }
-            finishCallback.onTransitionFinished(mixed.mFinishWCT);
-        };
-        if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
-                != SPLIT_POSITION_UNDEFINED) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
-                    + "since entering-PiP caused us to leave split and return home.");
-            // We need to split the transition into 2 parts: the pip part (animated by pip)
-            // and the dismiss-part (animated by launcher).
-            mixed.mInFlightSubAnimations = 2;
-            // immediately make the wallpaper visible (so that we don't see it pop-in during
-            // the time it takes to start recents animation (which is remote).
-            if (wallpaper != null) {
-                startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
-            }
-            // make a new startTransaction because pip's startEnterAnimation "consumes" it so
-            // we need a separate one to send over to launcher.
-            SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
-            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
-            if (splitHandler.isSplitScreenVisible()) {
-                // The non-going home case, we could be pip-ing one of the split stages and keep
-                // showing the other
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    TransitionInfo.Change change = info.getChanges().get(i);
-                    if (change == pipChange) {
-                        // Ignore the change/task that's going into Pip
-                        continue;
-                    }
-                    @SplitScreen.StageType int splitItemStage =
-                            splitHandler.getSplitItemStage(change.getLastParent());
-                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
-                        topStageToKeep = splitItemStage;
-                        break;
-                    }
-                }
-            }
-            // Let split update internal state for dismiss.
-            splitHandler.prepareDismissAnimation(topStageToKeep,
-                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
-                    finishTransaction);
-
-            // We are trying to accommodate launcher's close animation which can't handle the
-            // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
-            // from transition info.
-            for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
-                if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
-                    everythingElse.getChanges().remove(i);
-                    break;
-                }
-            }
-
-            pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
-            pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
-                    finishCB);
-            // Dispatch the rest of the transition normally. This will most-likely be taken by
-            // recents or default handler.
-            mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
-                    otherStartT, finishTransaction, finishCB, mixedHandler);
-        } else {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
-                    + "forward animation to Pip-Handler.");
-            // This happens if the pip-ing activity is in a multi-activity task (and thus a
-            // new pip task is spawned). In this case, we don't actually exit split so we can
-            // just let pip transition handle the animation verbatim.
-            mixed.mInFlightSubAnimations = 1;
-            pipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
-                    finishCB);
-        }
-        return true;
-    }
-
     private void unlinkMissingParents(TransitionInfo from) {
         for (int i = 0; i < from.getChanges().size(); ++i) {
             final TransitionInfo.Change chg = from.getChanges().get(i);
@@ -987,15 +483,14 @@
     public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
             Transitions.TransitionFinishCallback finishCallback) {
-        final MixedTransition mixed = createMixedTransition(
+        final MixedTransition mixed = createDefaultMixedTransition(
                 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
         mActiveTransitions.add(mixed);
         Transitions.TransitionFinishCallback callback = wct -> {
             mActiveTransitions.remove(mixed);
             finishCallback.onTransitionFinished(wct);
         };
-        return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback, mPlayer, this,
-                mPipHandler, mSplitHandler);
+        return mixed.startAnimation(transition, info, startT, finishT, callback);
     }
 
     /**
@@ -1018,7 +513,7 @@
         }
         if (displayPart.getChanges().isEmpty()) return false;
         unlinkMissingParents(everythingElse);
-        final MixedTransition mixed = createMixedTransition(
+        final MixedTransition mixed = createDefaultMixedTransition(
                 MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
         mActiveTransitions.add(mixed);
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
@@ -1135,7 +630,7 @@
      * {@link TransitionInfo} so that it can take over some parts of the animation without
      * reparenting to new transition roots.
      */
-    private static void handoverTransitionLeashes(
+    static void handoverTransitionLeashes(
             @NonNull TransitionInfo from,
             @NonNull TransitionInfo to,
             @NonNull SurfaceControl.Transaction startT,
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
new file mode 100644
index 0000000..9ce46d6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
+    private final UnfoldTransitionHandler mUnfoldHandler;
+    private final ActivityEmbeddingController mActivityEmbeddingController;
+
+    DefaultMixedTransition(int type, IBinder transition, Transitions player,
+            DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+            StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+            UnfoldTransitionHandler unfoldHandler,
+            ActivityEmbeddingController activityEmbeddingController) {
+        super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+        mUnfoldHandler = unfoldHandler;
+        mActivityEmbeddingController = activityEmbeddingController;
+
+        switch (type) {
+            case TYPE_UNFOLD:
+                mLeftoversHandler = mUnfoldHandler;
+                break;
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+            case TYPE_KEYGUARD:
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+            default:
+                break;
+        }
+    }
+
+    @Override
+    boolean startAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return switch (mType) {
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE -> false;
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING ->
+                    animateEnterPipFromActivityEmbedding(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_ENTER_PIP_FROM_SPLIT ->
+                    animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                            finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+            case TYPE_KEYGUARD ->
+                    animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
+                            mKeyguardHandler, mPipHandler);
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE ->
+                    animateOpenIntentWithRemoteAndPip(transition, info, startTransaction,
+                            finishTransaction, finishCallback);
+            case TYPE_UNFOLD ->
+                    animateUnfold(info, startTransaction, finishTransaction, finishCallback);
+            default -> throw new IllegalStateException(
+                    "Starting default mixed animation with unknown or illegal type: " + mType);
+        };
+    }
+
+    private boolean animateEnterPipFromActivityEmbedding(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+                + "entering PIP from an Activity Embedding window");
+        // Split into two transitions (wct)
+        TransitionInfo.Change pipChange = null;
+        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (mPipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                // going backwards, so remove-by-index is fine.
+                everythingElse.getChanges().remove(i);
+            }
+        }
+
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mInFlightSubAnimations;
+            joinFinishArgs(wct);
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(mFinishWCT);
+        };
+
+        if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
+            // Fallback to dispatching to other handlers.
+            return false;
+        }
+
+        // PIP window should always be on the highest Z order.
+        if (pipChange != null) {
+            mInFlightSubAnimations = 2;
+            mPipHandler.startEnterAnimation(
+                    pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
+                    finishTransaction,
+                    finishCB);
+        } else {
+            mInFlightSubAnimations = 1;
+        }
+
+        mActivityEmbeddingController.startAnimation(
+                mTransition, everythingElse, startTransaction, finishTransaction, finishCB);
+        return true;
+    }
+
+    private boolean animateOpenIntentWithRemoteAndPip(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean handledToPip = tryAnimateOpenIntentWithRemoteAndPip(
+                info, startTransaction, finishTransaction, finishCallback);
+        // Consume the transition on remote handler if the leftover handler already handle this
+        // transition. And if it cannot, the transition will be handled by remote handler, so don't
+        // consume here.
+        // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+        if (handledToPip && mHasRequestToRemote
+                && mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+        }
+        return handledToPip;
+    }
+
+    private boolean tryAnimateOpenIntentWithRemoteAndPip(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        TransitionInfo.Change pipChange = null;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (mPipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                info.getChanges().remove(i);
+            }
+        }
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mInFlightSubAnimations;
+            joinFinishArgs(wct);
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(mFinishWCT);
+        };
+        if (pipChange == null) {
+            if (mLeftoversHandler != null) {
+                mInFlightSubAnimations = 1;
+                if (mLeftoversHandler.startAnimation(
+                        mTransition, info, startTransaction, finishTransaction, finishCB)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+                + " animation because remote-animation likely doesn't support it");
+        // Split the transition into 2 parts: the pip part and the rest.
+        mInFlightSubAnimations = 2;
+        // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+        // we need a separate one to send over to launcher.
+        SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+
+        mPipHandler.startEnterAnimation(pipChange, otherStartT, finishTransaction, finishCB);
+
+        // Dispatch the rest of the transition normally.
+        if (mLeftoversHandler != null
+                && mLeftoversHandler.startAnimation(mTransition, info,
+                startTransaction, finishTransaction, finishCB)) {
+            return true;
+        }
+        mLeftoversHandler = mPlayer.dispatchTransition(
+                mTransition, info, startTransaction, finishTransaction, finishCB, mMixedHandler);
+        return true;
+    }
+
+    private boolean animateUnfold(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            mInFlightSubAnimations--;
+            if (mInFlightSubAnimations > 0) return;
+            finishCallback.onTransitionFinished(wct);
+        };
+        mInFlightSubAnimations = 1;
+        // Sync pip state.
+        if (mPipHandler != null) {
+            mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+        }
+        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+            mSplitHandler.updateSurfaces(startTransaction);
+        }
+        return mUnfoldHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+    }
+
+    @Override
+    void mergeAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        switch (mType) {
+            case TYPE_DISPLAY_AND_SPLIT_CHANGE:
+                // queue since no actual animation.
+                return;
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                mPipHandler.end();
+                mActivityEmbeddingController.mergeAnimation(
+                        transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+                if (mAnimType == ANIM_TYPE_GOING_HOME) {
+                    boolean ended = mSplitHandler.end();
+                    // If split couldn't end (because it is remote), then don't end everything else
+                    // since we have to play out the animation anyways.
+                    if (!ended) return;
+                    mPipHandler.end();
+                    if (mLeftoversHandler != null) {
+                        mLeftoversHandler.mergeAnimation(
+                                transition, info, t, mergeTarget, finishCallback);
+                    }
+                } else {
+                    mPipHandler.end();
+                }
+                return;
+            case TYPE_KEYGUARD:
+                mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                mPipHandler.end();
+                if (mLeftoversHandler != null) {
+                    mLeftoversHandler.mergeAnimation(
+                            transition, info, t, mergeTarget, finishCallback);
+                }
+                return;
+            case TYPE_UNFOLD:
+                mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            default:
+                throw new IllegalStateException("Playing a default mixed transition with unknown or"
+                        + " illegal type: " + mType);
+        }
+    }
+
+    @Override
+    void onTransitionConsumed(
+            @NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishT) {
+        switch (mType) {
+            case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
+                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_ENTER_PIP_FROM_SPLIT:
+                mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_KEYGUARD:
+                mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE:
+                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            case TYPE_UNFOLD:
+                mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            default:
+                break;
+        }
+
+        if (mHasRequestToRemote) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
new file mode 100644
index 0000000..0974cd1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
+
+import android.annotation.NonNull;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+public class MixedTransitionHelper {
+    static boolean animateEnterPipFromSplit(
+            @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Transitions player, @NonNull DefaultMixedHandler mixedHandler,
+            @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+                + "entering PIP while Split-Screen is foreground.");
+        TransitionInfo.Change pipChange = null;
+        TransitionInfo.Change wallpaper = null;
+        final TransitionInfo everythingElse =
+                subCopy(info, TRANSIT_TO_BACK, true /* changes */);
+        boolean homeIsOpening = false;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            TransitionInfo.Change change = info.getChanges().get(i);
+            if (pipHandler.isEnteringPip(change, info.getType())) {
+                if (pipChange != null) {
+                    throw new IllegalStateException("More than 1 pip-entering changes in one"
+                            + " transition? " + info);
+                }
+                pipChange = change;
+                // going backwards, so remove-by-index is fine.
+                everythingElse.getChanges().remove(i);
+            } else if (isHomeOpening(change)) {
+                homeIsOpening = true;
+            } else if (isWallpaper(change)) {
+                wallpaper = change;
+            }
+        }
+        if (pipChange == null) {
+            // um, something probably went wrong.
+            return false;
+        }
+        final boolean isGoingHome = homeIsOpening;
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            --mixed.mInFlightSubAnimations;
+            mixed.joinFinishArgs(wct);
+            if (mixed.mInFlightSubAnimations > 0) return;
+            if (isGoingHome) {
+                splitHandler.onTransitionAnimationComplete();
+            }
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
+        };
+        if (isGoingHome || splitHandler.getSplitItemPosition(pipChange.getLastParent())
+                != SPLIT_POSITION_UNDEFINED) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+                    + "since entering-PiP caused us to leave split and return home.");
+            // We need to split the transition into 2 parts: the pip part (animated by pip)
+            // and the dismiss-part (animated by launcher).
+            mixed.mInFlightSubAnimations = 2;
+            // immediately make the wallpaper visible (so that we don't see it pop-in during
+            // the time it takes to start recents animation (which is remote).
+            if (wallpaper != null) {
+                startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+            }
+            // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+            // we need a separate one to send over to launcher.
+            SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+            @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
+            if (splitHandler.isSplitScreenVisible()) {
+                // The non-going home case, we could be pip-ing one of the split stages and keep
+                // showing the other
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    TransitionInfo.Change change = info.getChanges().get(i);
+                    if (change == pipChange) {
+                        // Ignore the change/task that's going into Pip
+                        continue;
+                    }
+                    @SplitScreen.StageType int splitItemStage =
+                            splitHandler.getSplitItemStage(change.getLastParent());
+                    if (splitItemStage != STAGE_TYPE_UNDEFINED) {
+                        topStageToKeep = splitItemStage;
+                        break;
+                    }
+                }
+            }
+            // Let split update internal state for dismiss.
+            splitHandler.prepareDismissAnimation(topStageToKeep,
+                    EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+                    finishTransaction);
+
+            // We are trying to accommodate launcher's close animation which can't handle the
+            // divider-bar, so if split-handler is closing the divider-bar, just hide it and
+            // remove from transition info.
+            for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+                if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR)
+                        != 0) {
+                    everythingElse.getChanges().remove(i);
+                    break;
+                }
+            }
+
+            pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA);
+            pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+                    finishCB);
+            // Dispatch the rest of the transition normally. This will most-likely be taken by
+            // recents or default handler.
+            mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse,
+                    otherStartT, finishTransaction, finishCB, mixedHandler);
+        } else {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  Not leaving split, so just "
+                    + "forward animation to Pip-Handler.");
+            // This happens if the pip-ing activity is in a multi-activity task (and thus a
+            // new pip task is spawned). In this case, we don't actually exit split so we can
+            // just let pip transition handle the animation verbatim.
+            mixed.mInFlightSubAnimations = 1;
+            pipHandler.startAnimation(
+                    mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
+        }
+        return true;
+    }
+
+    private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+        return change.getTaskInfo() != null
+                && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
+    }
+
+    private static boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+        return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+    }
+
+    static boolean animateKeyguard(
+            @NonNull DefaultMixedHandler.MixedTransition mixed, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull KeyguardTransitionHandler keyguardHandler,
+            PipTransitionController pipHandler) {
+        if (mixed.mFinishT == null) {
+            mixed.mFinishT = finishTransaction;
+            mixed.mFinishCB = finishCallback;
+        }
+        // Sync pip state.
+        if (pipHandler != null) {
+            pipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
+        }
+        return mixed.startSubAnimation(keyguardHandler, info, startTransaction, finishTransaction);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
new file mode 100644
index 0000000..643e026
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
+import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
+    private final RecentsTransitionHandler mRecentsHandler;
+    private final DesktopTasksController mDesktopTasksController;
+
+    RecentsMixedTransition(int type, IBinder transition, Transitions player,
+            DefaultMixedHandler mixedHandler, PipTransitionController pipHandler,
+            StageCoordinator splitHandler, KeyguardTransitionHandler keyguardHandler,
+            RecentsTransitionHandler recentsHandler,
+            DesktopTasksController desktopTasksController) {
+        super(type, transition, player, mixedHandler, pipHandler, splitHandler, keyguardHandler);
+        mRecentsHandler = recentsHandler;
+        mDesktopTasksController = desktopTasksController;
+        mLeftoversHandler = mRecentsHandler;
+    }
+
+    @Override
+    boolean startAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP ->
+                    animateRecentsDuringDesktop(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_RECENTS_DURING_KEYGUARD ->
+                    animateRecentsDuringKeyguard(
+                            info, startTransaction, finishTransaction, finishCallback);
+            case TYPE_RECENTS_DURING_SPLIT ->
+                    animateRecentsDuringSplit(
+                            info, startTransaction, finishTransaction, finishCallback);
+            default -> throw new IllegalStateException(
+                    "Starting Recents mixed animation with unknown or illegal type: " + mType);
+        };
+    }
+
+    private boolean animateRecentsDuringDesktop(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mInfo == null) {
+            mInfo = info;
+            mFinishT = finishTransaction;
+            mFinishCB = finishCallback;
+        }
+        Transitions.TransitionFinishCallback finishCB = wct -> {
+            mInFlightSubAnimations--;
+            if (mInFlightSubAnimations == 0) {
+                finishCallback.onTransitionFinished(wct);
+            }
+        };
+
+        mInFlightSubAnimations++;
+        boolean consumed = mRecentsHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+        if (!consumed) {
+            mInFlightSubAnimations--;
+            return false;
+        }
+        if (mDesktopTasksController != null) {
+            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean animateRecentsDuringKeyguard(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mInfo == null) {
+            mInfo = info;
+            mFinishT = finishTransaction;
+            mFinishCB = finishCallback;
+        }
+        return startSubAnimation(mRecentsHandler, info, startTransaction, finishTransaction);
+    }
+
+    private boolean animateRecentsDuringSplit(
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            // Pip auto-entering info might be appended to recent transition like pressing
+            // home-key in 3-button navigation. This offers split handler the opportunity to
+            // handle split to pip animation.
+            if (mPipHandler.isEnteringPip(change, info.getType())
+                    && mSplitHandler.getSplitItemPosition(change.getLastParent())
+                    != SPLIT_POSITION_UNDEFINED) {
+                return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+                        finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+            }
+        }
+
+        // Split-screen is only interested in the recents transition finishing (and merging), so
+        // just wrap finish and start recents animation directly.
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
+            mInFlightSubAnimations = 0;
+            // If pair-to-pair switching, the post-recents clean-up isn't needed.
+            wct = wct != null ? wct : new WindowContainerTransaction();
+            if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) {
+                mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+            } else {
+                // notify pair-to-pair recents animation finish
+                mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
+            }
+            mSplitHandler.onTransitionAnimationComplete();
+            finishCallback.onTransitionFinished(wct);
+        };
+        mInFlightSubAnimations = 1;
+        mSplitHandler.onRecentsInSplitAnimationStart(info);
+        final boolean handled = mLeftoversHandler.startAnimation(
+                mTransition, info, startTransaction, finishTransaction, finishCB);
+        if (!handled) {
+            mSplitHandler.onRecentsInSplitAnimationCanceled();
+        }
+        return handled;
+    }
+
+    @Override
+    void mergeAnimation(
+            @NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP:
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            case TYPE_RECENTS_DURING_KEYGUARD:
+                if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+                    handoverTransitionLeashes(mInfo, info, t, mFinishT);
+                    if (animateKeyguard(
+                            this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+                        finishCallback.onTransitionFinished(null);
+                    }
+                }
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
+                return;
+            case TYPE_RECENTS_DURING_SPLIT:
+                if (mSplitHandler.isPendingEnter(transition)) {
+                    // Recents -> enter-split means that we are switching from one pair to
+                    // another pair.
+                    mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+                }
+                mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+                return;
+            default:
+                throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
+                        + " illegal type: " + mType);
+        }
+    }
+
+    @Override
+    void onTransitionConsumed(
+            @NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishT) {
+        switch (mType) {
+            case TYPE_RECENTS_DURING_DESKTOP:
+            case TYPE_RECENTS_DURING_SPLIT:
+                mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+                break;
+            default:
+                break;
+        }
+
+        if (mHasRequestToRemote) {
+            mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+        }
+    }
+}