Promote AppWindowToken.applyAnimationLocked() to the base class.

- Add WindowContainer#getAnimationAdapter for all window hierarchy.

- Extract logics related Remote animation / Local animation adapter
  creation from applyAnimationLocked into WC#getAnimationAdapter.

- Make RemoteAnimationRecord can accept in each window hierarchy.

Bug: 142617871
Bug: 131661052
Test: Refactoring, existing tests pass.
Change-Id: If1c39d09966d82653faf7ebd975592eaeacd7c24
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 982ef53..8bb545a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -214,10 +214,8 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -294,6 +292,7 @@
 import android.view.InputApplicationHandle;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager;
@@ -318,6 +317,7 @@
 import com.android.server.wm.ActivityMetricsLogger.WindowingModeTransitionInfoSnapshot;
 import com.android.server.wm.ActivityStack.ActivityState;
 import com.android.server.wm.WindowManagerService.H;
+import com.android.server.wm.utils.InsetUtils;
 
 import com.google.android.collect.Sets;
 
@@ -553,24 +553,6 @@
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
     /**
-     * This gets used during some open/close transitions as well as during a change transition
-     * where it represents the starting-state snapshot.
-     */
-    private AppWindowThumbnail mThumbnail;
-    private final Rect mTransitStartRect = new Rect();
-
-    /**
-     * If we are running an animation, this determines the transition type. Must be one of
-     * AppTransition.TRANSIT_* constants.
-     */
-    private int mTransit;
-
-    /**
-     * If we are running an animation, this determines the flags during this animation. Must be a
-     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
-     */
-    private int mTransitFlags;
-    /**
      * This leash is used to "freeze" the app surface in place after the state change, but before
      * the animation is ready to start.
      */
@@ -666,7 +648,6 @@
     // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
     boolean mIsExiting;
 
-    boolean mLaunchTaskBehind;
     boolean mEnteringAnimation;
 
     boolean mAppStopped;
@@ -678,15 +659,6 @@
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
     ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
 
-    /** Whether this token should be boosted at the top of all app window tokens. */
-    @VisibleForTesting boolean mNeedsZBoost;
-
-    /** Layer used to constrain the animation to a token's stack bounds. */
-    SurfaceControl mAnimationBoundsLayer;
-
-    /** Whether this token needs to create mAnimationBoundsLayer for cropping animations. */
-    boolean mNeedsAnimationBoundsLayer;
-
     private AppSaturationInfo mLastAppSaturationInfo;
 
     private final ColorDisplayService.ColorTransformController mColorTransformController =
@@ -712,10 +684,6 @@
     private final Configuration mTmpConfig = new Configuration();
     private final Rect mTmpBounds = new Rect();
 
-    private final Point mTmpPoint = new Point();
-    private final Rect mTmpRect = new Rect();
-    private final Rect mTmpPrevBounds = new Rect();
-
     // Token for targeting this activity for assist purposes.
     final Binder assistToken = new Binder();
 
@@ -4124,7 +4092,7 @@
             if (transit != WindowManager.TRANSIT_UNSET) {
                 if (mUseTransferredAnimation) {
                     runningAppAnimation = isAnimating();
-                } else if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) {
+                } else if (applyAnimation(lp, transit, visible, isVoiceInteraction)) {
                     runningAppAnimation = true;
                 }
                 delayed = runningAppAnimation;
@@ -5701,28 +5669,44 @@
         }
     }
 
-
     @VisibleForTesting
     boolean shouldAnimate(int transit) {
+        final Task task = getTask();
+        if (task != null && !task.shouldAnimate()) {
+            return false;
+        }
         final boolean isSplitScreenPrimary =
                 getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
         final boolean allowSplitScreenPrimaryAnimation = transit != TRANSIT_WALLPAPER_OPEN;
 
-        // Don't animate while the task runs recents animation but only if we are in the mode
-        // where we cancel with deferred screenshot, which means that the controller has
-        // transformed the task.
-        final RecentsAnimationController controller = mWmService.getRecentsAnimationController();
-        if (controller != null && controller.isAnimatingTask(getTask())
-                && controller.shouldDeferCancelUntilNextTransition()) {
-            return false;
-        }
-
         // We animate always if it's not split screen primary, and only some special cases in split
         // screen primary because it causes issues with stack clipping when we run an un-minimize
         // animation at the same time.
         return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation;
     }
 
+    @Override
+    boolean isChangingAppTransition() {
+        final Task task = getTask();
+        if (task != null) {
+            return task.isChangingAppTransition();
+        }
+        return super.isChangingAppTransition();
+    }
+
+    @Override
+    boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+            boolean isVoiceInteraction) {
+        if (mWmService.mDisableTransitionAnimation || !shouldAnimate(transit)) {
+            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
+                    "applyAnimation: transition animation is disabled or skipped. "
+                            + "container=%s", this);
+            cancelAnimation();
+            return false;
+        }
+        return super.applyAnimation(lp, transit, enter, isVoiceInteraction);
+    }
+
     /**
      * Creates a layer to apply crop to an animation.
      */
@@ -5736,171 +5720,6 @@
         return boundsLayer;
     }
 
-    boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction) {
-
-        if (mWmService.mDisableTransitionAnimation || !shouldAnimate(transit)) {
-            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
-                    "applyAnimation: transition animation is disabled or skipped. "
-                            + "atoken=%s", this);
-            cancelAnimation();
-            return false;
-        }
-
-        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
-        // to animate and it can cause strange artifacts when we unfreeze the display if some
-        // different animation is running.
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#applyAnimationLocked");
-        if (okToAnimate()) {
-            final AnimationAdapter adapter;
-            AnimationAdapter thumbnailAdapter = null;
-
-            final int appStackClipMode =
-                    getDisplayContent().mAppTransition.getAppStackClipMode();
-
-            // Separate position and size for use in animators.
-            mTmpRect.set(getAnimationBounds(appStackClipMode));
-            mTmpPoint.set(mTmpRect.left, mTmpRect.top);
-            mTmpRect.offsetTo(0, 0);
-
-            final boolean isChanging = AppTransition.isChangeTransit(transit) && enter
-                    && getDisplayContent().mChangingApps.contains(this);
-
-            // Delaying animation start isn't compatible with remote animations at all.
-            if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
-                    && !mSurfaceAnimator.isAnimationStartDelayed()) {
-                RemoteAnimationController.RemoteAnimationRecord adapters =
-                        getDisplayContent().mAppTransition.getRemoteAnimationController()
-                                .createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
-                                        (isChanging ? mTransitStartRect : null));
-                adapter = adapters.mAdapter;
-                thumbnailAdapter = adapters.mThumbnailAdapter;
-            } else if (isChanging) {
-                final float durationScale = mWmService.getTransitionAnimationScaleLocked();
-                mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
-                adapter = new LocalAnimationAdapter(
-                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                getDisplayContent().getDisplayInfo(), durationScale,
-                                true /* isAppAnimation */, false /* isThumbnail */),
-                        mWmService.mSurfaceAnimationRunner);
-                if (mThumbnail != null) {
-                    thumbnailAdapter = new LocalAnimationAdapter(
-                            new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                    getDisplayContent().getDisplayInfo(), durationScale,
-                                    true /* isAppAnimation */, true /* isThumbnail */),
-                            mWmService.mSurfaceAnimationRunner);
-                }
-                mTransit = transit;
-                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
-            } else {
-                mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM);
-
-                final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
-                if (a != null) {
-                    // Only apply corner radius to animation if we're not in multi window mode.
-                    // We don't want rounded corners when in pip or split screen.
-                    final float windowCornerRadius = !inMultiWindowMode()
-                            ? getDisplayContent().getWindowCornerRadius()
-                            : 0;
-                    adapter = new LocalAnimationAdapter(
-                            new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
-                                    getDisplayContent().mAppTransition.canSkipFirstFrame(),
-                                    appStackClipMode,
-                                    true /* isAppAnimation */,
-                                    windowCornerRadius),
-                            mWmService.mSurfaceAnimationRunner);
-                    if (a.getZAdjustment() == Animation.ZORDER_TOP) {
-                        mNeedsZBoost = true;
-                    }
-                    mTransit = transit;
-                    mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
-                } else {
-                    adapter = null;
-                }
-            }
-            if (adapter != null) {
-                startAnimation(getPendingTransaction(), adapter, !isVisible());
-                if (adapter.getShowWallpaper()) {
-                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                }
-                if (thumbnailAdapter != null) {
-                    mThumbnail.startAnimation(
-                            getPendingTransaction(), thumbnailAdapter, !isVisible());
-                }
-            }
-        } else {
-            cancelAnimation();
-        }
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
-        return isAnimating();
-    }
-
-    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction) {
-        final DisplayContent displayContent = getTask().getDisplayContent();
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        final int width = displayInfo.appWidth;
-        final int height = displayInfo.appHeight;
-        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
-                "applyAnimation: atoken=%s", this);
-
-        // Determine the visible rect to calculate the thumbnail clip
-        final WindowState win = findMainWindow();
-        final Rect frame = new Rect(0, 0, width, height);
-        final Rect displayFrame = new Rect(0, 0,
-                displayInfo.logicalWidth, displayInfo.logicalHeight);
-        final Rect insets = new Rect();
-        final Rect stableInsets = new Rect();
-        Rect surfaceInsets = null;
-        final boolean freeform = win != null && win.inFreeformWindowingMode();
-        if (win != null) {
-            // Containing frame will usually cover the whole screen, including dialog windows.
-            // For freeform workspace windows it will not cover the whole screen and it also
-            // won't exactly match the final freeform window frame (e.g. when overlapping with
-            // the status bar). In that case we need to use the final frame.
-            if (freeform) {
-                frame.set(win.getFrameLw());
-            } else if (win.isLetterboxedAppWindow()) {
-                frame.set(getTask().getBounds());
-            } else if (win.isDockedResizing()) {
-                // If we are animating while docked resizing, then use the stack bounds as the
-                // animation target (which will be different than the task bounds)
-                frame.set(getTask().getParent().getBounds());
-            } else {
-                frame.set(win.getContainingFrame());
-            }
-            surfaceInsets = win.getAttrs().surfaceInsets;
-            // XXX(b/72757033): These are insets relative to the window frame, but we're really
-            // interested in the insets relative to the frame we chose in the if-blocks above.
-            win.getContentInsets(insets);
-            win.getStableInsets(stableInsets);
-        }
-
-        if (mLaunchTaskBehind) {
-            // Differentiate the two animations. This one which is briefly on the screen
-            // gets the !enter animation, and the other activity which remains on the
-            // screen gets the enter animation. Both appear in the mOpeningApps set.
-            enter = false;
-        }
-        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
-                "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
-                        + "surfaceInsets=%s",
-                AppTransition.appTransitionToString(transit), enter, frame, insets, surfaceInsets);
-        final Configuration displayConfig = displayContent.getConfiguration();
-        final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
-                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
-                surfaceInsets, stableInsets, isVoiceInteraction, freeform, getTask().mTaskId);
-        if (a != null) {
-            if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
-            final int containingWidth = frame.width();
-            final int containingHeight = frame.height();
-            a.initialize(containingWidth, containingHeight, width, height);
-            a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
-        }
-        return a;
-    }
-
     @Override
     public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
         return mAnimatingActivityRegistry != null
@@ -6080,11 +5899,10 @@
         if (!isAnimating()) {
             return;
         }
-        final int taskId = getTask().mTaskId;
         final GraphicBuffer thumbnailHeader =
-                getDisplayContent().mAppTransition.getAppTransitionThumbnailHeader(taskId);
+                getDisplayContent().mAppTransition.getAppTransitionThumbnailHeader(getTask());
         if (thumbnailHeader == null) {
-            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "No thumbnail header bitmap for: %d", taskId);
+            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "No thumbnail header bitmap for: %s", getTask());
             return;
         }
         clearThumbnail();
@@ -6138,7 +5956,7 @@
         final Rect insets = win != null ? win.getContentInsets() : null;
         final Configuration displayConfig = mDisplayContent.getConfiguration();
         return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
-                appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode,
+                appRect, insets, thumbnailHeader, getTask(), displayConfig.uiMode,
                 displayConfig.orientation);
     }
 
@@ -6648,6 +6466,7 @@
     }
 
     @VisibleForTesting
+    @Override
     Rect getAnimationBounds(int appStackClipMode) {
         if (appStackClipMode == STACK_CLIP_BEFORE_ANIM && getStack() != null) {
             // Using the stack bounds here effectively applies the clipping before animation.
@@ -7722,4 +7541,35 @@
             System.arraycopy(translation, 0, mTranslation, 0, mTranslation.length);
         }
     }
+
+    @Override
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        final Task task = getTask();
+        final WindowState mainWindow = findMainWindow();
+        if (task == null || mainWindow == null) {
+            return null;
+        }
+        final Rect insets = new Rect();
+        mainWindow.getContentInsets(insets);
+        InsetUtils.addInsets(insets, getLetterboxInsets());
+        return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
+                record.mAdapter.mCapturedLeash, !task.fillsParent(),
+                mainWindow.mWinAnimator.mLastClipRect, insets,
+                getPrefixOrderIndex(), record.mAdapter.mPosition,
+                record.mAdapter.mStackBounds, task.getWindowConfiguration(),
+                false /*isNotInRecents*/,
+                record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
+                record.mStartBounds);
+    }
+
+    @Override
+    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
+            Rect outSurfaceInsets) {
+        final WindowState win = findMainWindow();
+        if (win == null) {
+            return;
+        }
+        win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c1143c8..cb9a200 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -219,7 +219,7 @@
     private int mNextAppTransitionExit;
     private int mNextAppTransitionInPlace;
 
-    // Keyed by task id.
+    // Keyed by WindowContainer hashCode.
     private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
             = new SparseArray<>();
     private IAppTransitionAnimationSpecsFuture mNextAppTransitionAnimationsSpecsFuture;
@@ -372,8 +372,9 @@
         setAppTransitionState(APP_STATE_TIMEOUT);
     }
 
-    GraphicBuffer getAppTransitionThumbnailHeader(int taskId) {
-        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+    GraphicBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+                container.hashCode());
         if (spec == null) {
             spec = mDefaultNextAppTransitionAnimationSpec;
         }
@@ -789,14 +790,15 @@
         }
     }
 
-    void getNextAppTransitionStartRect(int taskId, Rect rect) {
-        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(taskId);
+    void getNextAppTransitionStartRect(WindowContainer container, Rect rect) {
+        AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+                container.hashCode());
         if (spec == null) {
             spec = mDefaultNextAppTransitionAnimationSpec;
         }
         if (spec == null || spec.rect == null) {
-            Slog.e(TAG, "Starting rect for task: " + taskId + " requested, but not available",
-                    new Throwable());
+            Slog.e(TAG, "Starting rect for container: " + container
+                            + " requested, but not available", new Throwable());
             rect.setEmpty();
         } else {
             rect.set(spec.rect);
@@ -1065,7 +1067,7 @@
      * when a thumbnail is specified with the pending animation override.
      */
     Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
-            GraphicBuffer thumbnailHeader, final int taskId, int uiMode, int orientation) {
+            GraphicBuffer thumbnailHeader, WindowContainer container, int uiMode, int orientation) {
         Animation a;
         final int thumbWidthI = thumbnailHeader.getWidth();
         final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
@@ -1073,7 +1075,7 @@
         final int appWidth = appRect.width();
 
         float scaleW = appWidth / thumbWidth;
-        getNextAppTransitionStartRect(taskId, mTmpRect);
+        getNextAppTransitionStartRect(container, mTmpRect);
         final float fromX;
         float fromY;
         final float toX;
@@ -1226,7 +1228,7 @@
     Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
             int uiMode, int orientation, int transit, Rect containingFrame, Rect contentInsets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
-            int taskId) {
+            WindowContainer container) {
         Animation a;
         final int appWidth = containingFrame.width();
         final int appHeight = containingFrame.height();
@@ -1244,10 +1246,10 @@
                 final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
                 if (freeform && scaleUp) {
                     a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
-                            containingFrame, surfaceInsets, taskId);
+                            containingFrame, surfaceInsets, container);
                 } else if (freeform) {
                     a = createAspectScaledThumbnailExitFreeformAnimationLocked(
-                            containingFrame, surfaceInsets, taskId);
+                            containingFrame, surfaceInsets, container);
                 } else {
                     AnimationSet set = new AnimationSet(true);
 
@@ -1359,15 +1361,15 @@
     }
 
     private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
-            @Nullable Rect surfaceInsets, int taskId) {
-        getNextAppTransitionStartRect(taskId, mTmpRect);
+            @Nullable Rect surfaceInsets, WindowContainer container) {
+        getNextAppTransitionStartRect(container, mTmpRect);
         return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
                 true);
     }
 
     private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
-            @Nullable Rect surfaceInsets, int taskId) {
-        getNextAppTransitionStartRect(taskId, mTmpRect);
+            @Nullable Rect surfaceInsets, WindowContainer container) {
+        getNextAppTransitionStartRect(container, mTmpRect);
         return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
                 false);
     }
@@ -1469,10 +1471,10 @@
      * leaving, and the activity that is entering.
      */
     Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
-            int transit, int taskId) {
+            int transit, WindowContainer container) {
         final int appWidth = containingFrame.width();
         final int appHeight = containingFrame.height();
-        final GraphicBuffer thumbnailHeader = getAppTransitionThumbnailHeader(taskId);
+        final GraphicBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
         Animation a;
         getDefaultNextAppTransitionStartRect(mTmpRect);
         final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
@@ -1615,7 +1617,7 @@
     Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode,
             int orientation, Rect frame, Rect displayFrame, Rect insets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
-            boolean freeform, int taskId) {
+            boolean freeform, WindowContainer container) {
         Animation a;
         if (isKeyguardGoingAwayTransit(transit) && enter) {
             a = loadKeyguardExitAnimation(transit);
@@ -1679,7 +1681,7 @@
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
             a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    frame, transit, taskId);
+                    frame, transit, container);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
                             + "Callers=%s",
@@ -1692,7 +1694,7 @@
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
             a = createAspectScaledThumbnailEnterExitAnimationLocked(
                     getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
-                    insets, surfaceInsets, stableInsets, freeform, taskId);
+                    insets, surfaceInsets, stableInsets, freeform, container);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
                             + "Callers=%s",
@@ -1895,7 +1897,11 @@
                 for (int i = 0; i < specs.length; i++) {
                     AppTransitionAnimationSpec spec = specs[i];
                     if (spec != null) {
-                        mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec);
+                        final WindowContainer container = findTask(spec.taskId);
+                        if (container == null) {
+                            continue;
+                        }
+                        mNextAppTransitionAnimationsSpecs.put(container.hashCode(), spec);
                         if (i == 0) {
                             // In full screen mode, the transition code depends on the default spec
                             // to be set.
@@ -1912,6 +1918,19 @@
         }
     }
 
+    private Task findTask(int taskId) {
+        if (taskId < 0) {
+            return null;
+        }
+        ArrayList<Task> tasks = new ArrayList<>();
+        mDisplayContent.forAllTasks(task -> {
+            if (task.mTaskId == taskId) {
+                tasks.add(task);
+            }
+        });
+        return tasks.size() == 1 ? tasks.get(0) : null;
+    }
+
     void overridePendingAppTransitionMultiThumbFuture(
             IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
             boolean scaleUp) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 3bda0c2..bef6af3 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -410,7 +410,7 @@
             ActivityRecord activity = apps.valueAt(i);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now changing app %s", activity);
             activity.cancelAnimationOnly();
-            activity.applyAnimationLocked(null, transit, true, false);
+            activity.applyAnimation(null, transit, true, false);
             activity.updateReportedVisibilityLocked();
             mService.openSurfaceTransaction();
             try {
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index c23ffd9..efd1241 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -41,7 +41,6 @@
 import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -76,20 +75,20 @@
     }
 
     /**
-     * Creates an animation record for each individual {@link ActivityRecord}.
+     * Creates an animation record for each individual {@link WindowContainer}.
      *
-     * @param activity The app to animate.
+     * @param windowContainer The windows to animate.
      * @param position The position app bounds, in screen coordinates.
      * @param stackBounds The stack bounds of the app relative to position.
      * @param startBounds The stack bounds before the transition, in screen coordinates
      * @return The record representing animation(s) to run on the app.
      */
-    RemoteAnimationRecord createRemoteAnimationRecord(ActivityRecord activity, Point position,
-            Rect stackBounds, Rect startBounds) {
-        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): token=%s",
-                activity);
+    RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
+            Point position, Rect stackBounds, Rect startBounds) {
+        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
+                windowContainer);
         final RemoteAnimationRecord adapters =
-                new RemoteAnimationRecord(activity, position, stackBounds, startBounds);
+                new RemoteAnimationRecord(windowContainer, position, stackBounds, startBounds);
         mPendingAnimations.add(adapters);
         return adapters;
     }
@@ -169,11 +168,12 @@
             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
             final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
             if (target != null) {
-                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd token=%s", wrappers.mActivityRecord);
+                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd container=%s",
+                        wrappers.mWindowContainer);
                 targets.add(target);
             } else {
-                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tRemove token=%s",
-                        wrappers.mActivityRecord);
+                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tRemove container=%s",
+                        wrappers.mWindowContainer);
 
                 // We can't really start an animation but we still need to make sure to finish the
                 // pending animation that was started by SurfaceAnimator
@@ -228,7 +228,8 @@
                                 .onAnimationFinished(adapters.mThumbnailAdapter);
                     }
                     mPendingAnimations.remove(i);
-                    ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tapp=%s", adapters.mActivityRecord);
+                    ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tcontainer=%s",
+                            adapters.mWindowContainer);
                 }
 
                 for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
@@ -332,7 +333,7 @@
     };
 
     /**
-     * Contains information about a remote-animation for one AppWindowToken. This keeps track of,
+     * Contains information about a remote-animation for one WindowContainer. This keeps track of,
      * potentially, multiple animating surfaces (AdapterWrappers) associated with one
      * Window/Transition. For example, a change transition has an adapter controller for the
      * main window and an adapter controlling the start-state snapshot.
@@ -345,12 +346,12 @@
         RemoteAnimationAdapterWrapper mAdapter;
         RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
         RemoteAnimationTarget mTarget;
-        final ActivityRecord mActivityRecord;
+        final WindowContainer mWindowContainer;
         final Rect mStartBounds;
 
-        RemoteAnimationRecord(ActivityRecord activityRecord, Point endPos, Rect endBounds,
+        RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect endBounds,
                 Rect startBounds) {
-            mActivityRecord = activityRecord;
+            mWindowContainer = windowContainer;
             mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, endBounds);
             if (startBounds != null) {
                 mStartBounds = new Rect(startBounds);
@@ -366,31 +367,20 @@
         }
 
         RemoteAnimationTarget createRemoteAnimationTarget() {
-            final Task task = mActivityRecord.getTask();
-            final WindowState mainWindow = mActivityRecord.findMainWindow();
-            if (task == null || mainWindow == null || mAdapter == null
+            if (mAdapter == null
                     || mAdapter.mCapturedFinishCallback == null
                     || mAdapter.mCapturedLeash == null) {
                 return null;
             }
-            final Rect insets = new Rect();
-            mainWindow.getContentInsets(insets);
-            InsetUtils.addInsets(insets, mActivityRecord.getLetterboxInsets());
-            mTarget = new RemoteAnimationTarget(task.mTaskId, getMode(),
-                    mAdapter.mCapturedLeash, !mActivityRecord.fillsParent(),
-                    mainWindow.mWinAnimator.mLastClipRect, insets,
-                    mActivityRecord.getPrefixOrderIndex(), mAdapter.mPosition,
-                    mAdapter.mStackBounds, task.getWindowConfiguration(), false /*isNotInRecents*/,
-                    mThumbnailAdapter != null ? mThumbnailAdapter.mCapturedLeash : null,
-                    mStartBounds);
+            mTarget = mWindowContainer.createRemoteAnimationTarget(this);
             return mTarget;
         }
 
-        private int getMode() {
-            final DisplayContent dc = mActivityRecord.getDisplayContent();
-            if (dc.mOpeningApps.contains(mActivityRecord)) {
+        int getMode() {
+            final DisplayContent dc = mWindowContainer.getDisplayContent();
+            if (dc.mOpeningApps.contains(mWindowContainer)) {
                 return RemoteAnimationTarget.MODE_OPENING;
-            } else if (dc.mChangingApps.contains(mActivityRecord)) {
+            } else if (dc.mChangingApps.contains(mWindowContainer)) {
                 return RemoteAnimationTarget.MODE_CHANGING;
             } else {
                 return RemoteAnimationTarget.MODE_CLOSING;
@@ -398,12 +388,12 @@
         }
     }
 
-    private class RemoteAnimationAdapterWrapper implements AnimationAdapter {
+    class RemoteAnimationAdapterWrapper implements AnimationAdapter {
         private final RemoteAnimationRecord mRecord;
         SurfaceControl mCapturedLeash;
         private OnAnimationFinishedCallback mCapturedFinishCallback;
-        private final Point mPosition = new Point();
-        private final Rect mStackBounds = new Rect();
+        final Point mPosition = new Point();
+        final Rect mStackBounds = new Rect();
 
         RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
                 Rect stackBounds) {
@@ -423,7 +413,7 @@
             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
 
             // Restore z-layering, position and stack crop until client has a chance to modify it.
-            t.setLayer(animationLeash, mRecord.mActivityRecord.getPrefixOrderIndex());
+            t.setLayer(animationLeash, mRecord.mWindowContainer.getPrefixOrderIndex());
             if (mRecord.mStartBounds != null) {
                 t.setPosition(animationLeash, mRecord.mStartBounds.left, mRecord.mStartBounds.top);
                 t.setWindowCrop(animationLeash, mRecord.mStartBounds.width(),
@@ -464,7 +454,7 @@
 
         @Override
         public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.print("token="); pw.println(mRecord.mActivityRecord);
+            pw.print(prefix); pw.print("container="); pw.println(mRecord.mWindowContainer);
             if (mRecord.mTarget != null) {
                 pw.print(prefix); pw.println("Target:");
                 mRecord.mTarget.dump(pw, prefix + "  ");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8c03db5..92ff2dc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -51,6 +51,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
 
@@ -646,6 +647,18 @@
         return getAppAnimationLayer(ANIMATION_LAYER_HOME);
     }
 
+    boolean shouldAnimate() {
+        // Don't animate while the task runs recents animation but only if we are in the mode
+        // where we cancel with deferred screenshot, which means that the controller has
+        // transformed the task.
+        final RecentsAnimationController controller = mWmService.getRecentsAnimationController();
+        if (controller != null && controller.isAnimatingTask(this)
+                && controller.shouldDeferCancelUntilNextTransition()) {
+            return false;
+        }
+        return true;
+    }
+
     @Override
     SurfaceControl.Builder makeSurface() {
         return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
@@ -661,6 +674,22 @@
         return false;
     }
 
+    /**
+     * @return {@code true} if changing app transition is running.
+     */
+    @Override
+    boolean isChangingAppTransition() {
+        final ActivityRecord activity = getTopVisibleActivity();
+        return activity != null && getDisplayContent().mChangingApps.contains(activity);
+    }
+
+    @Override
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        final ActivityRecord activity = getTopVisibleActivity();
+        return activity != null ? activity.createRemoteAnimationTarget(record) : null;
+    }
+
     WindowState getTopVisibleAppMainWindow() {
         final ActivityRecord activity = getTopVisibleActivity();
         return activity != null ? activity.findMainWindow() : null;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index ca41bc5..8f8c7e7 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -71,6 +71,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -1868,4 +1869,11 @@
     AnimatingActivityRegistry getAnimatingActivityRegistry() {
         return mAnimatingActivityRegistry;
     }
+
+    @Override
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        final Task task = getTopChild();
+        return task != null ? task.createRemoteAnimationTarget(record) : null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3142da5..3632284 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -162,7 +162,8 @@
 
         final RecentsAnimationController recentsAnimationController =
                 mService.getRecentsAnimationController();
-        final boolean animationWallpaper = w.mActivityRecord != null && w.mActivityRecord.getAnimation() != null
+        final boolean animationWallpaper = w.mActivityRecord != null
+                && w.mActivityRecord.getAnimation() != null
                 && w.mActivityRecord.getAnimation().getShowWallpaper();
         final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
                 || animationWallpaper;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ac4f7b6..7ce2b5e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -24,8 +24,12 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.SurfaceControl.Transaction;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -36,7 +40,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
@@ -48,17 +54,24 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Trace;
+import android.util.Pair;
 import android.util.Pools;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
 import android.view.MagnificationSpec;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.view.animation.Animation;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.protolog.common.ProtoLog;
 import com.android.server.wm.SurfaceAnimator.Animatable;
 
 import java.io.PrintWriter;
@@ -189,6 +202,47 @@
         void onPreAssignChildLayers();
     }
 
+    /**
+     * True if this an AppWindowToken and the activity which created this was launched with
+     * ActivityOptions.setLaunchTaskBehind.
+     *
+     * TODO(b/142617871): We run a special animation when the activity was launched with that
+     * flag, but it's not necessary anymore. Keep the window invisible until the task is explicitly
+     * selected to suppress an animation, and remove this flag.
+     */
+    boolean mLaunchTaskBehind;
+
+    /**
+     * If we are running an animation, this determines the transition type. Must be one of
+     * {@link AppTransition#TransitionFlags}.
+     */
+    int mTransit;
+
+    /**
+     * If we are running an animation, this determines the flags during this animation. Must be a
+     * bitwise combination of AppTransition.TRANSIT_FLAG_* constants.
+     */
+    int mTransitFlags;
+
+    /** Whether this container should be boosted at the top of all its siblings. */
+    @VisibleForTesting boolean mNeedsZBoost;
+
+    /** Layer used to constrain the animation to a container's stack bounds. */
+    SurfaceControl mAnimationBoundsLayer;
+
+    /** Whether this container needs to create mAnimationBoundsLayer for cropping animations. */
+    boolean mNeedsAnimationBoundsLayer;
+
+    /**
+     * This gets used during some open/close transitions as well as during a change transition
+     * where it represents the starting-state snapshot.
+     */
+    AppWindowThumbnail mThumbnail;
+    final Rect mTransitStartRect = new Rect();
+    final Point mTmpPoint = new Point();
+    protected final Rect mTmpRect = new Rect();
+    final Rect mTmpPrevBounds = new Rect();
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -735,6 +789,13 @@
         return isAnimating(0 /* self only */);
     }
 
+    /**
+     * @return {@code true} if the container is in changing app transition.
+     */
+    boolean isChangingAppTransition() {
+        return false;
+    }
+
     void sendAppVisibilityToClients() {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
@@ -1423,6 +1484,203 @@
         return null;
     }
 
+    // TODO: Remove this and use #getBounds() instead once we set an app transition animation
+    // on TaskStack.
+    Rect getAnimationBounds(int appStackClipMode) {
+        return getBounds();
+    }
+
+    /**
+     * Applies the app transition animation according the given the layout properties in the
+     * window hierarchy.
+     *
+     * @param lp The layout parameters of the window.
+     * @param transit The app transition type indicates what kind of transition to be applied.
+     * @param enter Whether the app transition is entering transition or not.
+     * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
+     *
+     * @return {@code true} when the container applied the app transition, {@code false} if the
+     *         app transition is disabled or skipped.
+     *
+     * @see #getAnimationAdapter
+     */
+    boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+                           boolean isVoiceInteraction) {
+        if (mWmService.mDisableTransitionAnimation) {
+            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
+                    "applyAnimation: transition animation is disabled or skipped. "
+                            + "container=%s", this);
+            cancelAnimation();
+            return false;
+        }
+
+        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
+        // to animate and it can cause strange artifacts when we unfreeze the display if some
+        // different animation is running.
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
+            if (okToAnimate()) {
+                Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, transit,
+                        enter, isVoiceInteraction);
+                AnimationAdapter adapter = adapters.first;
+                AnimationAdapter thumbnailAdapter = adapters.second;
+                if (adapter != null) {
+                    startAnimation(getPendingTransaction(), adapter, !isVisible());
+                    if (adapter.getShowWallpaper()) {
+                        mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                    }
+                    if (thumbnailAdapter != null) {
+                        mThumbnail.startAnimation(
+                                getPendingTransaction(), thumbnailAdapter, !isVisible());
+                    }
+                }
+            } else {
+                cancelAnimation();
+            }
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+
+        return isAnimating();
+    }
+
+    /**
+     * Gets the {@link AnimationAdapter} according the given window layout properties in the window
+     * hierarchy.
+     *
+     * @return The return value will always contain two elements, one for normal animations and the
+     *         other for thumbnail animation, both can be {@code null}.
+     *
+     * @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord
+     * @See LocalAnimationAdapter
+     */
+    Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
+            int transit, boolean enter, boolean isVoiceInteraction) {
+        final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
+        final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode();
+
+        // Separate position and size for use in animators.
+        mTmpRect.set(getAnimationBounds(appStackClipMode));
+        mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+        mTmpRect.offsetTo(0, 0);
+
+        final RemoteAnimationController controller =
+                getDisplayContent().mAppTransition.getRemoteAnimationController();
+        final boolean isChanging = AppTransition.isChangeTransit(transit) && enter
+                && isChangingAppTransition();
+
+        // Delaying animation start isn't compatible with remote animations at all.
+        if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
+            final RemoteAnimationController.RemoteAnimationRecord adapters =
+                    controller.createRemoteAnimationRecord(this, mTmpPoint, mTmpRect,
+                            (isChanging ? mTransitStartRect : null));
+            resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
+        } else if (isChanging) {
+            final float durationScale = mWmService.getTransitionAnimationScaleLocked();
+            final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
+            mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
+
+            AnimationAdapter adapter = new LocalAnimationAdapter(
+                    new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, displayInfo,
+                            durationScale, true /* isAppAnimation */, false /* isThumbnail */),
+                    getSurfaceAnimationRunner());
+
+            AnimationAdapter thumbnailAdapter = null;
+            if (mThumbnail != null) {
+                thumbnailAdapter = new LocalAnimationAdapter(
+                        new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect, displayInfo,
+                                durationScale, true /* isAppAnimation */, true /* isThumbnail */),
+                        getSurfaceAnimationRunner());
+            }
+            resultAdapters = new Pair<>(adapter, thumbnailAdapter);
+            mTransit = transit;
+            mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
+        } else {
+            mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM);
+            final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
+
+            if (a != null) {
+                // Only apply corner radius to animation if we're not in multi window mode.
+                // We don't want rounded corners when in pip or split screen.
+                final float windowCornerRadius = !inMultiWindowMode()
+                        ? getDisplayContent().getWindowCornerRadius()
+                        : 0;
+                AnimationAdapter adapter = new LocalAnimationAdapter(
+                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
+                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
+                                appStackClipMode, true /* isAppAnimation */, windowCornerRadius),
+                        getSurfaceAnimationRunner());
+
+                resultAdapters = new Pair<>(adapter, null);
+                mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP;
+                mTransit = transit;
+                mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
+            } else {
+                resultAdapters = new Pair<>(null, null);
+            }
+        }
+        return resultAdapters;
+    }
+
+    final SurfaceAnimationRunner getSurfaceAnimationRunner() {
+        return mWmService.mSurfaceAnimationRunner;
+    }
+
+    private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+                                    boolean isVoiceInteraction) {
+        final DisplayContent displayContent = getDisplayContent();
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int width = displayInfo.appWidth;
+        final int height = displayInfo.appHeight;
+        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: container=%s", this);
+
+        // Determine the visible rect to calculate the thumbnail clip with
+        // getAnimationFrames.
+        final Rect frame = new Rect(0, 0, width, height);
+        final Rect displayFrame = new Rect(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        final Rect insets = new Rect();
+        final Rect stableInsets = new Rect();
+        final Rect surfaceInsets = new Rect();
+        getAnimationFrames(frame, insets, stableInsets, surfaceInsets);
+
+        if (mLaunchTaskBehind) {
+            // Differentiate the two animations. This one which is briefly on the screen
+            // gets the !enter animation, and the other one which remains on the
+            // screen gets the enter animation. Both appear in the mOpeningApps set.
+            enter = false;
+        }
+        ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+                "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s "
+                        + "surfaceInsets=%s",
+                AppTransition.appTransitionToString(transit), enter, frame, insets, surfaceInsets);
+        final Configuration displayConfig = displayContent.getConfiguration();
+        final Animation a = getDisplayContent().mAppTransition.loadAnimation(lp, transit, enter,
+                displayConfig.uiMode, displayConfig.orientation, frame, displayFrame, insets,
+                surfaceInsets, stableInsets, isVoiceInteraction, inFreeformWindowingMode(), this);
+        if (a != null) {
+            if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+            final int containingWidth = frame.width();
+            final int containingHeight = frame.height();
+            a.initialize(containingWidth, containingHeight, width, height);
+            a.scaleCurrentDuration(mWmService.getTransitionAnimationScaleLocked());
+        }
+        return a;
+    }
+
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        return null;
+    }
+
+    boolean okToDisplay() {
+        return mDisplayContent != null && mDisplayContent.okToDisplay();
+    }
+
+    boolean okToAnimate() {
+        return mDisplayContent != null && mDisplayContent.okToAnimate();
+    }
+
     @Override
     public void commitPendingTransaction() {
         scheduleAnimation();
@@ -1522,6 +1780,26 @@
         return getBounds();
     }
 
+    /**
+     * The {@code outFrame} retrieved by this method specifies where the animation will finish
+     * the entrance animation, as the next frame will display the window at these coordinates. In
+     * case of exit animation, this is where the animation will start, as the frame before the
+     * animation is displaying the window at these bounds.
+     *
+     * @param outFrame The bounds where entrance animation finishes or exit animation starts.
+     * @param outInsets Insets that are covered by system windows.
+     * @param outStableInsets Insets that determine the area covered by the stable system windows.
+     * @param outSurfaceInsets Positive insets between the drawing surface and window content.
+     */
+    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
+            Rect outSurfaceInsets) {
+        final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
+        outFrame.set(0, 0, displayInfo.appWidth, displayInfo.appHeight);
+        outInsets.setEmpty();
+        outStableInsets.setEmpty();
+        outSurfaceInsets.setEmpty();
+    }
+
     void getRelativeDisplayedPosition(Point outPos) {
         final Rect dispBounds = getDisplayedBounds();
         outPos.set(dispBounds.left, dispBounds.top);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d87c5bb..f7402e1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5404,4 +5404,29 @@
         }
         return mKeyInterceptionInfo;
     }
+
+    @Override
+    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
+            Rect outSurfaceInsets) {
+        // Containing frame will usually cover the whole screen, including dialog windows.
+        // For freeform workspace windows it will not cover the whole screen and it also
+        // won't exactly match the final freeform window frame (e.g. when overlapping with
+        // the status bar). In that case we need to use the final frame.
+        if (inFreeformWindowingMode()) {
+            outFrame.set(getFrameLw());
+        } else if (isLetterboxedAppWindow()) {
+            outFrame.set(getTask().getBounds());
+        } else if (isDockedResizing()) {
+            // If we are animating while docked resizing, then use the stack bounds as the
+            // animation target (which will be different than the task bounds)
+            outFrame.set(getTask().getParent().getBounds());
+        } else {
+            outFrame.set(getContainingFrame());
+        }
+        outSurfaceInsets.set(getAttrs().surfaceInsets);
+        // TODO(b/72757033): These are insets relative to the window frame, but we're really
+        // interested in the insets relative to the frame we chose in the if-blocks above.
+        getContentInsets(outInsets);
+        getStableInsets(outStableInsets);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index dda2a2d..88a1458 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -321,14 +321,6 @@
         return toString();
     }
 
-    boolean okToDisplay() {
-        return mDisplayContent != null && mDisplayContent.okToDisplay();
-    }
-
-    boolean okToAnimate() {
-        return mDisplayContent != null && mDisplayContent.okToAnimate();
-    }
-
     /**
      * Return whether windows from this token can layer above the
      * system bars, or in other words extend outside of the "Decor Frame"