Each displays can have individual app transition.

Include below refectoring items to support per display AppTransition:

WMS / AM refectoring parts:
- Move AppTransition related stuff from WMS into DisplayContent.
- Move WMS.prepareAppTransition into DisplayWindowController.
- Move WMS.executeAppTransition to DisplayWindowController.
- Move ATM.isNextTransitionForward to DisplayWindowController.
- Move WMS.getPendingAppTransition to DisplayWindowController.
- Move WMS.overrideAppTransition like APIs to DisplayWindowController.
- Move ActivityRecord.applyOptionsLocked to AppContainerController.
- Support tracing all display's AppTransition status for
  DisplayContent.pendingLayoutChanges & window hierachy update.
- Modify logics for AppTransition related caller parts.
- Move WindowSurfacePlacer.handleAppTransitionReadyLocked related
  stuffs into added class AppTransitionController.

WM unit test parts:
- Add test case for verifying app transition state per display:
  - AppTransitionTests.testAppTransitionStateForMultiDisplay
  - AppTransitionTests.testCleanAppTransitionWhenTaskStackReparent
- Rename WindowSurfacePlacerTest to AppTransitionControllerTest since
  the test is related handle AppTransition flow.

Bug: 111362605
Test: go/wm-smoke
Test: atest ActivityManagerTransitionSelectionTests
Test: atest ActivityManagerMultiDisplayTests
Test: atest FrameworksServicesTests for DisplayContent / AppTransition
      related tests.
Change-Id: Ic1793aa794eb161bec31fda57847a6ba2ff4f84f
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 865c774..47b4f47 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -18,16 +18,7 @@
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
-import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
-import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
-import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
-import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
-import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
-import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP;
-import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
-import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
@@ -156,7 +147,6 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Build;
@@ -177,8 +167,6 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager.LayoutParams;
@@ -1502,93 +1490,7 @@
     void applyOptionsLocked() {
         if (pendingOptions != null
                 && pendingOptions.getAnimationType() != ANIM_SCENE_TRANSITION) {
-            final int animationType = pendingOptions.getAnimationType();
-            switch (animationType) {
-                case ANIM_CUSTOM:
-                    service.mWindowManager.overridePendingAppTransition(
-                            pendingOptions.getPackageName(),
-                            pendingOptions.getCustomEnterResId(),
-                            pendingOptions.getCustomExitResId(),
-                            pendingOptions.getOnAnimationStartListener());
-                    break;
-                case ANIM_CLIP_REVEAL:
-                    service.mWindowManager.overridePendingAppTransitionClipReveal(
-                            pendingOptions.getStartX(), pendingOptions.getStartY(),
-                            pendingOptions.getWidth(), pendingOptions.getHeight());
-                    if (intent.getSourceBounds() == null) {
-                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                pendingOptions.getStartY(),
-                                pendingOptions.getStartX()+pendingOptions.getWidth(),
-                                pendingOptions.getStartY()+pendingOptions.getHeight()));
-                    }
-                    break;
-                case ANIM_SCALE_UP:
-                    service.mWindowManager.overridePendingAppTransitionScaleUp(
-                            pendingOptions.getStartX(), pendingOptions.getStartY(),
-                            pendingOptions.getWidth(), pendingOptions.getHeight());
-                    if (intent.getSourceBounds() == null) {
-                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                pendingOptions.getStartY(),
-                                pendingOptions.getStartX()+pendingOptions.getWidth(),
-                                pendingOptions.getStartY()+pendingOptions.getHeight()));
-                    }
-                    break;
-                case ANIM_THUMBNAIL_SCALE_UP:
-                case ANIM_THUMBNAIL_SCALE_DOWN:
-                    final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP);
-                    final GraphicBuffer buffer = pendingOptions.getThumbnail();
-                    service.mWindowManager.overridePendingAppTransitionThumb(buffer,
-                            pendingOptions.getStartX(), pendingOptions.getStartY(),
-                            pendingOptions.getOnAnimationStartListener(),
-                            scaleUp);
-                    if (intent.getSourceBounds() == null && buffer != null) {
-                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                pendingOptions.getStartY(),
-                                pendingOptions.getStartX() + buffer.getWidth(),
-                                pendingOptions.getStartY() + buffer.getHeight()));
-                    }
-                    break;
-                case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
-                case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
-                    final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
-                    final IAppTransitionAnimationSpecsFuture specsFuture =
-                            pendingOptions.getSpecsFuture();
-                    if (specsFuture != null) {
-                        service.mWindowManager.overridePendingAppTransitionMultiThumbFuture(
-                                specsFuture, pendingOptions.getOnAnimationStartListener(),
-                                animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
-                    } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
-                            && specs != null) {
-                        service.mWindowManager.overridePendingAppTransitionMultiThumb(
-                                specs, pendingOptions.getOnAnimationStartListener(),
-                                pendingOptions.getAnimationFinishedListener(), false);
-                    } else {
-                        service.mWindowManager.overridePendingAppTransitionAspectScaledThumb(
-                                pendingOptions.getThumbnail(),
-                                pendingOptions.getStartX(), pendingOptions.getStartY(),
-                                pendingOptions.getWidth(), pendingOptions.getHeight(),
-                                pendingOptions.getOnAnimationStartListener(),
-                                (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
-                        if (intent.getSourceBounds() == null) {
-                            intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
-                                    pendingOptions.getStartY(),
-                                    pendingOptions.getStartX() + pendingOptions.getWidth(),
-                                    pendingOptions.getStartY() + pendingOptions.getHeight()));
-                        }
-                    }
-                    break;
-                case ANIM_OPEN_CROSS_PROFILE_APPS:
-                    service.mWindowManager.overridePendingAppTransitionStartCrossProfileApps();
-                    break;
-                case ANIM_REMOTE_ANIMATION:
-                    service.mWindowManager.overridePendingAppTransitionRemote(
-                            pendingOptions.getRemoteAnimationAdapter());
-                    break;
-                default:
-                    Slog.e(TAG, "applyOptionsLocked: Unknown animationType=" + animationType);
-                    break;
-            }
-
+            mWindowContainerController.applyOptionsLocked(pendingOptions, intent);
             if (task == null) {
                 clearOptionsLocked(false /* withAbort */);
             } else {
@@ -2768,7 +2670,8 @@
                     preserveWindow);
             final ActivityLifecycleItem lifecycleItem;
             if (andResume) {
-                lifecycleItem = ResumeActivityItem.obtain(service.isNextTransitionForward());
+                lifecycleItem = ResumeActivityItem.obtain(
+                        getDisplay().getWindowContainerController().isNextTransitionForward());
             } else {
                 lifecycleItem = PauseActivityItem.obtain();
             }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 026c5cc..b8d85a9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -156,6 +156,7 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
 import com.android.server.wm.ConfigurationContainer;
+import com.android.server.wm.DisplayWindowController;
 import com.android.server.wm.StackWindowController;
 import com.android.server.wm.StackWindowListener;
 import com.android.server.wm.WindowManagerService;
@@ -2615,17 +2616,18 @@
         // that the previous one will be hidden soon.  This way it can know
         // to ignore it when computing the desired screen orientation.
         boolean anim = true;
+        final DisplayWindowController dwc = getDisplay().getWindowContainerController();
         if (prev != null) {
             if (prev.finishing) {
                 if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                         "Prepare close transition: prev=" + prev);
                 if (mStackSupervisor.mNoAnimActivities.contains(prev)) {
                     anim = false;
-                    mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                    dwc.prepareAppTransition(TRANSIT_NONE, false);
                 } else {
-                    mWindowManager.prepareAppTransition(prev.getTask() == next.getTask()
-                            ? TRANSIT_ACTIVITY_CLOSE
-                            : TRANSIT_TASK_CLOSE, false);
+                    dwc.prepareAppTransition(
+                            prev.getTask() == next.getTask() ? TRANSIT_ACTIVITY_CLOSE
+                                    : TRANSIT_TASK_CLOSE, false);
                 }
                 prev.setVisibility(false);
             } else {
@@ -2633,22 +2635,21 @@
                         "Prepare open transition: prev=" + prev);
                 if (mStackSupervisor.mNoAnimActivities.contains(next)) {
                     anim = false;
-                    mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                    dwc.prepareAppTransition(TRANSIT_NONE, false);
                 } else {
-                    mWindowManager.prepareAppTransition(prev.getTask() == next.getTask()
-                            ? TRANSIT_ACTIVITY_OPEN
-                            : next.mLaunchTaskBehind
-                                    ? TRANSIT_TASK_OPEN_BEHIND
-                                    : TRANSIT_TASK_OPEN, false);
+                    dwc.prepareAppTransition(
+                            prev.getTask() == next.getTask() ? TRANSIT_ACTIVITY_OPEN
+                                    : next.mLaunchTaskBehind ? TRANSIT_TASK_OPEN_BEHIND
+                                            : TRANSIT_TASK_OPEN, false);
                 }
             }
         } else {
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
             if (mStackSupervisor.mNoAnimActivities.contains(next)) {
                 anim = false;
-                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                dwc.prepareAppTransition(TRANSIT_NONE, false);
             } else {
-                mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false);
+                dwc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false);
             }
         }
 
@@ -2777,7 +2778,8 @@
                     next.clearOptionsLocked();
                     transaction.setLifecycleStateRequest(
                             ResumeActivityItem.obtain(next.app.getReportedProcState(),
-                                    mService.isNextTransitionForward()));
+                                    getDisplay().getWindowContainerController()
+                                            .isNextTransitionForward()));
                     mService.getLifecycleManager().scheduleTransaction(transaction);
 
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
@@ -2984,10 +2986,11 @@
         task.setFrontOfTask();
 
         if (!isHomeOrRecentsStack() || numActivities() > 0) {
+            final DisplayWindowController dwc = getDisplay().getWindowContainerController();
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                     "Prepare open transition: starting " + r);
             if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
-                mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
+                dwc.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
                 mStackSupervisor.mNoAnimActivities.add(r);
             } else {
                 int transit = TRANSIT_ACTIVITY_OPEN;
@@ -3006,7 +3009,7 @@
                         transit = TRANSIT_TASK_OPEN;
                     }
                 }
-                mWindowManager.prepareAppTransition(transit, keepCurTransition);
+                dwc.prepareAppTransition(transit, keepCurTransition);
                 mStackSupervisor.mNoAnimActivities.remove(r);
             }
             boolean doShow = true;
@@ -3635,8 +3638,8 @@
         int taskNdx = mTaskHistory.indexOf(finishedTask);
         final TaskRecord task = finishedTask;
         int activityNdx = task.mActivities.indexOf(r);
-        mWindowManager.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE,
-                false /* alwaysKeepCurrent */);
+        getDisplay().getWindowContainerController().prepareAppTransition(
+                TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
         finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false);
         finishedTask = task;
         // Also terminate any activities below it that aren't yet
@@ -3801,7 +3804,7 @@
                     mService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
                             task.taskId);
                 }
-                mWindowManager.prepareAppTransition(transit, false);
+                getDisplay().getWindowContainerController().prepareAppTransition(transit, false);
 
                 // Tell window manager to prepare for this one to be removed.
                 r.setVisibility(false);
@@ -3856,9 +3859,10 @@
     }
 
     private void prepareActivityHideTransitionAnimation(ActivityRecord r, int transit) {
-        mWindowManager.prepareAppTransition(transit, false);
+        final DisplayWindowController dwc = getDisplay().getWindowContainerController();
+        dwc.prepareAppTransition(transit, false);
         r.setVisibility(false);
-        mWindowManager.executeAppTransition();
+        dwc.executeAppTransition();
         if (!mStackSupervisor.mActivitiesWaitingForVisibleActivity.contains(r)) {
             mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(r);
         }
@@ -4602,7 +4606,7 @@
                 ActivityOptions.abort(options);
             }
         }
-        mWindowManager.prepareAppTransition(transit, false);
+        getDisplay().getWindowContainerController().prepareAppTransition(transit, false);
     }
 
     private void updateTaskMovement(TaskRecord task, boolean toFront) {
@@ -4671,7 +4675,8 @@
 
             if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr);
             if (noAnimation) {
-                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
+                getDisplay().getWindowContainerController().prepareAppTransition(
+                        TRANSIT_NONE, false);
                 if (r != null) {
                     mStackSupervisor.mNoAnimActivities.add(r);
                 }
@@ -4753,7 +4758,8 @@
         mTaskHistory.add(0, tr);
         updateTaskMovement(tr, false);
 
-        mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
+        getDisplay().getWindowContainerController().prepareAppTransition(
+                TRANSIT_TASK_TO_BACK, false);
         moveToBack("moveTaskToBackLocked", tr);
 
         if (inPinnedWindowingMode()) {
@@ -5077,8 +5083,8 @@
                             + r.intent.getComponent().flattenToShortString());
                     // Force the destroy to skip right to removal.
                     r.app = null;
-                    mWindowManager.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE,
-                            false /* alwaysKeepCurrent */);
+                    getDisplay().getWindowContainerController().prepareAppTransition(
+                            TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
                     finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false,
                             "handleAppCrashedLocked");
                 }
@@ -5410,7 +5416,7 @@
     }
 
     void executeAppTransition(ActivityOptions options) {
-        mWindowManager.executeAppTransition();
+        getDisplay().getWindowContainerController().executeAppTransition();
         ActivityOptions.abort(options);
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 17454b4..8f5e02d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1072,6 +1072,13 @@
         return foundResumed;
     }
 
+    private void executeAppTransitionForAllDisplay() {
+        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+            display.getWindowContainerController().executeAppTransition();
+        }
+    }
+
     /**
      * Pause all activities in either all of the stacks or just the back stacks.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
@@ -1421,6 +1428,8 @@
                 // Create activity launch transaction.
                 final ClientTransaction clientTransaction = ClientTransaction.obtain(
                         proc.getThread(), r.appToken);
+
+                final DisplayWindowController dwc = r.getDisplay().getWindowContainerController();
                 clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
@@ -1429,12 +1438,12 @@
                         mergedConfiguration.getOverrideConfiguration(), r.compat,
                         r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
                         r.icicle, r.persistentState, results, newIntents,
-                        mService.isNextTransitionForward(), profilerInfo));
+                        dwc.isNextTransitionForward(), profilerInfo));
 
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
                 if (andResume) {
-                    lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
+                    lifecycleItem = ResumeActivityItem.obtain(dwc.isNextTransitionForward());
                 } else {
                     lifecycleItem = PauseActivityItem.obtain();
                 }
@@ -3520,7 +3529,9 @@
         }
         if (stack.getDisplay().allResumedActivitiesComplete()) {
             ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-            mWindowManager.executeAppTransition();
+            // Make sure activity & window visibility should be identical
+            // for all displays in this stage.
+            executeAppTransitionForAllDisplay();
             return true;
         }
         return false;
@@ -4618,6 +4629,7 @@
                 // not run into issues where we still need to draw the task in recents but the
                 // docked stack is already created.
                 deferUpdateRecentsHomeStackBounds();
+                // TODO(multi-display): currently recents animation only support default display.
                 mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
             }
 
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index fa227a2..37ddaf9 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1502,7 +1502,7 @@
                 mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
-                mService.mWindowManager.executeAppTransition();
+                mTargetStack.getDisplay().getWindowContainerController().executeAppTransition();
             } else {
                 // If the target stack was not previously focusable (previous top running activity
                 // on that stack was not visible) then any prior calls to move the stack to the
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index 5b0a4a9..078ed0e 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -65,11 +65,8 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
-import static android.view.WindowManager.TRANSIT_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
 import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
 import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -259,6 +256,7 @@
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.DisplayWindowController;
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.WindowManagerService;
 
@@ -1622,8 +1620,8 @@
 
             if (self.isState(
                     ActivityStack.ActivityState.RESUMED, ActivityStack.ActivityState.PAUSING)) {
-                mWindowManager.overridePendingAppTransition(packageName,
-                        enterAnim, exitAnim, null);
+                self.getDisplay().getWindowContainerController().overridePendingAppTransition(
+                        packageName, enterAnim, exitAnim, null);
             }
 
             Binder.restoreCallingIdentity(origId);
@@ -2941,10 +2939,16 @@
             throw new IllegalArgumentException("Expected in-place ActivityOption " +
                     "with valid animation");
         }
-        mWindowManager.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false);
-        mWindowManager.overridePendingAppTransitionInPlace(activityOptions.getPackageName(),
-                activityOptions.getCustomInPlaceResId());
-        mWindowManager.executeAppTransition();
+        // Get top display of front most application.
+        final ActivityStack focusedStack = getTopDisplayFocusedStack();
+        if (focusedStack != null) {
+            final DisplayWindowController dwc =
+                    focusedStack.getDisplay().getWindowContainerController();
+            dwc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false);
+            dwc.overridePendingAppTransitionInPlace(activityOptions.getPackageName(),
+                    activityOptions.getCustomInPlaceResId());
+            dwc.executeAppTransition();
+        }
     }
 
     @Override
@@ -4384,13 +4388,6 @@
         return mKeyguardController.isKeyguardLocked();
     }
 
-    boolean isNextTransitionForward() {
-        int transit = mWindowManager.getPendingAppTransition();
-        return transit == TRANSIT_ACTIVITY_OPEN
-                || transit == TRANSIT_TASK_OPEN
-                || transit == TRANSIT_TASK_TO_FRONT;
-    }
-
     void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
@@ -5701,23 +5698,23 @@
         }
 
         @Override
-        public void notifyKeyguardFlagsChanged(@Nullable Runnable callback) {
+        public void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
             synchronized (mGlobalLock) {
 
                 // We might change the visibilities here, so prepare an empty app transition which
                 // might be overridden later if we actually change visibilities.
-                final boolean wasTransitionSet =
-                        mWindowManager.getPendingAppTransition() != TRANSIT_NONE;
+                final DisplayWindowController dwc = mStackSupervisor.getActivityDisplay(displayId)
+                        .getWindowContainerController();
+                final boolean wasTransitionSet = dwc.getPendingAppTransition() != TRANSIT_NONE;
                 if (!wasTransitionSet) {
-                    mWindowManager.prepareAppTransition(TRANSIT_NONE,
-                            false /* alwaysKeepCurrent */);
+                    dwc.prepareAppTransition(TRANSIT_NONE, false /* alwaysKeepCurrent */);
                 }
                 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
 
                 // If there was a transition set already we don't want to interfere with it as we
                 // might be starting it too early.
                 if (!wasTransitionSet) {
-                    mWindowManager.executeAppTransition();
+                    dwc.executeAppTransition();
                 }
             }
             if (callback != null) {
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 28b2a42..9c41c77 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -48,6 +48,7 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.ActivityTaskManagerInternal.SleepToken;
+import com.android.server.wm.DisplayWindowController;
 import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
@@ -160,9 +161,10 @@
         mWindowManager.deferSurfaceLayout();
         try {
             setKeyguardGoingAway(true);
-            mWindowManager.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
-                    false /* alwaysKeepCurrent */, convertTransitFlags(flags),
-                    false /* forceOverride */);
+            mStackSupervisor.getDefaultDisplay().getWindowContainerController()
+                    .prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY,
+                            false /* alwaysKeepCurrent */, convertTransitFlags(flags),
+                            false /* forceOverride */);
             updateKeyguardSleepToken();
 
             // Some stack visibility might change (e.g. docked stack)
@@ -285,8 +287,10 @@
         if (isKeyguardLocked()) {
             mWindowManager.deferSurfaceLayout();
             try {
-                mWindowManager.prepareAppTransition(resolveOccludeTransit(),
-                        false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */);
+                mStackSupervisor.getDefaultDisplay().getWindowContainerController()
+                        .prepareAppTransition(resolveOccludeTransit(),
+                                false /* alwaysKeepCurrent */, 0 /* flags */,
+                                true /* forceOverride */);
                 updateKeyguardSleepToken();
                 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 mWindowManager.executeAppTransition();
@@ -310,10 +314,12 @@
 
             // If we are about to unocclude the Keyguard, but we can dismiss it without security,
             // we immediately dismiss the Keyguard so the activity gets shown without a flicker.
+            final DisplayWindowController dwc =
+                    mStackSupervisor.getDefaultDisplay().getWindowContainerController();
             if (mKeyguardShowing && canDismissKeyguard()
-                    && mWindowManager.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
-                mWindowManager.prepareAppTransition(mBeforeUnoccludeTransit,
-                        false /* alwaysKeepCurrent */, 0 /* flags */, true /* forceOverride */);
+                    && dwc.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+                dwc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */,
+                        0 /* flags */, true /* forceOverride */);
                 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 mWindowManager.executeAppTransition();
             }
@@ -332,8 +338,10 @@
     }
 
     private int resolveOccludeTransit() {
+        final DisplayWindowController dwc =
+                mStackSupervisor.getDefaultDisplay().getWindowContainerController();
         if (mBeforeUnoccludeTransit != TRANSIT_UNSET
-                && mWindowManager.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
+                && dwc.getPendingAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
                 // TODO(b/113840485): Handle app transition for individual display.
                 && isDisplayOccluded(DEFAULT_DISPLAY)) {
 
@@ -344,7 +352,7 @@
         } else if (!isDisplayOccluded(DEFAULT_DISPLAY)) {
 
             // Save transit in case we dismiss/occlude Keyguard shortly after.
-            mBeforeUnoccludeTransit = mWindowManager.getPendingAppTransition();
+            mBeforeUnoccludeTransit = dwc.getPendingAppTransition();
             return TRANSIT_KEYGUARD_UNOCCLUDE;
         } else {
             return TRANSIT_KEYGUARD_OCCLUDE;
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index d5f2d34..bcebaaa 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -580,7 +580,10 @@
             mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
                     lockTaskModeState != LOCK_TASK_MODE_NONE);
             mSupervisor.resumeFocusedStacksTopActivitiesLocked();
-            mWindowManager.executeAppTransition();
+            final ActivityStack stack = task.getStack();
+            if (stack != null) {
+                stack.getDisplay().getWindowContainerController().executeAppTransition();
+            }
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
             mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
                     DEFAULT_DISPLAY, task.getStack(), true /* forceNonResizable */);
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index c5586bb..41d488ba 100644
--- a/services/core/java/com/android/server/am/RecentsAnimation.java
+++ b/services/core/java/com/android/server/am/RecentsAnimation.java
@@ -41,6 +41,8 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.view.IRecentsAnimationRunner;
+
+import com.android.server.wm.DisplayWindowController;
 import com.android.server.wm.RecentsAnimationController;
 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
 import com.android.server.wm.WindowManagerService;
@@ -85,10 +87,13 @@
                 + " assistDataReceiver=" + assistDataReceiver);
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
 
+        // TODO(multi-display) currently only support recents animation in default display.
+        final DisplayWindowController dwc =
+                mStackSupervisor.getDefaultDisplay().getWindowContainerController();
         if (!mWindowManager.canStartRecentsAnimation()) {
             notifyAnimationCancelBeforeStart(recentsAnimationRunner);
             if (DEBUG) Slog.d(TAG, "Can't start recents animation, nextAppTransition="
-                        + mWindowManager.getPendingAppTransition());
+                        + dwc.getPendingAppTransition());
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index b011da6..ae47c27 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -229,8 +229,9 @@
      * @param callback Callback to run after activity visibilities have been reevaluated. This can
      *                 be used from window manager so that when the callback is called, it's
      *                 guaranteed that all apps have their visibility updated accordingly.
+     * @param displayId The id of the display where the keyguard flags changed.
      */
-    public abstract void notifyKeyguardFlagsChanged(@Nullable Runnable callback);
+    public abstract void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId);
 
     /**
      * Called when the trusted state of Keyguard has changed.
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index a9d0978..10a1be5 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -166,6 +166,7 @@
 
     private final Context mContext;
     private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
 
     private @TransitionType int mNextAppTransition = TRANSIT_UNSET;
     private @TransitionFlags int mNextAppTransitionFlags = 0;
@@ -257,10 +258,11 @@
     final Handler mHandler;
     final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout();
 
-    AppTransition(Context context, WindowManagerService service) {
+    AppTransition(Context context, WindowManagerService service, DisplayContent displayContent) {
         mContext = context;
         mService = service;
         mHandler = new Handler(service.mH.getLooper());
+        mDisplayContent = displayContent;
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -426,7 +428,7 @@
                         ? topOpeningAnim.getStatusBarTransitionsStartTime()
                         : SystemClock.uptimeMillis(),
                 AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
-        mService.getDefaultDisplayContentLocked().getDockedDividerController()
+        mDisplayContent.getDockedDividerController()
                 .notifyAppTransitionStarting(openingApps, transit);
 
         if (mRemoteAnimationController != null) {
@@ -2142,7 +2144,8 @@
                 + " transit=" + appTransitionToString(transit)
                 + " " + this
                 + " alwaysKeepCurrent=" + alwaysKeepCurrent
-                + " Callers=" + Debug.getCallers(3));
+                + " displayId=" + mDisplayContent.getDisplayId()
+                + " Callers=" + Debug.getCallers(5));
         final boolean allowSetCrashing = !isKeyguardTransit(mNextAppTransition)
                 && transit == TRANSIT_CRASHING_ACTIVITY_CLOSE;
         if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet()
@@ -2218,14 +2221,18 @@
 
     private void handleAppTransitionTimeout() {
         synchronized (mService.mWindowMap) {
-            if (isTransitionSet() || !mService.mOpeningApps.isEmpty()
-                    || !mService.mClosingApps.isEmpty()) {
+            final DisplayContent dc = mDisplayContent;
+            if (dc == null) {
+                return;
+            }
+            if (isTransitionSet() || !dc.mOpeningApps.isEmpty() || !dc.mClosingApps.isEmpty()) {
                 if (DEBUG_APP_TRANSITIONS) {
                     Slog.v(TAG_WM, "*** APP TRANSITION TIMEOUT."
+                            + " displayId=" + dc.getDisplayId()
                             + " isTransitionSet()="
-                            + mService.mAppTransition.isTransitionSet()
-                            + " mOpeningApps.size()=" + mService.mOpeningApps.size()
-                            + " mClosingApps.size()=" + mService.mClosingApps.size());
+                            + dc.mAppTransition.isTransitionSet()
+                            + " mOpeningApps.size()=" + dc.mOpeningApps.size()
+                            + " mClosingApps.size()=" + dc.mClosingApps.size());
                 }
                 setTimeout();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
new file mode 100644
index 0000000..94a47dd
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2018 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.server.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_WALLPAPER_CLOSE;
+import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE;
+import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN;
+import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
+
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+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.H.NOTIFY_APP_TRANSITION_STARTING;
+
+import android.app.WindowConfiguration;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.function.Predicate;
+
+
+/**
+ * Checks for app transition readiness, resolves animation attributes and performs visibility
+ * change for apps that animate as part of an app transition.
+ */
+public class AppTransitionController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransitionController" : TAG_WM;
+    private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
+    private final WallpaperController mWallpaperControllerLocked;
+
+    private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
+
+    AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
+        mService = service;
+        mDisplayContent = displayContent;
+        mWallpaperControllerLocked = new WallpaperController(mService);
+    }
+
+    /**
+     * Handle application transition for given display.
+     */
+    void handleAppTransitionReady() {
+        final int appsCount = mDisplayContent.mOpeningApps.size();
+        if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
+            return;
+        }
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
+        int transit = mDisplayContent.mAppTransition.getAppTransition();
+        if (mDisplayContent.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
+            transit = WindowManager.TRANSIT_UNSET;
+        }
+        mDisplayContent.mSkipAppTransitionAnimation = false;
+        mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
+
+        mDisplayContent.mAppTransition.removeAppTransitionTimeoutCallbacks();
+
+        mService.mRoot.mWallpaperMayChange = false;
+
+        int i;
+        for (i = 0; i < appsCount; i++) {
+            final AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+            // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
+            // window is removed, or window relayout to invisible. This also affects window
+            // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
+            // transition selection depends on wallpaper target visibility.
+            wtoken.clearAnimatingFlags();
+        }
+
+        // Adjust wallpaper before we pull the lower/upper target, since pending changes
+        // (like the clearAnimatingFlags() above) might affect wallpaper target result.
+        // Or, the opening app window should be a wallpaper target.
+        mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(mDisplayContent,
+                mDisplayContent.mOpeningApps);
+
+        // Determine if closing and opening app token sets are wallpaper targets, in which case
+        // special animations are needed.
+        final boolean hasWallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget() != null;
+        final boolean openingAppHasWallpaper = canBeWallpaperTarget(mDisplayContent.mOpeningApps)
+                && hasWallpaperTarget;
+        final boolean closingAppHasWallpaper = canBeWallpaperTarget(mDisplayContent.mClosingApps)
+                && hasWallpaperTarget;
+
+        transit = maybeUpdateTransitToTranslucentAnim(transit);
+        transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper,
+                closingAppHasWallpaper);
+
+        // Find the layout params of the top-most application window in the tokens, which is
+        // what will control the animation theme. If all closing windows are obscured, then there is
+        // no need to do an animation. This is the case, for example, when this transition is being
+        // done behind a dream window.
+        final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
+                mDisplayContent.mClosingApps);
+        final boolean allowAnimations = mService.mPolicy.allowAppAnimationsLw();
+        final AppWindowToken animLpToken = allowAnimations
+                ? findAnimLayoutParamsToken(transit, activityTypes)
+                : null;
+        final AppWindowToken topOpeningApp = allowAnimations
+                ? getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */)
+                : null;
+        final AppWindowToken topClosingApp = allowAnimations
+                ? getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */)
+                : null;
+        final WindowManager.LayoutParams animLp = getAnimLp(animLpToken);
+        overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes);
+
+        final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
+                || containsVoiceInteraction(mDisplayContent.mOpeningApps);
+
+        final int layoutRedo;
+        mService.mSurfaceAnimationRunner.deferStartingAnimations();
+        try {
+            processApplicationsAnimatingInPlace(transit);
+
+            handleClosingApps(transit, animLp, voiceInteraction);
+            handleOpeningApps(transit, animLp, voiceInteraction);
+
+            mDisplayContent.mAppTransition.setLastAppTransition(transit, topOpeningApp,
+                    topClosingApp);
+
+            final int flags = mDisplayContent.mAppTransition.getTransitFlags();
+            layoutRedo = mDisplayContent.mAppTransition.goodToGo(transit, topOpeningApp,
+                    topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps);
+            handleNonAppWindowsInTransition(transit, flags);
+            mDisplayContent.mAppTransition.postAnimationCallback();
+            mDisplayContent.mAppTransition.clear();
+        } finally {
+            mService.mSurfaceAnimationRunner.continueStartingAnimations();
+        }
+
+        mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent);
+
+        mDisplayContent.mOpeningApps.clear();
+        mDisplayContent.mClosingApps.clear();
+        mDisplayContent.mUnknownAppVisibilityController.clear();
+
+        // This has changed the visibility of windows, so perform
+        // a new layout to get them all up-to-date.
+        mDisplayContent.setLayoutNeeded();
+
+        mDisplayContent.computeImeTarget(true /* updateImeTarget */);
+
+        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
+                mTempTransitionReasons.clone()).sendToTarget();
+
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+
+        mDisplayContent.pendingLayoutChanges |=
+                layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
+    }
+
+    private static WindowManager.LayoutParams getAnimLp(AppWindowToken wtoken) {
+        final WindowState mainWindow = wtoken != null ? wtoken.findMainWindow() : null;
+        return mainWindow != null ? mainWindow.mAttrs : null;
+    }
+
+    /**
+     * Overrides the pending transition with the remote animation defined for the transition in the
+     * set of defined remote animations in the app window token.
+     */
+    private void overrideWithRemoteAnimationIfSet(AppWindowToken animLpToken, int transit,
+            ArraySet<Integer> activityTypes) {
+        if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
+            // The crash transition has higher priority than any involved remote animations.
+            return;
+        }
+        if (animLpToken == null) {
+            return;
+        }
+        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
+        if (definition == null) {
+            return;
+        }
+        final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
+        if (adapter != null) {
+            animLpToken.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(
+                    adapter);
+        }
+    }
+
+    /**
+     * @return The window token that determines the animation theme.
+     */
+    private AppWindowToken findAnimLayoutParamsToken(@WindowManager.TransitionType int transit,
+            ArraySet<Integer> activityTypes) {
+        AppWindowToken result;
+        final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
+        final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+
+        // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
+        result = lookForHighestTokenWithFilter(closingApps, openingApps,
+                w -> w.getRemoteAnimationDefinition() != null
+                        && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
+        if (result != null) {
+            return result;
+        }
+        result = lookForHighestTokenWithFilter(closingApps, openingApps,
+                w -> w.fillsParent() && w.findMainWindow() != null);
+        if (result != null) {
+            return result;
+        }
+        return lookForHighestTokenWithFilter(closingApps, openingApps,
+                w -> w.findMainWindow() != null);
+    }
+
+    /**
+     * @return The set of {@link WindowConfiguration.ActivityType}s contained in the set of apps in
+     *         {@code array1} and {@code array2}.
+     */
+    private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
+            ArraySet<AppWindowToken> array2) {
+        final ArraySet<Integer> result = new ArraySet<>();
+        for (int i = array1.size() - 1; i >= 0; i--) {
+            result.add(array1.valueAt(i).getActivityType());
+        }
+        for (int i = array2.size() - 1; i >= 0; i--) {
+            result.add(array2.valueAt(i).getActivityType());
+        }
+        return result;
+    }
+
+    private static AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1,
+            ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) {
+        final int array1count = array1.size();
+        final int count = array1count + array2.size();
+        int bestPrefixOrderIndex = Integer.MIN_VALUE;
+        AppWindowToken bestToken = null;
+        for (int i = 0; i < count; i++) {
+            final AppWindowToken wtoken = i < array1count
+                    ? array1.valueAt(i)
+                    : array2.valueAt(i - array1count);
+            final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
+            if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
+                bestPrefixOrderIndex = prefixOrderIndex;
+                bestToken = wtoken;
+            }
+        }
+        return bestToken;
+    }
+
+    private boolean containsVoiceInteraction(ArraySet<AppWindowToken> apps) {
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            if (apps.valueAt(i).mVoiceInteraction) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+        final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+        final int appsCount = openingApps.size();
+        for (int i = 0; i < appsCount; i++) {
+            AppWindowToken wtoken = openingApps.valueAt(i);
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
+
+            if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)) {
+                // This token isn't going to be animating. Add it to the list of tokens to
+                // be notified of app transition complete since the notification will not be
+                // sent be the app window animator.
+                mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+            }
+            wtoken.updateReportedVisibilityLocked();
+            wtoken.waitingToShow = false;
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION handleAppTransitionReady()");
+            mService.openSurfaceTransaction();
+            try {
+                wtoken.showAllWindowsLocked();
+            } finally {
+                mService.closeSurfaceTransaction("handleAppTransitionReady");
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION handleAppTransitionReady()");
+            }
+
+            if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
+                wtoken.attachThumbnailAnimation();
+            } else if (mDisplayContent.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
+                wtoken.attachCrossProfileAppsThumbnailAnimation();
+            }
+        }
+    }
+
+    private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+        final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
+        final int appsCount = closingApps.size();
+        for (int i = 0; i < appsCount; i++) {
+            AppWindowToken wtoken = closingApps.valueAt(i);
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
+            // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
+            //       animating?
+            wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
+            wtoken.updateReportedVisibilityLocked();
+            // Force the allDrawn flag, because we want to start
+            // this guy's animations regardless of whether it's
+            // gotten drawn.
+            wtoken.allDrawn = true;
+            wtoken.deferClearAllDrawn = false;
+            // Ensure that apps that are mid-starting are also scheduled to have their
+            // starting windows removed after the animation is complete
+            if (wtoken.startingWindow != null && !wtoken.startingWindow.mAnimatingExit
+                    && wtoken.getController() != null) {
+                wtoken.getController().removeStartingWindow();
+            }
+
+            if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailDown()) {
+                wtoken.attachThumbnailAnimation();
+            }
+        }
+    }
+
+    private void handleNonAppWindowsInTransition(int transit, int flags) {
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
+                Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
+                        (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+                if (anim != null) {
+                    mDisplayContent.mWallpaperController.startWallpaperAnimation(anim);
+                }
+            }
+        }
+        if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+                || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+            mDisplayContent.startKeyguardExitOnNonAppWindows(
+                    transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+        }
+    }
+
+    private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                "Checking " + appsCount + " opening apps (frozen="
+                        + mService.mDisplayFrozen + " timeout="
+                        + mDisplayContent.mAppTransition.isTimeout() + ")...");
+        final ScreenRotationAnimation screenRotationAnimation =
+                mService.mAnimator.getScreenRotationAnimationLocked(
+                        Display.DEFAULT_DISPLAY);
+
+        outReasons.clear();
+        if (!mDisplayContent.mAppTransition.isTimeout()) {
+            // Imagine the case where we are changing orientation due to an app transition, but a
+            // previous orientation change is still in progress. We won't process the orientation
+            // change for our transition because we need to wait for the rotation animation to
+            // finish.
+            // If we start the app transition at this point, we will interrupt it halfway with a
+            // new rotation animation after the old one finally finishes. It's better to defer the
+            // app transition.
+            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
+                    mService.getDefaultDisplayContentLocked().rotationNeedsUpdate()) {
+                if (DEBUG_APP_TRANSITIONS) {
+                    Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
+                }
+                return false;
+            }
+            for (int i = 0; i < appsCount; i++) {
+                AppWindowToken wtoken = mDisplayContent.mOpeningApps.valueAt(i);
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "Check opening app=" + wtoken + ": allDrawn="
+                                + wtoken.allDrawn + " startingDisplayed="
+                                + wtoken.startingDisplayed + " startingMoved="
+                                + wtoken.startingMoved + " isRelaunching()="
+                                + wtoken.isRelaunching() + " startingWindow="
+                                + wtoken.startingWindow);
+
+
+                final boolean allDrawn = wtoken.allDrawn && !wtoken.isRelaunching();
+                if (!allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
+                    return false;
+                }
+                final int windowingMode = wtoken.getWindowingMode();
+                if (allDrawn) {
+                    outReasons.put(windowingMode,  APP_TRANSITION_WINDOWS_DRAWN);
+                } else {
+                    outReasons.put(windowingMode,
+                            wtoken.startingData instanceof SplashScreenStartingData
+                                    ? APP_TRANSITION_SPLASH_SCREEN
+                                    : APP_TRANSITION_SNAPSHOT);
+                }
+            }
+
+            // We also need to wait for the specs to be fetched, if needed.
+            if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
+                return false;
+            }
+
+            if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
+                if (DEBUG_APP_TRANSITIONS) {
+                    Slog.v(TAG, "unknownApps is not empty: "
+                            + mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
+                }
+                return false;
+            }
+
+            // If the wallpaper is visible, we need to check it's ready too.
+            boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
+                    mWallpaperControllerLocked.wallpaperTransitionReady();
+            if (wallpaperReady) {
+                return true;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private int maybeUpdateTransitToWallpaper(int transit, boolean openingAppHasWallpaper,
+            boolean closingAppHasWallpaper) {
+        // Given no app transition pass it through instead of a wallpaper transition.
+        // Never convert the crashing transition.
+        // Never update the transition for the wallpaper if we are just docking from recents
+        if (transit == TRANSIT_NONE || transit == TRANSIT_CRASHING_ACTIVITY_CLOSE
+                || transit == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+            return transit;
+        }
+
+        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
+        final boolean showWallpaper = wallpaperTarget != null
+                && (wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+        // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
+        // don't consider upgrading to wallpaper transition.
+        final WindowState oldWallpaper =
+                (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper)
+                        ? null
+                        : wallpaperTarget;
+        final ArraySet<AppWindowToken> openingApps = mDisplayContent.mOpeningApps;
+        final ArraySet<AppWindowToken> closingApps = mDisplayContent.mClosingApps;
+        final AppWindowToken topOpeningApp = getTopApp(mDisplayContent.mOpeningApps,
+                false /* ignoreHidden */);
+        final AppWindowToken topClosingApp = getTopApp(mDisplayContent.mClosingApps,
+                true /* ignoreHidden */);
+
+        boolean openingCanBeWallpaperTarget = canBeWallpaperTarget(openingApps);
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                "New wallpaper target=" + wallpaperTarget
+                        + ", oldWallpaper=" + oldWallpaper
+                        + ", openingApps=" + openingApps
+                        + ", closingApps=" + closingApps);
+
+        if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+            transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "New transit: " + AppTransition.appTransitionToString(transit));
+        }
+        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
+        // relies on the fact that we always execute a Keyguard transition after preparing one.
+        else if (!isKeyguardGoingAwayTransit(transit)) {
+            if (closingAppHasWallpaper && openingAppHasWallpaper) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
+                switch (transit) {
+                    case TRANSIT_ACTIVITY_OPEN:
+                    case TRANSIT_TASK_OPEN:
+                    case TRANSIT_TASK_TO_FRONT:
+                        transit = TRANSIT_WALLPAPER_INTRA_OPEN;
+                        break;
+                    case TRANSIT_ACTIVITY_CLOSE:
+                    case TRANSIT_TASK_CLOSE:
+                    case TRANSIT_TASK_TO_BACK:
+                        transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
+                        break;
+                }
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "New transit: " + AppTransition.appTransitionToString(transit));
+            } else if (oldWallpaper != null && !mDisplayContent.mOpeningApps.isEmpty()
+                    && !openingApps.contains(oldWallpaper.mAppToken)
+                    && closingApps.contains(oldWallpaper.mAppToken)
+                    && topClosingApp == oldWallpaper.mAppToken) {
+                // We are transitioning from an activity with a wallpaper to one without.
+                transit = TRANSIT_WALLPAPER_CLOSE;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw()
+                    && openingApps.contains(wallpaperTarget.mAppToken)
+                    && topOpeningApp == wallpaperTarget.mAppToken
+                    && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE) {
+                // We are transitioning from an activity without
+                // a wallpaper to now showing the wallpaper
+                transit = TRANSIT_WALLPAPER_OPEN;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
+                        + AppTransition.appTransitionToString(transit));
+            }
+        }
+        return transit;
+    }
+
+    /**
+     * There are cases where we open/close a new task/activity, but in reality only a translucent
+     * activity on top of existing activities is opening/closing. For that one, we have a different
+     * animation because non of the task/activity animations actually work well with translucent
+     * apps.
+     *
+     * @param transit The current transition type.
+     * @return The current transition type or
+     *         {@link WindowManager#TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE}/
+     *         {@link WindowManager#TRANSIT_TRANSLUCENT_ACTIVITY_OPEN} if appropriate for the
+     *         situation.
+     */
+    @VisibleForTesting
+    int maybeUpdateTransitToTranslucentAnim(int transit) {
+        final boolean taskOrActivity = AppTransition.isTaskTransit(transit)
+                || AppTransition.isActivityTransit(transit);
+        boolean allOpeningVisible = true;
+        boolean allTranslucentOpeningApps = !mDisplayContent.mOpeningApps.isEmpty();
+        for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mDisplayContent.mOpeningApps.valueAt(i);
+            if (!token.isVisible()) {
+                allOpeningVisible = false;
+                if (token.fillsParent()) {
+                    allTranslucentOpeningApps = false;
+                }
+            }
+        }
+        boolean allTranslucentClosingApps = !mDisplayContent.mClosingApps.isEmpty();
+        for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) {
+            if (mDisplayContent.mClosingApps.valueAt(i).fillsParent()) {
+                allTranslucentClosingApps = false;
+                break;
+            }
+        }
+
+        if (taskOrActivity && allTranslucentClosingApps && allOpeningVisible) {
+            return TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE;
+        }
+        if (taskOrActivity && allTranslucentOpeningApps && mDisplayContent.mClosingApps.isEmpty()) {
+            return TRANSIT_TRANSLUCENT_ACTIVITY_OPEN;
+        }
+        return transit;
+    }
+
+    private boolean canBeWallpaperTarget(ArraySet<AppWindowToken> apps) {
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Finds the top app in a list of apps, using its {@link AppWindowToken#getPrefixOrderIndex} to
+     * compare z-order.
+     *
+     * @param apps The list of apps to search.
+     * @param ignoreHidden If set to true, ignores apps that are {@link AppWindowToken#isHidden}.
+     * @return The top {@link AppWindowToken}.
+     */
+    private AppWindowToken getTopApp(ArraySet<AppWindowToken> apps, boolean ignoreHidden) {
+        int topPrefixOrderIndex = Integer.MIN_VALUE;
+        AppWindowToken topApp = null;
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            final AppWindowToken app = apps.valueAt(i);
+            if (ignoreHidden && app.isHidden()) {
+                continue;
+            }
+            final int prefixOrderIndex = app.getPrefixOrderIndex();
+            if (prefixOrderIndex > topPrefixOrderIndex) {
+                topPrefixOrderIndex = prefixOrderIndex;
+                topApp = app;
+            }
+        }
+        return topApp;
+    }
+
+    private void processApplicationsAnimatingInPlace(int transit) {
+        if (transit == TRANSIT_TASK_IN_PLACE) {
+            // Find the focused window
+            final WindowState win = mDisplayContent.findFocusedWindow();
+            if (win != null) {
+                final AppWindowToken wtoken = win.mAppToken;
+                if (DEBUG_APP_TRANSITIONS)
+                    Slog.v(TAG, "Now animating app in place " + wtoken);
+                wtoken.cancelAnimation();
+                wtoken.applyAnimationLocked(null, transit, false, false);
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.showAllWindowsLocked();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 330c54c..7435ea5 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -16,6 +16,15 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
+import static android.app.ActivityOptions.ANIM_CUSTOM;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
+import static android.app.ActivityOptions.ANIM_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
+import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 
@@ -30,14 +39,20 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.app.ActivityManager.TaskSnapshot;
+import android.app.ActivityOptions;
+import android.content.Intent;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
@@ -324,6 +339,7 @@
             }
 
             final AppWindowToken wtoken = mContainer;
+            final AppTransition appTransition = mContainer.getDisplayContent().mAppTransition;
 
             // Don't set visibility to false if we were already not visible. This prevents WM from
             // adding the app to the closing app list which doesn't make sense for something that is
@@ -344,12 +360,13 @@
             }
 
             if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG_WM, "setAppVisibility("
-                    + mToken + ", visible=" + visible + "): " + mService.mAppTransition
+                    + mToken + ", visible=" + visible + "): " + appTransition
                     + " hidden=" + wtoken.isHidden() + " hiddenRequested="
                     + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6));
 
-            mService.mOpeningApps.remove(wtoken);
-            mService.mClosingApps.remove(wtoken);
+            final DisplayContent displayContent = mContainer.getDisplayContent();
+            displayContent.mOpeningApps.remove(wtoken);
+            displayContent.mClosingApps.remove(wtoken);
             wtoken.waitingToShow = false;
             wtoken.hiddenRequested = !visible;
             wtoken.mDeferHidingClient = deferHidingClient;
@@ -360,12 +377,12 @@
                 // if made visible again.
                 wtoken.removeDeadWindows();
             } else {
-                if (!mService.mAppTransition.isTransitionSet()
-                        && mService.mAppTransition.isReady()) {
+                if (!appTransition.isTransitionSet()
+                        && appTransition.isReady()) {
                     // Add the app mOpeningApps if transition is unset but ready. This means
                     // we're doing a screen freeze, and the unfreeze will wait for all opening
                     // apps to be ready.
-                    mService.mOpeningApps.add(wtoken);
+                    displayContent.mOpeningApps.add(wtoken);
                 }
                 wtoken.startingMoved = false;
                 // If the token is currently hidden (should be the common case), or has been
@@ -395,16 +412,16 @@
 
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
-            if (wtoken.okToAnimate() && mService.mAppTransition.isTransitionSet()) {
+            if (wtoken.okToAnimate() && appTransition.isTransitionSet()) {
                 wtoken.inPendingTransaction = true;
                 if (visible) {
-                    mService.mOpeningApps.add(wtoken);
+                    displayContent.mOpeningApps.add(wtoken);
                     wtoken.mEnteringAnimation = true;
                 } else {
-                    mService.mClosingApps.add(wtoken);
+                    displayContent.mClosingApps.add(wtoken);
                     wtoken.mEnteringAnimation = false;
                 }
-                if (mService.mAppTransition.getAppTransition()
+                if (appTransition.getAppTransition()
                         == WindowManager.TRANSIT_TASK_OPEN_BEHIND) {
                     // We're launchingBehind, add the launching activity to mOpeningApps.
                     final WindowState win = mContainer.getDisplayContent().findFocusedWindow();
@@ -415,7 +432,7 @@
                                     + " adding " + focusedToken + " to mOpeningApps");
                             // Force animation to be loaded.
                             focusedToken.setHidden(true);
-                            mService.mOpeningApps.add(focusedToken);
+                            displayContent.mOpeningApps.add(focusedToken);
                         }
                     }
                 }
@@ -434,7 +451,8 @@
     public void notifyUnknownVisibilityLaunched() {
         synchronized(mWindowMap) {
             if (mContainer != null) {
-                mService.mUnknownAppVisibilityController.notifyLaunched(mContainer);
+                mContainer.getDisplayContent().mUnknownAppVisibilityController.notifyLaunched(
+                        mContainer);
             }
         }
     }
@@ -547,7 +565,8 @@
     private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
             boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
             TaskSnapshot snapshot) {
-        if (mService.mAppTransition.getAppTransition() == TRANSIT_DOCK_TASK_FROM_RECENTS) {
+        if (mContainer.getDisplayContent().mAppTransition.getAppTransition()
+                == TRANSIT_DOCK_TASK_FROM_RECENTS) {
             // TODO(b/34099271): Remove this statement to add back the starting window and figure
             // out why it causes flickering, the starting window appears over the thumbnail while
             // the docked from recents transition occurs
@@ -753,6 +772,104 @@
     }
 
     /**
+     * Apply override app transition base on options & animation type.
+     */
+    public void applyOptionsLocked(ActivityOptions pendingOptions, Intent intent) {
+        synchronized (mWindowMap) {
+            final int animationType = pendingOptions.getAnimationType();
+            final DisplayContent displayContent = mContainer.getDisplayContent();
+            switch (animationType) {
+                case ANIM_CUSTOM:
+                    displayContent.mAppTransition.overridePendingAppTransition(
+                            pendingOptions.getPackageName(),
+                            pendingOptions.getCustomEnterResId(),
+                            pendingOptions.getCustomExitResId(),
+                            pendingOptions.getOnAnimationStartListener());
+                    break;
+                case ANIM_CLIP_REVEAL:
+                    displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getWidth(), pendingOptions.getHeight());
+                    if (intent.getSourceBounds() == null) {
+                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                pendingOptions.getStartY(),
+                                pendingOptions.getStartX() + pendingOptions.getWidth(),
+                                pendingOptions.getStartY() + pendingOptions.getHeight()));
+                    }
+                    break;
+                case ANIM_SCALE_UP:
+                    displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getWidth(), pendingOptions.getHeight());
+                    if (intent.getSourceBounds() == null) {
+                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                pendingOptions.getStartY(),
+                                pendingOptions.getStartX() + pendingOptions.getWidth(),
+                                pendingOptions.getStartY() + pendingOptions.getHeight()));
+                    }
+                    break;
+                case ANIM_THUMBNAIL_SCALE_UP:
+                case ANIM_THUMBNAIL_SCALE_DOWN:
+                    final boolean scaleUp = (animationType == ANIM_THUMBNAIL_SCALE_UP);
+                    final GraphicBuffer buffer = pendingOptions.getThumbnail();
+                    displayContent.mAppTransition.overridePendingAppTransitionThumb(buffer,
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getOnAnimationStartListener(),
+                            scaleUp);
+                    if (intent.getSourceBounds() == null && buffer != null) {
+                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                pendingOptions.getStartY(),
+                                pendingOptions.getStartX() + buffer.getWidth(),
+                                pendingOptions.getStartY() + buffer.getHeight()));
+                    }
+                    break;
+                case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+                case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+                    final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs();
+                    final IAppTransitionAnimationSpecsFuture specsFuture =
+                            pendingOptions.getSpecsFuture();
+                    if (specsFuture != null) {
+                        // TODO(multidisplay): Shouldn't be really used anymore from next CL.
+                        displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(
+                                specsFuture, pendingOptions.getOnAnimationStartListener(),
+                                animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP);
+                    } else if (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_DOWN
+                            && specs != null) {
+                        displayContent.mAppTransition.overridePendingAppTransitionMultiThumb(
+                                specs, pendingOptions.getOnAnimationStartListener(),
+                                pendingOptions.getAnimationFinishedListener(), false);
+                    } else {
+                        displayContent.mAppTransition.overridePendingAppTransitionAspectScaledThumb(
+                                pendingOptions.getThumbnail(),
+                                pendingOptions.getStartX(), pendingOptions.getStartY(),
+                                pendingOptions.getWidth(), pendingOptions.getHeight(),
+                                pendingOptions.getOnAnimationStartListener(),
+                                (animationType == ANIM_THUMBNAIL_ASPECT_SCALE_UP));
+                        if (intent.getSourceBounds() == null) {
+                            intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                    pendingOptions.getStartY(),
+                                    pendingOptions.getStartX() + pendingOptions.getWidth(),
+                                    pendingOptions.getStartY() + pendingOptions.getHeight()));
+                        }
+                    }
+                    break;
+                case ANIM_OPEN_CROSS_PROFILE_APPS:
+                    displayContent.mAppTransition
+                            .overridePendingAppTransitionStartCrossProfileApps();
+                    break;
+                case ANIM_REMOTE_ANIMATION:
+                    // TODO(multidisplay): Will pass displayId and adjust dependencies from next CL.
+                    displayContent.mAppTransition.overridePendingAppTransitionRemote(
+                            pendingOptions.getRemoteAnimationAdapter());
+                    break;
+                default:
+                    Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType);
+                    break;
+            }
+        }
+    }
+
+    /**
      * Notifies AWT that this app is waiting to pause in order to determine if it will enter PIP.
      * This information helps AWT know that the app is in the process of pausing before it gets the
      * signal on the WM side.
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index ad92f81..729f89b 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -96,7 +96,7 @@
         anim.scaleCurrentDuration(mAppToken.mService.getTransitionAnimationScaleLocked());
         mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
                 new WindowAnimationSpec(anim, position,
-                        mAppToken.mService.mAppTransition.canSkipFirstFrame()),
+                        mAppToken.getDisplayContent().mAppTransition.canSkipFirstFrame()),
                 mAppToken.mService.mSurfaceAnimationRunner), false /* hidden */);
     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e38e229..9baafcb 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -111,6 +111,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 class AppTokenList extends ArrayList<AppWindowToken> {
 }
@@ -500,14 +501,14 @@
                 setClientHidden(!visible);
             }
 
-            if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
+            if (!getDisplayContent().mClosingApps.contains(this)
+                    && !getDisplayContent().mOpeningApps.contains(this)) {
                 // The token is not closing nor opening, so even if there is an animation set, that
                 // doesn't mean that it goes through the normal app transition cycle so we have
                 // to inform the docked controller about visibility change.
                 // TODO(multi-display): notify docked divider on all displays where visibility was
                 // affected.
-                mService.getDefaultDisplayContentLocked().getDockedDividerController()
-                        .notifyAppVisibilityChanged();
+                getDisplayContent().getDockedDividerController().notifyAppVisibilityChanged();
 
                 // Take the screenshot before possibly hiding the WSA, otherwise the screenshot
                 // will not be taken.
@@ -524,7 +525,7 @@
             // no animation but there will still be a transition set.
             // We still need to delay hiding the surface such that it
             // can be synchronized with showing the next surface in the transition.
-            if (isHidden() && !delayed && !mService.mAppTransition.isTransitionSet()) {
+            if (isHidden() && !delayed && !getDisplayContent().mAppTransition.isTransitionSet()) {
                 SurfaceControl.openTransaction();
                 for (int i = mChildren.size() - 1; i >= 0; i--) {
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
@@ -630,14 +631,14 @@
 
         boolean delayed = setVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
 
-        mService.mOpeningApps.remove(this);
-        mService.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+        getDisplayContent().mOpeningApps.remove(this);
+        getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mService.mTaskSnapshotController.onAppRemoved(this);
         waitingToShow = false;
-        if (mService.mClosingApps.contains(this)) {
+        if (getDisplayContent().mClosingApps.contains(this)) {
             delayed = true;
-        } else if (mService.mAppTransition.isTransitionSet()) {
-            mService.mClosingApps.add(this);
+        } else if (getDisplayContent().mAppTransition.isTransitionSet()) {
+            getDisplayContent().mClosingApps.add(this);
             delayed = true;
         }
 
@@ -652,10 +653,10 @@
         }
 
         // If this window was animating, then we need to ensure that the app transition notifies
-        // that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
-        // add to that list now
+        // that animations have completed in DisplayContent.handleAnimatingStoppedAndTransition(),
+        // so add to that list now
         if (isSelfAnimating()) {
-            mService.mNoAnimationNotifyOnTransitionFinished.add(token);
+            getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
         }
 
         final TaskStack stack = getStack();
@@ -795,7 +796,7 @@
             if (task == null) {
                 // It is possible we have been marked as a closing app earlier. We must remove ourselves
                 // from this list so we do not participate in any future animations.
-                mService.mClosingApps.remove(this);
+                getDisplayContent().mClosingApps.remove(this);
             } else if (mLastParent != null && mLastParent.mStack != null) {
                 task.mStack.mExitingAppTokens.remove(this);
             }
@@ -1219,7 +1220,7 @@
         if (tStartingWindow != null && fromToken.startingSurface != null) {
             // In this case, the starting icon has already been displayed, so start
             // letting windows get shown immediately without any more transitions.
-            mService.mSkipAppTransitionAnimation = true;
+            getDisplayContent().mSkipAppTransitionAnimation = true;
 
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Moving existing starting " + tStartingWindow
                     + " from " + fromToken + " to " + this);
@@ -1269,7 +1270,7 @@
                 // When transferring an animation, we no longer need to apply an animation to the
                 // the token we transfer the animation over. Thus, remove the animation from
                 // pending opening apps.
-                mService.mOpeningApps.remove(this);
+                getDisplayContent().mOpeningApps.remove(this);
 
                 mService.updateFocusedWindowLocked(
                         UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
@@ -1323,8 +1324,8 @@
         // The {@link AppWindowToken} should only specify an orientation when it is not closing or
         // going to the bottom. Allowing closing {@link AppWindowToken} to participate can lead to
         // an Activity in another task being started in the wrong orientation during the transition.
-        if (!(sendingToBottom || mService.mClosingApps.contains(this))
-                && (isVisible() || mService.mOpeningApps.contains(this))) {
+        if (!(sendingToBottom || getDisplayContent().mClosingApps.contains(this))
+                && (isVisible() || getDisplayContent().mOpeningApps.contains(this))) {
             return mOrientation;
         }
 
@@ -1398,7 +1399,7 @@
             setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
 
             // We can now show all of the drawn windows!
-            if (!mService.mOpeningApps.contains(this) && canShowWindows()) {
+            if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
                 showAllWindowsLocked();
             }
         }
@@ -1572,6 +1573,11 @@
         return forAllWindowsUnchecked(callback, traverseTopToBottom);
     }
 
+    @Override
+    void forAllAppWindows(Consumer<AppWindowToken> callback) {
+        callback.accept(this);
+    }
+
     boolean forAllWindowsUnchecked(ToBooleanFunction<WindowState> callback,
             boolean traverseTopToBottom) {
         return super.forAllWindows(callback, traverseTopToBottom);
@@ -1629,7 +1635,8 @@
         final boolean containsShowWhenLocked = containsShowWhenLockedWindow();
         if (containsDismissKeyguard != mLastContainsDismissKeyguardWindow
                 || containsShowWhenLocked != mLastContainsShowWhenLockedWindow) {
-            mService.notifyKeyguardFlagsChanged(null /* callback */);
+            mService.notifyKeyguardFlagsChanged(null /* callback */,
+                    getDisplayContent().getDisplayId());
         }
         mLastContainsDismissKeyguardWindow = containsDismissKeyguard;
         mLastContainsShowWhenLockedWindow = containsShowWhenLocked;
@@ -1787,19 +1794,20 @@
             getAnimationBounds(mTmpPoint, mTmpRect);
 
             // Delaying animation start isn't compatible with remote animations at all.
-            if (mService.mAppTransition.getRemoteAnimationController() != null
+            if (getDisplayContent().mAppTransition.getRemoteAnimationController() != null
                     && !mSurfaceAnimator.isAnimationStartDelayed()) {
-                adapter = mService.mAppTransition.getRemoteAnimationController()
+                adapter = getDisplayContent().mAppTransition.getRemoteAnimationController()
                         .createAnimationAdapter(this, mTmpPoint, mTmpRect);
             } else {
-                final int appStackClipMode = mService.mAppTransition.getAppStackClipMode();
+                final int appStackClipMode =
+                        getDisplayContent().mAppTransition.getAppStackClipMode();
                 mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM);
 
                 final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction);
                 if (a != null) {
                     adapter = new LocalAnimationAdapter(
                             new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
-                                    mService.mAppTransition.canSkipFirstFrame(),
+                                    getDisplayContent().mAppTransition.canSkipFirstFrame(),
                                     appStackClipMode,
                                     true /* isAppAnimation */),
                             mService.mSurfaceAnimationRunner);
@@ -1807,7 +1815,7 @@
                         mNeedsZBoost = true;
                     }
                     mTransit = transit;
-                    mTransitFlags = mService.mAppTransition.getTransitFlags();
+                    mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
                 } else {
                     adapter = null;
                 }
@@ -1877,7 +1885,7 @@
                 + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter
                 + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
         final Configuration displayConfig = displayContent.getConfiguration();
-        final Animation a = mService.mAppTransition.loadAnimation(lp, transit, enter,
+        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) {
@@ -2017,7 +2025,7 @@
         final ArrayList<WindowState> children = new ArrayList<>(mChildren);
         children.forEach(WindowState::onExitAnimationDone);
 
-        mService.mAppTransition.notifyAppTransitionFinishedLocked(token);
+        getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
         scheduleAnimation();
     }
 
@@ -2048,8 +2056,9 @@
     }
 
     boolean isWaitingForTransitionStart() {
-        return mService.mAppTransition.isTransitionSet()
-                && (mService.mOpeningApps.contains(this) || mService.mClosingApps.contains(this));
+        return getDisplayContent().mAppTransition.isTransitionSet()
+                && (getDisplayContent().mOpeningApps.contains(this)
+                    || getDisplayContent().mClosingApps.contains(this));
     }
 
     public int getTransit() {
@@ -2066,7 +2075,7 @@
         }
         final int taskId = getTask().mTaskId;
         final GraphicBuffer thumbnailHeader =
-                mService.mAppTransition.getAppTransitionThumbnailHeader(taskId);
+                getDisplayContent().mAppTransition.getAppTransitionThumbnailHeader(taskId);
         if (thumbnailHeader == null) {
             if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "No thumbnail header bitmap for: " + taskId);
             return;
@@ -2095,14 +2104,14 @@
                 ? R.drawable.ic_account_circle
                 : R.drawable.ic_corp_badge;
         final GraphicBuffer thumbnail =
-                mService.mAppTransition
+                getDisplayContent().mAppTransition
                         .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
         if (thumbnail == null) {
             return;
         }
         mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnail);
         final Animation animation =
-                mService.mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
+                getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
                         win.getFrameLw());
         mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
                 frame.top));
@@ -2119,7 +2128,7 @@
                 new Rect(0, 0, displayInfo.appWidth, displayInfo.appHeight);
         final Rect insets = win != null ? win.getContentInsets() : null;
         final Configuration displayConfig = mDisplayContent.getConfiguration();
-        return mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(
+        return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
                 appRect, insets, thumbnailHeader, getTask().mTaskId, displayConfig.uiMode,
                 displayConfig.orientation);
     }
@@ -2357,4 +2366,11 @@
         return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
                 true /* topToBottom */);
     }
+
+    void removeFromPendingTransition() {
+        if (isWaitingForTransitionStart() && mDisplayContent != null) {
+            mDisplayContent.mOpeningApps.remove(this);
+            mDisplayContent.mClosingApps.remove(this);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fa9ae52..ba03034 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -65,6 +65,7 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
@@ -80,6 +81,7 @@
 import static com.android.server.wm.DisplayContentProto.SURFACE_SIZE;
 import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -119,6 +121,7 @@
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
 
+import android.animation.AnimationHandler;
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -155,6 +158,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
@@ -228,6 +232,21 @@
     private boolean mTmpInitial;
     private int mMaxUiWidth;
 
+    final AppTransition mAppTransition;
+    final AppTransitionController mAppTransitionController;
+    boolean mSkipAppTransitionAnimation = false;
+
+    final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
+    final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
+    final UnknownAppVisibilityController mUnknownAppVisibilityController;
+    BoundsAnimationController mBoundsAnimationController;
+
+    /**
+     * List of clients without a transtiton animation that we notify once we are done
+     * transitioning since they won't be notified through the app window animator.
+     */
+    final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
+
     // Mapping from a token IBinder to a WindowToken object on this display.
     private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
 
@@ -821,6 +840,15 @@
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
 
+        mAppTransition = new AppTransition(service.mContext, service, this);
+        mAppTransition.registerListenerLocked(service.mActivityManagerAppTransitionNotifier);
+        mAppTransitionController = new AppTransitionController(service, this);
+        mUnknownAppVisibilityController = new UnknownAppVisibilityController(service, this);
+
+        AnimationHandler animationHandler = new AnimationHandler();
+        mBoundsAnimationController = new BoundsAnimationController(service.mContext,
+                mAppTransition, SurfaceAnimationThread.getHandler(), animationHandler);
+
         // We use this as our arbitrary surface size for buffer-less parents
         // that don't impose cropping on their children. It may need to be larger
         // than the display size because fullscreen windows can be shifted offscreen
@@ -2135,6 +2163,9 @@
                     + " to its current displayId=" + mDisplayId);
         }
 
+        // Clean up all pending transitions when stack reparent to another display.
+        stack.forAllAppWindows(AppWindowToken::removeFromPendingTransition);
+
         prevDc.mTaskStackContainers.removeChild(stack);
         mTaskStackContainers.addStackToDisplay(stack, onTop);
     }
@@ -2294,6 +2325,13 @@
     void removeImmediately() {
         mRemovingDisplay = true;
         try {
+            // Clear all transitions & screen frozen states when removing display.
+            mOpeningApps.clear();
+            mClosingApps.clear();
+            mUnknownAppVisibilityController.clear();
+            mAppTransition.removeAppTransitionTimeoutCallbacks();
+            handleAnimatingStoppedAndTransition();
+            mService.stopFreezingDisplayLocked();
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             if (mPointerEventDispatcher != null && mTapDetector != null) {
@@ -2514,6 +2552,7 @@
             screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
         }
         mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES);
+        mAppTransition.writeToProto(proto, APP_TRANSITION);
         proto.write(SURFACE_SIZE, mSurfaceSize);
         if (mFocusedApp != null) {
             mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
@@ -2998,11 +3037,10 @@
                 }
 
                 if (highestTarget != null) {
-                    final AppTransition appTransition = mService.mAppTransition;
-                    if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
+                    if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, mAppTransition + " " + highestTarget
                             + " animating=" + highestTarget.isAnimating());
 
-                    if (appTransition.isTransitionSet()) {
+                    if (mAppTransition.isTransitionSet()) {
                         // If we are currently setting up for an animation, hold everything until we
                         // can find out what will happen.
                         setInputMethodTarget(highestTarget, true);
@@ -3093,6 +3131,18 @@
                 pw.println();
             }
         }
+
+        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
+            pw.println();
+            if (mOpeningApps.size() > 0) {
+                pw.print("  mOpeningApps="); pw.println(mOpeningApps);
+            }
+            if (mClosingApps.size() > 0) {
+                pw.print("  mClosingApps="); pw.println(mClosingApps);
+            }
+        }
+
+        mUnknownAppVisibilityController.dump(pw, "  ");
     }
 
     void dumpWindowAnimators(PrintWriter pw, String subPrefix) {
@@ -3989,7 +4039,7 @@
                 final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
                 for (int j = appTokens.size() - 1; j >= 0; --j) {
                     final AppWindowToken token = appTokens.get(j);
-                    if (!token.hasVisible && !mService.mClosingApps.contains(token)
+                    if (!token.hasVisible && !mClosingApps.contains(token)
                             && (!token.mIsExiting || token.isEmpty())) {
                         // Make sure there is no animation running on this token, so any windows
                         // associated with it will be removed as soon as their animations are
@@ -4287,8 +4337,8 @@
             // Only allow force setting the orientation when all unknown visibilities have been
             // resolved, as otherwise we just may be starting another occluding activity.
             final boolean isUnoccluding =
-                    mService.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
-                            && mService.mUnknownAppVisibilityController.allResolved();
+                    mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE
+                            && mUnknownAppVisibilityController.allResolved();
             if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
                 return mLastKeyguardForcedOrientation;
             }
@@ -4528,4 +4578,56 @@
             mPointerEventDispatcher.unregisterInputEventListener(listener);
         }
     }
+
+    void prepareAppTransition(@WindowManager.TransitionType int transit,
+            boolean alwaysKeepCurrent, @WindowManager.TransitionFlags int flags,
+            boolean forceOverride) {
+        final boolean prepared = mAppTransition.prepareAppTransitionLocked(
+                transit, alwaysKeepCurrent, flags, forceOverride);
+        if (prepared && okToAnimate()) {
+            mSkipAppTransitionAnimation = false;
+        }
+    }
+
+    void executeAppTransition() {
+        if (mAppTransition.isTransitionSet()) {
+            if (DEBUG_APP_TRANSITIONS) {
+                Slog.w(TAG_WM, "Execute app transition: " + mAppTransition + ", displayId: "
+                        + mDisplayId + " Callers=" + Debug.getCallers(5));
+            }
+            mAppTransition.setReady();
+            mService.mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
+    /**
+     * Update pendingLayoutChanges after app transition has finished.
+     */
+    void handleAnimatingStoppedAndTransition() {
+        int changes = 0;
+
+        mAppTransition.setIdle();
+
+        for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
+            final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
+            mAppTransition.notifyAppTransitionFinishedLocked(token);
+        }
+        mNoAnimationNotifyOnTransitionFinished.clear();
+
+        mWallpaperController.hideDeferredWallpapersIfNeeded();
+
+        onAppTransitionDone();
+
+        changes |= FINISH_LAYOUT_REDO_LAYOUT;
+        if (DEBUG_WALLPAPER_LIGHT) {
+            Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
+        }
+        computeImeTarget(true /* updateImeTarget */);
+        mService.mRoot.mWallpaperMayChange = true;
+        // Since the window list has been rebuilt, focus might have to be recomputed since the
+        // actual order of windows might have changed again.
+        mService.mFocusMayChange = true;
+
+        pendingLayoutChanges |= changes;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index 3282b1c..01d556a 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -16,6 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
@@ -23,10 +27,17 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
 import android.content.res.Configuration;
+import android.graphics.GraphicBuffer;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
 import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionType;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Controller for the display container. This is created by activity manager to link activity
@@ -56,6 +67,12 @@
         }
     }
 
+    @VisibleForTesting
+    public DisplayWindowController(Display display, WindowManagerService service) {
+        super(null, service);
+        mDisplayId = display.getDisplayId();
+    }
+
     @Override
     public void removeContainer() {
         synchronized (mWindowMap) {
@@ -179,6 +196,124 @@
         }
     }
 
+    public void prepareAppTransition(@WindowManager.TransitionType int transit,
+            boolean alwaysKeepCurrent) {
+        prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */);
+    }
+
+    /**
+     * @param transit What kind of transition is happening. Use one of the constants
+     *                AppTransition.TRANSIT_*.
+     * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT
+     *                          be set.
+     * @param flags Additional flags for the app transition, Use a combination of the constants
+     *              AppTransition.TRANSIT_FLAG_*.
+     * @param forceOverride Always override the transit, not matter what was set previously.
+     */
+    public void prepareAppTransition(@WindowManager.TransitionType int transit,
+            boolean alwaysKeepCurrent, @WindowManager.TransitionFlags int flags,
+            boolean forceOverride) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId).prepareAppTransition(transit, alwaysKeepCurrent,
+                    flags, forceOverride);
+        }
+    }
+
+    public void executeAppTransition() {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId).executeAppTransition();
+        }
+    }
+
+    public void overridePendingAppTransition(String packageName,
+            int enterAnim, int exitAnim, IRemoteCallback startedCallback) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId).mAppTransition.overridePendingAppTransition(
+                    packageName, enterAnim, exitAnim, startedCallback);
+        }
+    }
+
+    public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+            int startHeight) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId).mAppTransition.overridePendingAppTransitionScaleUp(
+                    startX, startY, startWidth, startHeight);
+        }
+    }
+
+    public void overridePendingAppTransitionClipReveal(int startX, int startY,
+            int startWidth, int startHeight) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overridePendingAppTransitionClipReveal(startX, startY,
+                    startWidth, startHeight);
+        }
+    }
+
+    public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX,
+            int startY, IRemoteCallback startedCallback, boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY,
+                    startedCallback, scaleUp);
+        }
+    }
+
+    public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
+            int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
+            boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX,
+                    startY, targetWidth, targetHeight, startedCallback, scaleUp);
+        }
+    }
+
+    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
+            IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
+            boolean scaleUp) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overridePendingAppTransitionMultiThumb(specs,
+                    onAnimationStartedCallback, onAnimationFinishedCallback, scaleUp);
+        }
+    }
+
+    public void overridePendingAppTransitionStartCrossProfileApps() {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overridePendingAppTransitionStartCrossProfileApps();
+        }
+    }
+
+    public void overridePendingAppTransitionInPlace(String packageName, int anim) {
+        synchronized (mWindowMap) {
+            mRoot.getDisplayContent(mDisplayId)
+                    .mAppTransition.overrideInPlaceAppTransition(packageName, anim);
+        }
+    }
+
+    /**
+     * Get Pending App transition of display.
+     *
+     * @return The pending app transition of the display.
+     */
+    public @TransitionType int getPendingAppTransition() {
+        synchronized (mWindowMap) {
+            return mRoot.getDisplayContent(mDisplayId).mAppTransition.getAppTransition();
+        }
+    }
+
+    /**
+     * Check if pending app transition is for activity / task launch.
+     */
+    public boolean isNextTransitionForward() {
+        final int transit = getPendingAppTransition();
+        return transit == TRANSIT_ACTIVITY_OPEN
+                || transit == TRANSIT_TASK_OPEN
+                || transit == TRANSIT_TASK_TO_FRONT;
+    }
+
     @Override
     public String toString() {
         return "{DisplayWindowController displayId=" + mDisplayId + "}";
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ac93848..8ed29a9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -531,7 +531,7 @@
             final TaskStack stack =
                     mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
             final long transitionDuration = isAnimationMaximizing()
-                    ? mService.mAppTransition.getLastClipRevealTransitionDuration()
+                    ? mDisplayContent.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
             mAnimationDuration = (long)
                     (transitionDuration * mService.getTransitionAnimationScaleLocked());
@@ -950,7 +950,7 @@
             return naturalAmount;
         }
         final int minimizeDistance = stack.getMinimizeDistance();
-        float startPrime = mService.mAppTransition.getLastClipRevealMaxTranslation()
+        final float startPrime = mDisplayContent.mAppTransition.getLastClipRevealMaxTranslation()
                 / (float) minimizeDistance;
         final float amountPrime = t * mAnimationTarget + (1 - t) * startPrime;
         final float t2 = Math.min(t / mMaximizeMeetFraction, 1);
@@ -963,12 +963,12 @@
      */
     private float getClipRevealMeetFraction(TaskStack stack) {
         if (!isAnimationMaximizing() || stack == null ||
-                !mService.mAppTransition.hadClipRevealAnimation()) {
+                !mDisplayContent.mAppTransition.hadClipRevealAnimation()) {
             return 1f;
         }
         final int minimizeDistance = stack.getMinimizeDistance();
-        final float fraction = Math.abs(mService.mAppTransition.getLastClipRevealMaxTranslation())
-                / (float) minimizeDistance;
+        final float fraction = Math.abs(mDisplayContent.mAppTransition
+                .getLastClipRevealMaxTranslation()) / (float) minimizeDistance;
         final float t = Math.max(0, Math.min(1, (fraction - CLIP_REVEAL_MEET_FRACTION_MIN)
                 / (CLIP_REVEAL_MEET_FRACTION_MAX - CLIP_REVEAL_MEET_FRACTION_MIN)));
         return CLIP_REVEAL_MEET_EARLIEST
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 02fbfba..1807eeb 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -117,11 +117,12 @@
             final Rect finalToBounds = toBounds;
             final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
                 schedulePipModeChangedState;
-            mService.mBoundsAnimationController.getHandler().post(() -> {
+            final DisplayContent displayContent = mContainer.getDisplayContent();
+            displayContent.mBoundsAnimationController.getHandler().post(() -> {
                 if (mContainer == null) {
                     return;
                 }
-                mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
+                displayContent.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
                         finalToBounds, animationDuration, finalSchedulePipModeChangedState,
                         fromFullscreen, toFullscreen);
             });
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 5c80759..c4fbee9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -467,7 +467,8 @@
         // so if we are actually transitioning there, notify again here
         if (mTargetAppToken != null) {
             if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
-                mService.mAppTransition.notifyAppTransitionFinishedLocked(mTargetAppToken.token);
+                mService.mRoot.getDisplayContent(mDisplayId)
+                        .mAppTransition.notifyAppTransitionFinishedLocked(mTargetAppToken.token);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 8ec0a01..0ec4baf 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -317,7 +317,7 @@
         }
 
         private int getMode() {
-            if (mService.mOpeningApps.contains(mAppWindowToken)) {
+            if (mAppWindowToken.getDisplayContent().mOpeningApps.contains(mAppWindowToken)) {
                 return RemoteAnimationTarget.MODE_OPENING;
             } else {
                 return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c8977be..62078f7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -576,17 +576,15 @@
         mSustainedPerformanceModeCurrent = false;
         mService.mTransactionSequence++;
 
-        // TODO(multi-display):
+        // TODO(multi-display): recents animation & wallpaper need support multi-display.
         final DisplayContent defaultDisplay = mService.getDefaultDisplayContentLocked();
-        final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
-        final int defaultDw = defaultInfo.logicalWidth;
-        final int defaultDh = defaultInfo.logicalHeight;
+        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
 
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
         mService.openSurfaceTransaction();
         try {
-            applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);
+            applySurfaceChangesTransaction(recoveringMemory);
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
@@ -594,39 +592,13 @@
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
-
         mService.mAnimator.executeAfterPrepareSurfacesRunnables();
 
-        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
-
-        // If we are ready to perform an app transition, check through all of the app tokens to be
-        // shown and see if they are ready to go.
-        if (mService.mAppTransition.isReady()) {
-            // This needs to be split into two expressions, as handleAppTransitionReadyLocked may
-            // modify dc.pendingLayoutChanges, which would get lost when writing
-            // defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked()
-            final int layoutChanges = surfacePlacer.handleAppTransitionReadyLocked();
-            defaultDisplay.pendingLayoutChanges |= layoutChanges;
-            if (DEBUG_LAYOUT_REPEATS)
-                surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
-                        defaultDisplay.pendingLayoutChanges);
-        }
-
-        if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
-            // We have finished the animation of an app transition. To do this, we have delayed a
-            // lot of operations like showing and hiding apps, moving apps in Z-order, etc. The app
-            // token list reflects the correct Z-order, but the window list may now be out of sync
-            // with it. So here we will just rebuild the entire app window list. Fun!
-            defaultDisplay.pendingLayoutChanges |=
-                    mService.handleAnimatingStoppedAndTransitionLocked();
-            if (DEBUG_LAYOUT_REPEATS)
-                surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
-                        defaultDisplay.pendingLayoutChanges);
-        }
+        checkAppTransitionReady(surfacePlacer);
 
         // Defer starting the recents animation until the wallpaper has drawn
         final RecentsAnimationController recentsAnimationController =
-            mService.getRecentsAnimationController();
+                mService.getRecentsAnimationController();
         if (recentsAnimationController != null) {
             recentsAnimationController.checkAnimationReady(mWallpaperController);
         }
@@ -732,8 +704,8 @@
             mUpdateRotation = updateRotationUnchecked();
         }
 
-        if (mService.mWaitingForDrawnCallback != null ||
-                (mOrientationChangeComplete && !defaultDisplay.isLayoutNeeded()
+        if (mService.mWaitingForDrawnCallback != null
+                || (mOrientationChangeComplete && !isLayoutNeeded()
                         && !mUpdateRotation)) {
             mService.checkDrawnWindowsLocked();
         }
@@ -741,7 +713,7 @@
         final int N = mService.mPendingRemove.size();
         if (N > 0) {
             if (mService.mPendingRemoveTmp.length < N) {
-                mService.mPendingRemoveTmp = new WindowState[N+10];
+                mService.mPendingRemoveTmp = new WindowState[N + 10];
             }
             mService.mPendingRemove.toArray(mService.mPendingRemoveTmp);
             mService.mPendingRemove.clear();
@@ -783,12 +755,47 @@
                 "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
     }
 
-    private void applySurfaceChangesTransaction(boolean recoveringMemory, int defaultDw,
-            int defaultDh) {
+    private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
+        // Trace all displays app transition by Z-order for pending layout change.
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final DisplayContent curDisplay = mChildren.get(i);
+
+            // If we are ready to perform an app transition, check through all of the app tokens
+            // to be shown and see if they are ready to go.
+            if (curDisplay.mAppTransition.isReady()) {
+                // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
+                curDisplay.mAppTransitionController.handleAppTransitionReady();
+                if (DEBUG_LAYOUT_REPEATS) {
+                    surfacePlacer.debugLayoutRepeats("after handleAppTransitionReady",
+                            curDisplay.pendingLayoutChanges);
+                }
+            }
+
+            if (!curDisplay.isAppAnimating() && curDisplay.mAppTransition.isRunning()) {
+                // We have finished the animation of an app transition. To do this, we have
+                // delayed a lot of operations like showing and hiding apps, moving apps in
+                // Z-order, etc.
+                // The app token list reflects the correct Z-order, but the window list may now
+                // be out of sync with it. So here we will just rebuild the entire app window
+                // list. Fun!
+                curDisplay.handleAnimatingStoppedAndTransition();
+                if (DEBUG_LAYOUT_REPEATS) {
+                    surfacePlacer.debugLayoutRepeats("after handleAnimStopAndXitionLock",
+                            curDisplay.pendingLayoutChanges);
+                }
+            }
+        }
+    }
+
+    private void applySurfaceChangesTransaction(boolean recoveringMemory) {
         mHoldScreenWindow = null;
         mObscuringWindow = null;
 
         // TODO(multi-display): Support these features on secondary screens.
+        final DisplayContent defaultDc = mService.getDefaultDisplayContentLocked();
+        final DisplayInfo defaultInfo = defaultDc.getDisplayInfo();
+        final int defaultDw = defaultInfo.logicalWidth;
+        final int defaultDh = defaultInfo.logicalHeight;
         if (mService.mWatermark != null) {
             mService.mWatermark.positionSurface(defaultDw, defaultDh);
         }
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index 1fd2c0f..6ac63a1 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -140,10 +140,11 @@
             }
             mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
 
-            if (mService.mAppTransition.isTransitionSet()) {
+            final DisplayContent displayContent = mContainer.getDisplayContent();
+            if (displayContent.mAppTransition.isTransitionSet()) {
                 childTask.setSendingToBottom(false);
             }
-            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+            displayContent.layoutAndAssignWindowLayersIfNeeded();
         }
     }
 
@@ -162,7 +163,7 @@
             }
             mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
 
-            if (mService.mAppTransition.isTransitionSet()) {
+            if (mContainer.getDisplayContent().mAppTransition.isTransitionSet()) {
                 childTask.setSendingToBottom(true);
             }
             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index ef63b9b..0d5469b 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -129,8 +129,8 @@
         mPersister.start();
     }
 
-    void onTransitionStarting() {
-        handleClosingApps(mService.mClosingApps);
+    void onTransitionStarting(DisplayContent displayContent) {
+        handleClosingApps(displayContent.mClosingApps);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 00caceb..81507fa 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1638,7 +1638,7 @@
             return;
         }
 
-        mService.mBoundsAnimationController.onAllWindowsDrawn();
+        getDisplayContent().mBoundsAnimationController.onAllWindowsDrawn();
     }
 
     @Override  // AnimatesBounds
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index eb751fa..01abcab 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -60,8 +60,11 @@
 
     private final WindowManagerService mService;
 
-    UnknownAppVisibilityController(WindowManagerService service) {
+    private final DisplayContent mDisplayContent;
+
+    UnknownAppVisibilityController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
+        mDisplayContent = displayContent;
     }
 
     boolean allResolved() {
@@ -128,7 +131,8 @@
         int state = mUnknownApps.get(appWindow);
         if (state == UNKNOWN_STATE_WAITING_RELAYOUT) {
             mUnknownApps.put(appWindow, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
-            mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated);
+            mService.notifyKeyguardFlagsChanged(this::notifyVisibilitiesUpdated,
+                    appWindow.getDisplayContent().getDisplayId());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a448f97..942cdb9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,16 +16,14 @@
 
 package com.android.server.wm;
 
-import com.android.internal.util.ToBooleanFunction;
-
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
@@ -50,6 +48,8 @@
 import android.view.WindowManager;
 import android.view.animation.Animation;
 
+import com.android.internal.util.ToBooleanFunction;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -263,7 +263,8 @@
                 && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) {
             return;
         }
-        if (mService.mAppTransition.isRunning()) {
+        if (mWallpaperTarget != null
+                && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) {
             // Defer hiding the wallpaper when app transition is running until the animations
             // are done.
             mDeferredHideWallpaper = winGoingAway;
@@ -549,9 +550,9 @@
             // is not. If they're both hidden, still use the new target.
             mWallpaperTarget = prevWallpaperTarget;
         } else if (newTargetHidden == oldTargetHidden
-                && !mService.mOpeningApps.contains(wallpaperTarget.mAppToken)
-                && (mService.mOpeningApps.contains(prevWallpaperTarget.mAppToken)
-                || mService.mClosingApps.contains(prevWallpaperTarget.mAppToken))) {
+                && !dc.mOpeningApps.contains(wallpaperTarget.mAppToken)
+                && (dc.mOpeningApps.contains(prevWallpaperTarget.mAppToken)
+                || dc.mClosingApps.contains(prevWallpaperTarget.mAppToken))) {
             // If they're both hidden (or both not hidden), prefer the one that's currently in
             // opening or closing app list, this allows transition selection logic to better
             // determine the wallpaper status of opening/closing apps.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 46999a2..abc3826 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -826,6 +826,12 @@
         wrapper.release();
     }
 
+    void forAllAppWindows(Consumer<AppWindowToken> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).forAllAppWindows(callback);
+        }
+    }
+
     /**
      * For all tasks at or below this container call the callback.
      *
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3aad73c..96fc2e2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -67,12 +67,10 @@
 import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
@@ -86,7 +84,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -96,7 +93,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON;
 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.WindowManagerServiceDumpProto.APP_TRANSITION;
 import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
 import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -108,7 +104,6 @@
 
 import android.Manifest;
 import android.Manifest.permission;
-import android.animation.AnimationHandler;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -134,7 +129,6 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -188,7 +182,6 @@
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -221,7 +214,6 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.TransitionFlags;
 import android.view.WindowManager.TransitionType;
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -266,7 +258,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
-import java.util.List;
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
@@ -612,14 +603,6 @@
     // changes the orientation.
     private final PowerManager.WakeLock mScreenFrozenLock;
 
-    final AppTransition mAppTransition;
-    boolean mSkipAppTransitionAnimation = false;
-
-    final ArraySet<AppWindowToken> mOpeningApps = new ArraySet<>();
-    final ArraySet<AppWindowToken> mClosingApps = new ArraySet<>();
-
-    final UnknownAppVisibilityController mUnknownAppVisibilityController =
-            new UnknownAppVisibilityController(this);
     final TaskSnapshotController mTaskSnapshotController;
 
     boolean mIsTouchDevice;
@@ -751,7 +734,6 @@
      * up when the animation finishes.
      */
     final ArrayMap<AnimationAdapter, SurfaceAnimator> mAnimationTransferMap = new ArrayMap<>();
-    final BoundsAnimationController mBoundsAnimationController;
 
     private WindowContentFrameStats mTempWindowRenderStats;
 
@@ -779,10 +761,6 @@
     // For example, when this flag is true, there will be no wallpaper service.
     final boolean mOnlyCore;
 
-    // List of clients without a transtiton animation that we notify once we are done transitioning
-    // since they won't be notified through the app window animator.
-    final List<IBinder> mNoAnimationNotifyOnTransitionFinished = new ArrayList<>();
-
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
@@ -975,13 +953,6 @@
                 PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
         mScreenFrozenLock.setReferenceCounted(false);
 
-        mAppTransition = new AppTransition(context, this);
-        mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
-
-        final AnimationHandler animationHandler = new AnimationHandler();
-        mBoundsAnimationController = new BoundsAnimationController(context, mAppTransition,
-                AnimationThread.getHandler(), animationHandler);
-
         mActivityManager = ActivityManager.getService();
         mActivityTaskManager = ActivityTaskManager.getService();
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -1577,11 +1548,13 @@
         Rect frame = replacedWindow.getVisibleFrameLw();
         // We treat this as if this activity was opening, so we can trigger the app transition
         // animation and piggy-back on existing transition animation infrastructure.
-        mOpeningApps.add(atoken);
-        prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT);
-        mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
+        final DisplayContent dc = atoken.getDisplayContent();
+        dc.mOpeningApps.add(atoken);
+        dc.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT,
+                0 /* flags */, false /* forceOverride */);
+        dc.mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top,
                 frame.width(), frame.height());
-        executeAppTransition();
+        dc.executeAppTransition();
         return true;
     }
 
@@ -1590,10 +1563,12 @@
         // unfreeze wait for the apps to be drawn.
         // Note that if the display unfroze already because app unfreeze timed out,
         // we don't set up the transition anymore and just let it go.
-        if (mDisplayFrozen && !mOpeningApps.contains(atoken) && atoken.isRelaunching()) {
-            mOpeningApps.add(atoken);
-            prepareAppTransition(WindowManager.TRANSIT_NONE, !ALWAYS_KEEP_CURRENT);
-            executeAppTransition();
+        final DisplayContent dc = atoken.getDisplayContent();
+        if (mDisplayFrozen && !dc.mOpeningApps.contains(atoken) && atoken.isRelaunching()) {
+            dc.mOpeningApps.add(atoken);
+            dc.prepareAppTransition(WindowManager.TRANSIT_NONE, !ALWAYS_KEEP_CURRENT, 0 /* flags */,
+                    false /* forceOverride */);
+            dc.executeAppTransition();
         }
     }
 
@@ -2087,7 +2062,7 @@
             }
 
             if (win.mAppToken != null) {
-                mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
+                dc.mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
             }
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
@@ -2445,6 +2420,9 @@
         long ident = Binder.clearCallingIdentity();
         try {
             final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                return false;
+            }
             final int req = dc.getOrientation();
             if (req != dc.getLastOrientation() || forceUpdate) {
                 dc.setLastOrientation(req);
@@ -2475,109 +2453,15 @@
         }
     }
 
+    // TODO(multi-display): remove when no default display use case.
+    // (i.e. KeyguardController / RecentsAnimation)
     @Override
     public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent) {
-        prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */);
-    }
-
-    /**
-     * @param transit What kind of transition is happening. Use one of the constants
-     *                AppTransition.TRANSIT_*.
-     * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT
-     *                          be set.
-     * @param flags Additional flags for the app transition, Use a combination of the constants
-     *              AppTransition.TRANSIT_FLAG_*.
-     * @param forceOverride Always override the transit, not matter what was set previously.
-     */
-    public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent,
-            @TransitionFlags int flags, boolean forceOverride) {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
-        synchronized(mWindowMap) {
-            boolean prepared = mAppTransition.prepareAppTransitionLocked(transit, alwaysKeepCurrent,
-                    flags, forceOverride);
-            // TODO (multidisplay): associate app transitions with displays
-            final DisplayContent dc = mRoot.getDisplayContent(DEFAULT_DISPLAY);
-            if (prepared && dc != null && dc.okToAnimate()) {
-                mSkipAppTransitionAnimation = false;
-            }
-        }
-    }
-
-    @Override
-    public @TransitionType int getPendingAppTransition() {
-        return mAppTransition.getAppTransition();
-    }
-
-    @Override
-    public void overridePendingAppTransition(String packageName,
-            int enterAnim, int exitAnim, IRemoteCallback startedCallback) {
-        synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransition(packageName, enterAnim, exitAnim,
-                    startedCallback);
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
-            int startHeight) {
-        synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionScaleUp(startX, startY, startWidth,
-                    startHeight);
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionClipReveal(int startX, int startY,
-            int startWidth, int startHeight) {
-        synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionClipReveal(startX, startY, startWidth,
-                    startHeight);
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX,
-            int startY, IRemoteCallback startedCallback, boolean scaleUp) {
-        synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY,
-                    startedCallback, scaleUp);
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
-            int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
-            boolean scaleUp) {
-        synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionAspectScaledThumb(srcThumb, startX, startY,
-                    targetWidth, targetHeight, startedCallback, scaleUp);
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
-            IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
-            boolean scaleUp) {
-        synchronized (mWindowMap) {
-            mAppTransition.overridePendingAppTransitionMultiThumb(specs, onAnimationStartedCallback,
-                    onAnimationFinishedCallback, scaleUp);
-
-        }
-    }
-
-    public void overridePendingAppTransitionStartCrossProfileApps() {
-        synchronized (mWindowMap) {
-            mAppTransition.overridePendingAppTransitionStartCrossProfileApps();
-        }
-    }
-
-    @Override
-    public void overridePendingAppTransitionInPlace(String packageName, int anim) {
-        synchronized(mWindowMap) {
-            mAppTransition.overrideInPlaceAppTransition(packageName, anim);
-        }
+        getDefaultDisplayContentLocked().prepareAppTransition(transit,
+                alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */);
     }
 
     @Override
@@ -2585,8 +2469,10 @@
             IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
             boolean scaleUp) {
         synchronized(mWindowMap) {
-            mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture, callback,
-                    scaleUp);
+            // TODO(multi-display): sysui using this api only support default display.
+            mRoot.getDisplayContent(DEFAULT_DISPLAY)
+                    .mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture,
+                    callback, scaleUp);
         }
     }
 
@@ -2598,7 +2484,9 @@
                     "Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission");
         }
         synchronized (mWindowMap) {
-            mAppTransition.overridePendingAppTransitionRemote(remoteAnimationAdapter);
+            // TODO(multi-display): sysui using this api only support default display.
+            mRoot.getDisplayContent(DEFAULT_DISPLAY)
+                    .mAppTransition.overridePendingAppTransitionRemote(remoteAnimationAdapter);
         }
     }
 
@@ -2607,20 +2495,14 @@
         // TODO: Remove once clients are updated.
     }
 
+    // TODO(multi-display): remove when no default display use case.
+    // (i.e. KeyguardController / RecentsAnimation)
     @Override
     public void executeAppTransition() {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "executeAppTransition()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
-
-        synchronized(mWindowMap) {
-            if (DEBUG_APP_TRANSITIONS) Slog.w(TAG_WM, "Execute app transition: " + mAppTransition
-                    + " Callers=" + Debug.getCallers(5));
-            if (mAppTransition.isTransitionSet()) {
-                mAppTransition.setReady();
-                mWindowPlacerLocked.requestTraversal();
-            }
-        }
+        getDefaultDisplayContentLocked().executeAppTransition();
     }
 
     public void initializeRecentsAnimation(int targetActivityType,
@@ -2630,7 +2512,7 @@
         synchronized (mWindowMap) {
             mRecentsAnimationController = new RecentsAnimationController(this,
                     recentsAnimationRunner, callbacks, displayId);
-            mAppTransition.updateBooster();
+            mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
             mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
         }
     }
@@ -2650,7 +2532,8 @@
      */
     public boolean canStartRecentsAnimation() {
         synchronized (mWindowMap) {
-            if (mAppTransition.isTransitionSet()) {
+            // TODO(multi-display): currently only default display support recent activity
+            if (getDefaultDisplayContentLocked().mAppTransition.isTransitionSet()) {
                 return false;
             }
             return true;
@@ -2677,7 +2560,8 @@
                 final RecentsAnimationController controller = mRecentsAnimationController;
                 mRecentsAnimationController = null;
                 controller.cleanupAnimation(reorderMode);
-                mAppTransition.updateBooster();
+                // TODO(mult-display): currently only default display support recents animation.
+                getDefaultDisplayContentLocked().mAppTransition.updateBooster();
             }
         }
     }
@@ -2747,7 +2631,8 @@
 
     @Override
     public void notifyShowingDreamChanged() {
-        notifyKeyguardFlagsChanged(null /* callback */);
+        // TODO(multi-display): support show dream in multi-display.
+        notifyKeyguardFlagsChanged(null /* callback */, DEFAULT_DISPLAY);
     }
 
     @Override
@@ -2822,11 +2707,12 @@
      * reevaluate the visibilities of the activities.
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
      */
-    void notifyKeyguardFlagsChanged(@Nullable Runnable callback) {
+    void notifyKeyguardFlagsChanged(@Nullable Runnable callback, int displayId) {
         final Runnable wrappedCallback = callback != null
                 ? () -> { synchronized (mWindowMap) { callback.run(); } }
                 : null;
-        mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, wrappedCallback).sendToTarget();
+        mH.obtainMessage(H.NOTIFY_KEYGUARD_FLAGS_CHANGED, displayId, 0,
+                wrappedCallback).sendToTarget();
     }
 
     public boolean isKeyguardTrusted() {
@@ -3215,7 +3101,6 @@
         synchronized (mWindowMap) {
             mCurrentUserId = newUserId;
             mCurrentProfileIds = currentProfileIds;
-            mAppTransition.setCurrentUser(newUserId);
             mPolicy.setCurrentUserLw(newUserId);
 
             // If keyguard was disabled, re-enable it
@@ -3234,6 +3119,8 @@
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
+            mRoot.forAllDisplays(dc -> dc.mAppTransition.setCurrentUser(newUserId));
+
             // If the display is already prepared, update the density.
             // Otherwise, we'll update it when it's prepared.
             if (mDisplayReady) {
@@ -4895,7 +4782,7 @@
                 }
                 break;
                 case NOTIFY_KEYGUARD_FLAGS_CHANGED: {
-                    mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj);
+                    mAtmInternal.notifyKeyguardFlagsChanged((Runnable) msg.obj, msg.arg1);
                 }
                 break;
                 case NOTIFY_KEYGUARD_TRUSTED_CHANGED: {
@@ -5314,39 +5201,6 @@
         }
     }
 
-    /**
-     * @return bitmap indicating if another pass through layout must be made.
-     */
-    int handleAnimatingStoppedAndTransitionLocked() {
-        int changes = 0;
-
-        mAppTransition.setIdle();
-
-        for (int i = mNoAnimationNotifyOnTransitionFinished.size() - 1; i >= 0; i--) {
-            final IBinder token = mNoAnimationNotifyOnTransitionFinished.get(i);
-            mAppTransition.notifyAppTransitionFinishedLocked(token);
-        }
-        mNoAnimationNotifyOnTransitionFinished.clear();
-
-        // TODO: multi-display.
-        final DisplayContent dc = getDefaultDisplayContentLocked();
-
-        dc.mWallpaperController.hideDeferredWallpapersIfNeeded();
-
-        dc.onAppTransitionDone();
-
-        changes |= FINISH_LAYOUT_REDO_LAYOUT;
-        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG_WM,
-                "Wallpaper layer changed: assigning layers + relayout");
-        dc.computeImeTarget(true /* updateImeTarget */);
-        mRoot.mWallpaperMayChange = true;
-        // Since the window list has been rebuilt, focus might have to be recomputed since the
-        // actual order of windows might have changed again.
-        mFocusMayChange = true;
-
-        return changes;
-    }
-
     void checkDrawnWindowsLocked() {
         if (mWaitingForDrawn.isEmpty() || mWaitingForDrawnCallback == null) {
             return;
@@ -5459,8 +5313,8 @@
 
         mInputManagerCallback.freezeInputDispatchingLw();
 
-        if (mAppTransition.isTransitionSet()) {
-            mAppTransition.freeze();
+        if (displayContent.mAppTransition.isTransitionSet()) {
+            displayContent.mAppTransition.freeze();
         }
 
         if (PROFILE_ORIENTATION) {
@@ -5495,15 +5349,16 @@
             return;
         }
 
+        final DisplayContent dc = mRoot.getDisplayContent(mFrozenDisplayId);
         if (mWaitingForConfig || mAppsFreezingScreen > 0
                 || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
-                || mClientFreezingScreen || !mOpeningApps.isEmpty()) {
+                || mClientFreezingScreen || (dc != null && !dc.mOpeningApps.isEmpty())) {
             if (DEBUG_ORIENTATION) Slog.d(TAG_WM,
                 "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
                 + ", mAppsFreezingScreen=" + mAppsFreezingScreen
                 + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen
                 + ", mClientFreezingScreen=" + mClientFreezingScreen
-                + ", mOpeningApps.size()=" + mOpeningApps.size());
+                + ", mOpeningApps.size()=" + (dc != null ? dc.mOpeningApps.size() : 0));
             return;
         }
 
@@ -5583,7 +5438,7 @@
 
         mScreenFrozenLock.release();
 
-        if (updateRotation) {
+        if (updateRotation && displayContent != null && updateRotation) {
             if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
             configChanged |= displayContent.updateRotationUnchecked();
         }
@@ -5909,7 +5764,8 @@
         synchronized (mWindowMap) {
             final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
             if (appWindow != null) {
-                mUnknownAppVisibilityController.notifyAppResumedFinished(appWindow);
+                appWindow.getDisplayContent().mUnknownAppVisibilityController
+                        .notifyAppResumedFinished(appWindow);
             }
         }
     }
@@ -5955,15 +5811,6 @@
     private void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
         pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
         mRoot.dumpTokens(pw, dumpAll);
-        if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) {
-            pw.println();
-            if (mOpeningApps.size() > 0) {
-                pw.print("  mOpeningApps="); pw.println(mOpeningApps);
-            }
-            if (mClosingApps.size() > 0) {
-                pw.print("  mClosingApps="); pw.println(mClosingApps);
-            }
-        }
     }
 
     private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
@@ -6000,7 +5847,6 @@
         final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
         proto.write(ROTATION, defaultDisplayContent.getRotation());
         proto.write(LAST_ORIENTATION, defaultDisplayContent.getLastOrientation());
-        mAppTransition.writeToProto(proto, APP_TRANSITION);
     }
 
     void traceStateLocked(String where) {
@@ -6133,7 +5979,6 @@
                 pw.println();
 
         mInputManagerCallback.dump(pw, "  ");
-        mUnknownAppVisibilityController.dump(pw, "  ");
         mTaskSnapshotController.dump(pw, "  ");
 
         if (dumpAll) {
@@ -6172,9 +6017,6 @@
                     pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
                     pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);
                     pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting);
-            pw.print("  mSkipAppTransitionAnimation=");pw.println(mSkipAppTransitionAnimation);
-            pw.println("  mLayoutToAnim:");
-            mAppTransition.dump(pw, "    ");
             if (mRecentsAnimationController != null) {
                 pw.print("  mRecentsAnimationController="); pw.println(mRecentsAnimationController);
                 mRecentsAnimationController.dump(pw, "    ");
@@ -7024,10 +6866,12 @@
             }
         }
 
+        // TODO(multi-display): currently only used by PWM to notify keyguard transitions as well
+        // forwarding it to SystemUI for synchronizing status and navigation bar animations.
         @Override
         public void registerAppTransitionListener(AppTransitionListener listener) {
             synchronized (mWindowMap) {
-                mAppTransition.registerListenerLocked(listener);
+                getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2f89d5c..9dc7721 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1364,7 +1364,7 @@
     @Override
     boolean hasContentToDisplay() {
         if (!mAppFreezing && isDrawnLw() && (mViewVisibility == View.VISIBLE
-                || (isAnimating() && !mService.mAppTransition.isTransitionSet()))) {
+                || (isAnimating() && !getDisplayContent().mAppTransition.isTransitionSet()))) {
             return true;
         }
 
@@ -1473,7 +1473,7 @@
      * of a transition that has not yet been started.
      */
     boolean isReadyForDisplay() {
-        if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
+        if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
             return false;
         }
         final boolean parentAndClientVisible = !isParentWindowHidden()
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 2beb788..838d2a1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1364,7 +1364,8 @@
                         break;
                 }
                 if (attr >= 0) {
-                    a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr, TRANSIT_NONE);
+                    a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
+                            mWin.mAttrs, attr, TRANSIT_NONE);
                 }
             }
             if (DEBUG_ANIM) Slog.v(TAG,
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e13a70a..e82ffe8 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -16,62 +16,18 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
-import static android.view.WindowManager.TRANSIT_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SNAPSHOT;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 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.H.NOTIFY_APP_TRANSITION_STARTING;
 import static com.android.server.wm.WindowManagerService.H.REPORT_WINDOWS_CHANGE;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
-import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 
-import android.app.WindowConfiguration;
 import android.os.Debug;
 import android.os.Trace;
-import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseIntArray;
-import android.view.Display;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.WindowManager.TransitionType;
-import android.view.animation.Animation;
-
-import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.util.function.Predicate;
 
 /**
  * Positions windows and their surfaces.
@@ -233,550 +189,6 @@
         return mInLayout;
     }
 
-    /**
-     * @return bitmap indicating if another pass through layout must be made.
-     */
-    int handleAppTransitionReadyLocked() {
-        int appsCount = mService.mOpeningApps.size();
-        if (!transitionGoodToGo(appsCount, mTempTransitionReasons)) {
-            return 0;
-        }
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
-
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
-        int transit = mService.mAppTransition.getAppTransition();
-        if (mService.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
-            transit = WindowManager.TRANSIT_UNSET;
-        }
-        mService.mSkipAppTransitionAnimation = false;
-        mService.mNoAnimationNotifyOnTransitionFinished.clear();
-
-        mService.mAppTransition.removeAppTransitionTimeoutCallbacks();
-
-        final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
-
-        mService.mRoot.mWallpaperMayChange = false;
-
-        int i;
-        for (i = 0; i < appsCount; i++) {
-            final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-            // Clearing the mAnimatingExit flag before entering animation. It's set to true if app
-            // window is removed, or window relayout to invisible. This also affects window
-            // visibility. We need to clear it *before* maybeUpdateTransitToWallpaper() as the
-            // transition selection depends on wallpaper target visibility.
-            wtoken.clearAnimatingFlags();
-        }
-
-        // Adjust wallpaper before we pull the lower/upper target, since pending changes
-        // (like the clearAnimatingFlags() above) might affect wallpaper target result.
-        // Or, the opening app window should be a wallpaper target.
-        mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(displayContent,
-                mService.mOpeningApps);
-
-        // Determine if closing and opening app token sets are wallpaper targets, in which case
-        // special animations are needed.
-        final boolean hasWallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget() != null;
-        final boolean openingAppHasWallpaper = canBeWallpaperTarget(mService.mOpeningApps)
-                && hasWallpaperTarget;
-        final boolean closingAppHasWallpaper = canBeWallpaperTarget(mService.mClosingApps)
-                && hasWallpaperTarget;
-
-        transit = maybeUpdateTransitToTranslucentAnim(transit);
-        transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper,
-                closingAppHasWallpaper);
-
-        // Find the layout params of the top-most application window in the tokens, which is
-        // what will control the animation theme. If all closing windows are obscured, then there is
-        // no need to do an animation. This is the case, for example, when this transition is being
-        // done behind a dream window.
-        final ArraySet<Integer> activityTypes = collectActivityTypes(mService.mOpeningApps,
-                mService.mClosingApps);
-        final boolean allowAnimations = mService.mPolicy.allowAppAnimationsLw();
-        final AppWindowToken animLpToken = allowAnimations
-                ? findAnimLayoutParamsToken(transit, activityTypes)
-                : null;
-        final AppWindowToken topOpeningApp = allowAnimations
-                ? getTopApp(mService.mOpeningApps, false /* ignoreHidden */)
-                : null;
-        final AppWindowToken topClosingApp = allowAnimations
-                ? getTopApp(mService.mClosingApps, false /* ignoreHidden */)
-                : null;
-        final LayoutParams animLp = getAnimLp(animLpToken);
-        overrideWithRemoteAnimationIfSet(animLpToken, transit, activityTypes);
-
-        final boolean voiceInteraction = containsVoiceInteraction(mService.mOpeningApps)
-                || containsVoiceInteraction(mService.mOpeningApps);
-
-        final int layoutRedo;
-        mService.mSurfaceAnimationRunner.deferStartingAnimations();
-        try {
-            processApplicationsAnimatingInPlace(transit);
-
-            handleClosingApps(transit, animLp, voiceInteraction);
-            handleOpeningApps(transit, animLp, voiceInteraction);
-
-            mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
-
-            final int flags = mService.mAppTransition.getTransitFlags();
-            layoutRedo = mService.mAppTransition.goodToGo(transit, topOpeningApp, topClosingApp,
-                    mService.mOpeningApps, mService.mClosingApps);
-            handleNonAppWindowsInTransition(transit, flags);
-            mService.mAppTransition.postAnimationCallback();
-            mService.mAppTransition.clear();
-        } finally {
-            mService.mSurfaceAnimationRunner.continueStartingAnimations();
-        }
-
-        mService.mTaskSnapshotController.onTransitionStarting();
-
-        mService.mOpeningApps.clear();
-        mService.mClosingApps.clear();
-        mService.mUnknownAppVisibilityController.clear();
-
-        // This has changed the visibility of windows, so perform
-        // a new layout to get them all up-to-date.
-        displayContent.setLayoutNeeded();
-
-        // TODO(multidisplay): IMEs are only supported on the default display.
-        final DisplayContent dc = mService.getDefaultDisplayContentLocked();
-        dc.computeImeTarget(true /* updateImeTarget */);
-        mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
-                true /*updateInputWindows*/);
-        mService.mFocusMayChange = false;
-
-        mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING,
-                mTempTransitionReasons.clone()).sendToTarget();
-
-        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-
-        return layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
-    }
-
-    private static LayoutParams getAnimLp(AppWindowToken wtoken) {
-        final WindowState mainWindow = wtoken != null ? wtoken.findMainWindow() : null;
-        return mainWindow != null ? mainWindow.mAttrs : null;
-    }
-
-    /**
-     * Overrides the pending transition with the remote animation defined for the transition in the
-     * set of defined remote animations in the app window token.
-     */
-    private void overrideWithRemoteAnimationIfSet(AppWindowToken animLpToken, int transit,
-            ArraySet<Integer> activityTypes) {
-        if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
-            // The crash transition has higher priority than any involved remote animations.
-            return;
-        }
-        if (animLpToken == null) {
-            return;
-        }
-        final RemoteAnimationDefinition definition = animLpToken.getRemoteAnimationDefinition();
-        if (definition != null) {
-            final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
-            if (adapter != null) {
-                mService.mAppTransition.overridePendingAppTransitionRemote(adapter);
-            }
-        }
-    }
-
-    /**
-     * @return The window token that determines the animation theme.
-     */
-    private AppWindowToken findAnimLayoutParamsToken(@TransitionType int transit,
-            ArraySet<Integer> activityTypes) {
-        AppWindowToken result;
-
-        // Remote animations always win, but fullscreen tokens override non-fullscreen tokens.
-        result = lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps,
-                w -> w.getRemoteAnimationDefinition() != null
-                        && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
-        if (result != null) {
-            return result;
-        }
-        result = lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps,
-                w -> w.fillsParent() && w.findMainWindow() != null);
-        if (result != null) {
-            return result;
-        }
-        return lookForHighestTokenWithFilter(mService.mClosingApps, mService.mOpeningApps,
-                w -> w.findMainWindow() != null);
-    }
-
-    /**
-     * @return The set of {@link WindowConfiguration.ActivityType}s contained in the set of apps in
-     *         {@code array1} and {@code array2}.
-     */
-    private ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
-            ArraySet<AppWindowToken> array2) {
-        final ArraySet<Integer> result = new ArraySet<>();
-        for (int i = array1.size() - 1; i >= 0; i--) {
-            result.add(array1.valueAt(i).getActivityType());
-        }
-        for (int i = array2.size() - 1; i >= 0; i--) {
-            result.add(array2.valueAt(i).getActivityType());
-        }
-        return result;
-    }
-
-    private AppWindowToken lookForHighestTokenWithFilter(ArraySet<AppWindowToken> array1,
-            ArraySet<AppWindowToken> array2, Predicate<AppWindowToken> filter) {
-        final int array1count = array1.size();
-        final int count = array1count + array2.size();
-        int bestPrefixOrderIndex = Integer.MIN_VALUE;
-        AppWindowToken bestToken = null;
-        for (int i = 0; i < count; i++) {
-            final AppWindowToken wtoken = i < array1count
-                    ? array1.valueAt(i)
-                    : array2.valueAt(i - array1count);
-            final int prefixOrderIndex = wtoken.getPrefixOrderIndex();
-            if (filter.test(wtoken) && prefixOrderIndex > bestPrefixOrderIndex) {
-                bestPrefixOrderIndex = prefixOrderIndex;
-                bestToken = wtoken;
-            }
-        }
-        return bestToken;
-    }
-
-    private boolean containsVoiceInteraction(ArraySet<AppWindowToken> apps) {
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            if (apps.valueAt(i).mVoiceInteraction) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
-        final int appsCount = mService.mOpeningApps.size();
-        for (int i = 0; i < appsCount; i++) {
-            AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
-
-            if (!wtoken.setVisibility(animLp, true, transit, false, voiceInteraction)) {
-                // This token isn't going to be animating. Add it to the list of tokens to
-                // be notified of app transition complete since the notification will not be
-                // sent be the app window animator.
-                mService.mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
-            }
-            wtoken.updateReportedVisibilityLocked();
-            wtoken.waitingToShow = false;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
-            mService.openSurfaceTransaction();
-            try {
-                wtoken.showAllWindowsLocked();
-            } finally {
-                mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
-            }
-
-            if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                wtoken.attachThumbnailAnimation();
-            } else if (mService.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
-                wtoken.attachCrossProfileAppsThumbnailAnimation();
-            }
-        }
-    }
-
-    private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
-        final int appsCount;
-        appsCount = mService.mClosingApps.size();
-        for (int i = 0; i < appsCount; i++) {
-            AppWindowToken wtoken = mService.mClosingApps.valueAt(i);
-
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
-            // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
-            //       animating?
-            wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
-            wtoken.updateReportedVisibilityLocked();
-            // Force the allDrawn flag, because we want to start
-            // this guy's animations regardless of whether it's
-            // gotten drawn.
-            wtoken.allDrawn = true;
-            wtoken.deferClearAllDrawn = false;
-            // Ensure that apps that are mid-starting are also scheduled to have their
-            // starting windows removed after the animation is complete
-            if (wtoken.startingWindow != null && !wtoken.startingWindow.mAnimatingExit
-                    && wtoken.getController() != null) {
-                wtoken.getController().removeStartingWindow();
-            }
-
-            if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                wtoken.attachThumbnailAnimation();
-            }
-        }
-    }
-
-    private void handleNonAppWindowsInTransition(int transit, int flags) {
-        if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
-            if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
-                    && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
-                Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
-                        (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
-                if (anim != null) {
-                    mService.getDefaultDisplayContentLocked().mWallpaperController
-                            .startWallpaperAnimation(anim);
-                }
-            }
-        }
-        if (transit == TRANSIT_KEYGUARD_GOING_AWAY
-                || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
-            mService.getDefaultDisplayContentLocked().startKeyguardExitOnNonAppWindows(
-                    transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                    (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
-        }
-    }
-
-    private boolean transitionGoodToGo(int appsCount, SparseIntArray outReasons) {
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                "Checking " + appsCount + " opening apps (frozen="
-                        + mService.mDisplayFrozen + " timeout="
-                        + mService.mAppTransition.isTimeout() + ")...");
-        final ScreenRotationAnimation screenRotationAnimation =
-            mService.mAnimator.getScreenRotationAnimationLocked(
-                    Display.DEFAULT_DISPLAY);
-
-        outReasons.clear();
-        if (!mService.mAppTransition.isTimeout()) {
-            // Imagine the case where we are changing orientation due to an app transition, but a previous
-            // orientation change is still in progress. We won't process the orientation change
-            // for our transition because we need to wait for the rotation animation to finish.
-            // If we start the app transition at this point, we will interrupt it halfway with a new rotation
-            // animation after the old one finally finishes. It's better to defer the
-            // app transition.
-            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
-                    mService.getDefaultDisplayContentLocked().rotationNeedsUpdate()) {
-                if (DEBUG_APP_TRANSITIONS) {
-                    Slog.v(TAG, "Delaying app transition for screen rotation animation to finish");
-                }
-                return false;
-            }
-            for (int i = 0; i < appsCount; i++) {
-                AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                        "Check opening app=" + wtoken + ": allDrawn="
-                        + wtoken.allDrawn + " startingDisplayed="
-                        + wtoken.startingDisplayed + " startingMoved="
-                        + wtoken.startingMoved + " isRelaunching()="
-                        + wtoken.isRelaunching() + " startingWindow="
-                        + wtoken.startingWindow);
-
-
-                final boolean allDrawn = wtoken.allDrawn && !wtoken.isRelaunching();
-                if (!allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) {
-                    return false;
-                }
-                final int windowingMode = wtoken.getWindowingMode();
-                if (allDrawn) {
-                    outReasons.put(windowingMode,  APP_TRANSITION_WINDOWS_DRAWN);
-                } else {
-                    outReasons.put(windowingMode,
-                            wtoken.startingData instanceof SplashScreenStartingData
-                                    ? APP_TRANSITION_SPLASH_SCREEN
-                                    : APP_TRANSITION_SNAPSHOT);
-                }
-            }
-
-            // We also need to wait for the specs to be fetched, if needed.
-            if (mService.mAppTransition.isFetchingAppTransitionsSpecs()) {
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "isFetchingAppTransitionSpecs=true");
-                return false;
-            }
-
-            if (!mService.mUnknownAppVisibilityController.allResolved()) {
-                if (DEBUG_APP_TRANSITIONS) {
-                    Slog.v(TAG, "unknownApps is not empty: "
-                            + mService.mUnknownAppVisibilityController.getDebugMessage());
-                }
-                return false;
-            }
-
-            // If the wallpaper is visible, we need to check it's ready too.
-            boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
-                    mWallpaperControllerLocked.wallpaperTransitionReady();
-            if (wallpaperReady) {
-                return true;
-            }
-            return false;
-        }
-        return true;
-    }
-
-    private int maybeUpdateTransitToWallpaper(int transit, boolean openingAppHasWallpaper,
-            boolean closingAppHasWallpaper) {
-        // Given no app transition pass it through instead of a wallpaper transition.
-        // Never convert the crashing transition.
-        // Never update the transition for the wallpaper if we are just docking from recents
-        if (transit == TRANSIT_NONE || transit == TRANSIT_CRASHING_ACTIVITY_CLOSE
-                || transit == TRANSIT_DOCK_TASK_FROM_RECENTS) {
-            return transit;
-        }
-
-        final WindowState wallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget();
-        final boolean showWallpaper = wallpaperTarget != null
-                && (wallpaperTarget.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
-        // If wallpaper is animating or wallpaperTarget doesn't have SHOW_WALLPAPER flag set,
-        // don't consider upgrading to wallpaper transition.
-        final WindowState oldWallpaper =
-                (mWallpaperControllerLocked.isWallpaperTargetAnimating() || !showWallpaper)
-                        ? null
-                        : wallpaperTarget;
-        final ArraySet<AppWindowToken> openingApps = mService.mOpeningApps;
-        final ArraySet<AppWindowToken> closingApps = mService.mClosingApps;
-        final AppWindowToken topOpeningApp = getTopApp(mService.mOpeningApps,
-                false /* ignoreHidden */);
-        final AppWindowToken topClosingApp = getTopApp(mService.mClosingApps,
-                true /* ignoreHidden */);
-
-        boolean openingCanBeWallpaperTarget = canBeWallpaperTarget(openingApps);
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                "New wallpaper target=" + wallpaperTarget
-                        + ", oldWallpaper=" + oldWallpaper
-                        + ", openingApps=" + openingApps
-                        + ", closingApps=" + closingApps);
-
-        if (openingCanBeWallpaperTarget && transit == TRANSIT_KEYGUARD_GOING_AWAY) {
-            transit = TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                    "New transit: " + AppTransition.appTransitionToString(transit));
-        }
-        // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic
-        // relies on the fact that we always execute a Keyguard transition after preparing one.
-        else if (!isKeyguardGoingAwayTransit(transit)) {
-            if (closingAppHasWallpaper && openingAppHasWallpaper) {
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
-                switch (transit) {
-                    case TRANSIT_ACTIVITY_OPEN:
-                    case TRANSIT_TASK_OPEN:
-                    case TRANSIT_TASK_TO_FRONT:
-                        transit = TRANSIT_WALLPAPER_INTRA_OPEN;
-                        break;
-                    case TRANSIT_ACTIVITY_CLOSE:
-                    case TRANSIT_TASK_CLOSE:
-                    case TRANSIT_TASK_TO_BACK:
-                        transit = TRANSIT_WALLPAPER_INTRA_CLOSE;
-                        break;
-                }
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
-                        "New transit: " + AppTransition.appTransitionToString(transit));
-            } else if (oldWallpaper != null && !mService.mOpeningApps.isEmpty()
-                    && !openingApps.contains(oldWallpaper.mAppToken)
-                    && closingApps.contains(oldWallpaper.mAppToken)
-                    && topClosingApp == oldWallpaper.mAppToken) {
-                // We are transitioning from an activity with a wallpaper to one without.
-                transit = TRANSIT_WALLPAPER_CLOSE;
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: "
-                        + AppTransition.appTransitionToString(transit));
-            } else if (wallpaperTarget != null && wallpaperTarget.isVisibleLw()
-                    && openingApps.contains(wallpaperTarget.mAppToken)
-                    && topOpeningApp == wallpaperTarget.mAppToken
-                    && transit != TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE) {
-                // We are transitioning from an activity without
-                // a wallpaper to now showing the wallpaper
-                transit = TRANSIT_WALLPAPER_OPEN;
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: "
-                        + AppTransition.appTransitionToString(transit));
-            }
-        }
-        return transit;
-    }
-
-    /**
-     * There are cases where we open/close a new task/activity, but in reality only a translucent
-     * activity on top of existing activities is opening/closing. For that one, we have a different
-     * animation because non of the task/activity animations actually work well with translucent
-     * apps.
-     *
-     * @param transit The current transition type.
-     * @return The current transition type or
-     *         {@link WindowManager#TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE}/
-     *         {@link WindowManager#TRANSIT_TRANSLUCENT_ACTIVITY_OPEN} if appropriate for the
-     *         situation.
-     */
-    @VisibleForTesting
-    int maybeUpdateTransitToTranslucentAnim(int transit) {
-        final boolean taskOrActivity = AppTransition.isTaskTransit(transit)
-                || AppTransition.isActivityTransit(transit);
-        boolean allOpeningVisible = true;
-        boolean allTranslucentOpeningApps = !mService.mOpeningApps.isEmpty();
-        for (int i = mService.mOpeningApps.size() - 1; i >= 0; i--) {
-            final AppWindowToken token = mService.mOpeningApps.valueAt(i);
-            if (!token.isVisible()) {
-                allOpeningVisible = false;
-                if (token.fillsParent()) {
-                    allTranslucentOpeningApps = false;
-                }
-            }
-        }
-        boolean allTranslucentClosingApps = !mService.mClosingApps.isEmpty();
-        for (int i = mService.mClosingApps.size() - 1; i >= 0; i--) {
-            if (mService.mClosingApps.valueAt(i).fillsParent()) {
-                allTranslucentClosingApps = false;
-                break;
-            }
-        }
-
-        if (taskOrActivity && allTranslucentClosingApps && allOpeningVisible) {
-            return TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE;
-        }
-        if (taskOrActivity && allTranslucentOpeningApps && mService.mClosingApps.isEmpty()) {
-            return TRANSIT_TRANSLUCENT_ACTIVITY_OPEN;
-        }
-        return transit;
-    }
-
-    private boolean canBeWallpaperTarget(ArraySet<AppWindowToken> apps) {
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            if (apps.valueAt(i).windowsCanBeWallpaperTarget()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Finds the top app in a list of apps, using its {@link AppWindowToken#getPrefixOrderIndex} to
-     * compare z-order.
-     *
-     * @param apps The list of apps to search.
-     * @param ignoreHidden If set to true, ignores apps that are {@link AppWindowToken#isHidden}.
-     * @return The top {@link AppWindowToken}.
-     */
-    private AppWindowToken getTopApp(ArraySet<AppWindowToken> apps, boolean ignoreHidden) {
-        int topPrefixOrderIndex = Integer.MIN_VALUE;
-        AppWindowToken topApp = null;
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            final AppWindowToken app = apps.valueAt(i);
-            if (ignoreHidden && app.isHidden()) {
-                continue;
-            }
-            final int prefixOrderIndex = app.getPrefixOrderIndex();
-            if (prefixOrderIndex > topPrefixOrderIndex) {
-                topPrefixOrderIndex = prefixOrderIndex;
-                topApp = app;
-            }
-        }
-        return topApp;
-    }
-
-    private void processApplicationsAnimatingInPlace(int transit) {
-        if (transit == TRANSIT_TASK_IN_PLACE) {
-            // TODO (b/111362605): non-default-display transition.
-            // Find the focused window
-            final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
-            if (win != null) {
-                final AppWindowToken wtoken = win.mAppToken;
-                if (DEBUG_APP_TRANSITIONS)
-                    Slog.v(TAG, "Now animating app in place " + wtoken);
-                wtoken.cancelAnimation();
-                wtoken.applyAnimationLocked(null, transit, false, false);
-                wtoken.updateReportedVisibilityLocked();
-                wtoken.showAllWindowsLocked();
-            }
-        }
-    }
-
     void requestTraversal() {
         if (!mTraversalScheduled) {
             mTraversalScheduled = true;