2/n Move task management logic to TaskContainers

Still no changes in logic, just moving the code around.

Bug: 152116619
Test: WM CTS and unit tests
Change-Id: I083b751c20fe110f120ae6c7626d3f2e974605ab
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 29f7d52..78d6e27 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2504,7 +2504,7 @@
                     final DisplayContent display = stack.getDisplay();
                     next = display.topRunningActivity();
                     if (next != null) {
-                        display.positionStackAtTop(next.getRootTask(),
+                        display.mTaskContainers.positionStackAtTop(next.getRootTask(),
                                 false /* includingParents */, "finish-display-top");
                     }
                 }
@@ -2679,7 +2679,7 @@
         final ActivityRecord next = display.topRunningActivity();
         final boolean isLastStackOverEmptyHome =
                 next == null && stack.isFocusedStackOnDisplay()
-                        && display.getOrCreateRootHomeTask() != null;
+                        && display.mTaskContainers.getOrCreateRootHomeTask() != null;
         if (isLastStackOverEmptyHome) {
             // Don't destroy activity immediately if this is the last activity on the display and
             // the display contains home stack. Although there is no next activity at the moment,
@@ -4477,7 +4477,7 @@
         // case where this is the top activity in a pinned stack.
         final boolean isTop = this == stack.getTopNonFinishingActivity();
         final boolean isTopNotPinnedStack = stack.isAttached()
-                && stack.getDisplay().isTopNotPinnedStack(stack);
+                && stack.getDisplay().mTaskContainers.isTopNotPinnedStack(stack);
         final boolean visibleIgnoringDisplayStatus = stack.checkKeyguardVisibility(this,
                 visibleIgnoringKeyguard, isTop && isTopNotPinnedStack);
 
@@ -7389,7 +7389,7 @@
      */
     boolean isResumedActivityOnDisplay() {
         final DisplayContent display = getDisplay();
-        return display != null && this == display.getResumedActivity();
+        return display != null && this == display.mTaskContainers.getResumedActivity();
     }
 
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 9089859..64c14cf 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -670,7 +670,7 @@
             // Since always on top is only on when the stack is freeform or pinned, the state
             // can be toggled when the windowing mode changes. We must make sure the stack is
             // placed properly when always on top state changes.
-            display.positionStackAtTop(this, false /* includingParents */);
+            display.mTaskContainers.positionStackAtTop(this, false /* includingParents */);
         }
     }
 
@@ -742,7 +742,7 @@
         // Need to make sure windowing mode is supported. If we in the process of creating the stack
         // no need to resolve the windowing mode again as it is already resolved to the right mode.
         if (!creating) {
-            windowingMode = display.validateWindowingMode(windowingMode,
+            windowingMode = display.mTaskContainers.validateWindowingMode(windowingMode,
                     null /* ActivityRecord */, topTask, getActivityType());
         }
         if (display.getRootSplitScreenPrimaryTask() == this
@@ -752,7 +752,8 @@
             windowingMode = mRestoreOverrideWindowingMode;
         }
 
-        final boolean alreadyInSplitScreenMode = display.isSplitScreenModeActivated();
+        final boolean alreadyInSplitScreenMode = display.mTaskContainers
+                .isSplitScreenModeActivated();
 
         // Don't send non-resizeable notifications if the windowing mode changed was a side effect
         // of us entering split-screen mode.
@@ -769,7 +770,7 @@
                 // warning toast about it.
                 mAtmService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                display.onSplitScreenModeDismissed();
+                display.mTaskContainers.onSplitScreenModeDismissed();
             }
         }
 
@@ -861,7 +862,7 @@
                 // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode
                 final boolean isRecentsComponentHome =
                         mAtmService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser);
-                final ActivityStack recentStack = display.getOrCreateStack(
+                final ActivityStack recentStack = display.mTaskContainers.getOrCreateStack(
                         WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
                         isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS,
                         true /* onTop */);
@@ -1058,7 +1059,7 @@
             // cutting between them.
             // TODO(b/70677280): This is a workaround until we can fix as part of b/70677280.
             final ActivityStack topFullScreenStack =
-                    display.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                    display.mTaskContainers.getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             if (topFullScreenStack != null) {
                 final ActivityStack primarySplitScreenStack = display.getRootSplitScreenPrimaryTask();
                 if (primarySplitScreenStack != null && display.getIndexOf(topFullScreenStack)
@@ -1071,11 +1072,11 @@
         if (!isActivityTypeHome() && returnsToHomeStack()) {
             // Make sure the home stack is behind this stack since that is where we should return to
             // when this stack is no longer visible.
-            display.moveHomeStackToFront(reason + " returnToHome");
+            display.mTaskContainers.moveHomeStackToFront(reason + " returnToHome");
         }
 
         if (isRootTask()) {
-            display.positionStackAtTop(this, false /* includingParents */, reason);
+            display.mTaskContainers.positionStackAtTop(this, false /* includingParents */, reason);
         }
         if (task == null) {
             task = this;
@@ -1092,7 +1093,7 @@
             return;
         }
 
-        getDisplay().positionStackAtBottom(this, reason);
+        getDisplay().mTaskContainers.positionStackAtBottom(this, reason);
         if (task != null && task != this) {
             positionChildAtBottom(task);
         }
@@ -1460,7 +1461,7 @@
 
     boolean isTopStackOnDisplay() {
         final DisplayContent display = getDisplay();
-        return display != null && display.isTopStack(this);
+        return display != null && display.mTaskContainers.isTopStack(this);
     }
 
     /**
@@ -1667,7 +1668,8 @@
      */
     boolean isTopSplitScreenStack() {
         return inSplitScreenWindowingMode()
-                && this == getDisplay().getTopStackInWindowingMode(getWindowingMode());
+                && this == getDisplay().mTaskContainers
+                .getTopStackInWindowingMode(getWindowingMode());
     }
 
     /** @return True if the resizing of the primary-split-screen stack affects this stack size. */
@@ -1900,7 +1902,7 @@
 
         // If the top activity is the resumed one, nothing to do.
         if (mResumedActivity == next && next.isState(RESUMED)
-                && display.allResumedActivitiesComplete()) {
+                && display.mTaskContainers.allResumedActivitiesComplete()) {
             // Make sure we have executed any pending transitions, since there
             // should be nothing left to do at this point.
             executeAppTransition(options);
@@ -1979,7 +1981,7 @@
         mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
 
         ActivityRecord lastResumed = null;
-        final ActivityStack lastFocusedStack = display.getLastFocusedStack();
+        final ActivityStack lastFocusedStack = display.mTaskContainers.getLastFocusedStack();
         if (lastFocusedStack != null && lastFocusedStack != this) {
             // So, why aren't we using prev here??? See the param comment on the method. prev doesn't
             // represent the last resumed activity. However, the last focus stack does if it isn't null.
@@ -1993,7 +1995,7 @@
             }
         }
 
-        boolean pausing = display.pauseBackStacks(userLeaving, next);
+        boolean pausing = display.mTaskContainers.pauseBackStacks(userLeaving, next);
         if (mResumedActivity != null) {
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "resumeTopActivityLocked: Pausing " + mResumedActivity);
@@ -2022,7 +2024,7 @@
             }
             return true;
         } else if (mResumedActivity == next && next.isState(RESUMED)
-                && display.allResumedActivitiesComplete()) {
+                && display.mTaskContainers.allResumedActivitiesComplete()) {
             // It is possible for the activity to be resumed when we paused back stacks above if the
             // next activity doesn't have to wait for pause to complete.
             // So, nothing else to-do except:
@@ -2542,7 +2544,7 @@
         if (stack.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
             // If we will be focusing on the home stack next and its current top activity isn't
             // visible, then use the move the home stack task to top to make the activity visible.
-            stack.getDisplay().moveHomeActivityToTop(reason);
+            stack.getDisplay().mTaskContainers.moveHomeActivityToTop(reason);
             return stack;
         }
 
@@ -3304,7 +3306,7 @@
         final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
 
         boolean toTop = position >= getChildCount();
-        boolean includingParents = toTop || getDisplay().getNextFocusableStack(this,
+        boolean includingParents = toTop || getDisplay().mTaskContainers.getNextFocusableStack(this,
                 true /* ignoreCurrent */) == null;
         if (WindowManagerDebugConfig.DEBUG_STACK) {
             Slog.i(TAG_WM, "positionChildAt: positioning task=" + task + " at " + position);
@@ -3348,7 +3350,7 @@
         // always on top windows. Since the position the stack should be inserted into is calculated
         // properly in {@link DisplayContent#getTopInsertPosition()} in both cases, we can just
         // request that the stack is put at top here.
-        display.positionStackAtTop(this, false /* includingParents */);
+        display.mTaskContainers.positionStackAtTop(this, false /* includingParents */);
     }
 
     /** NOTE: Should only be called from {@link Task#reparent}. */
@@ -3399,7 +3401,7 @@
             final Task task = getBottomMostTask();
             setWindowingMode(WINDOWING_MODE_UNDEFINED);
 
-            getDisplay().positionStackAtTop(this, false /* includingParents */);
+            getDisplay().mTaskContainers.positionStackAtTop(this, false /* includingParents */);
 
             mStackSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
             MetricsLoggerWrapper.logPictureInPictureFullScreen(mAtmService.mContext,
@@ -3519,7 +3521,7 @@
         // If there are other focusable stacks on the display, the z-order of the display should not
         // be changed just because a task was placed at the bottom. E.g. if it is moving the topmost
         // task to bottom, the next focusable stack on the same display should be focused.
-        final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack(
+        final ActivityStack nextFocusableStack = getDisplay().mTaskContainers.getNextFocusableStack(
                 child.getStack(), true /* ignoreCurrent */);
         positionChildAtBottom(child, nextFocusableStack == null /* includingParents */);
         child.updateTaskMovement(true);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 4652f49..3f8fd73 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -414,14 +414,15 @@
                 if (mToDisplay.getDisplayId() != stack.getDisplayId()) {
                     mToDisplay.moveStackToDisplay(stack, mOnTop);
                 } else if (mOnTop) {
-                    mToDisplay.positionStackAtTop(stack, false /* includingParents */);
+                    mToDisplay.mTaskContainers.positionStackAtTop(stack,
+                            false /* includingParents */);
                 } else {
-                    mToDisplay.positionStackAtBottom(stack);
+                    mToDisplay.mTaskContainers.positionStackAtBottom(stack);
                 }
                 return;
             }
 
-            final ActivityStack toStack = mToDisplay.getOrCreateStack(
+            final ActivityStack toStack = mToDisplay.mTaskContainers.getOrCreateStack(
                     null, mTmpOptions, task, task.getActivityType(), mOnTop);
             if (task == toStack) {
                 // The task was reused as the root task.
@@ -1458,7 +1459,7 @@
                 || (focusedStack != null && focusedStack.isActivityTypeRecents())) {
             // We move home stack to front when we are on a fullscreen display and caller has
             // requested the home activity to move with it. Or the previous stack is recents.
-            display.moveHomeStackToFront(reason);
+            display.mTaskContainers.moveHomeStackToFront(reason);
         }
     }
 
@@ -1877,7 +1878,7 @@
         mStoppingActivities.remove(r);
 
         final ActivityStack stack = r.getRootTask();
-        if (stack.getDisplay().allResumedActivitiesComplete()) {
+        if (stack.getDisplay().mTaskContainers.allResumedActivitiesComplete()) {
             mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
             // Make sure activity & window visibility should be identical
             // for all displays in this stage.
@@ -2241,7 +2242,7 @@
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.getDisplay().isSplitScreenModeActivated();
+                && actualStack.getDisplay().mTaskContainers.isSplitScreenModeActivated();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
@@ -2289,12 +2290,12 @@
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
             final DisplayContent display = task.getStack().getDisplay();
-            if (display.isSplitScreenModeActivated()) {
+            if (display.mTaskContainers.isSplitScreenModeActivated()) {
                 // Display a warning toast that we tried to put an app that doesn't support
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                display.onSplitScreenModeDismissed();
+                display.mTaskContainers.onSplitScreenModeDismissed();
                 display.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
                         true /* notifyClients */);
             }
@@ -2612,7 +2613,7 @@
                 // from whatever is started from the recents activity, so move the home stack
                 // forward.
                 // TODO (b/115289124): Multi-display supports for recents.
-                mRootWindowContainer.getDefaultDisplay().moveHomeStackToFront(
+                mRootWindowContainer.getDefaultDisplay().mTaskContainers.moveHomeStackToFront(
                         "startActivityFromRecents");
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 881dc81..0a0049d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -190,8 +190,8 @@
         final ActivityStack homeStack;
         try {
             // Make sure home stack exist on display.
-            homeStack = display.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
-                    ON_TOP);
+            homeStack = display.mTaskContainers.getOrCreateStack(WINDOWING_MODE_FULLSCREEN,
+                    ACTIVITY_TYPE_HOME, ON_TOP);
         } finally {
             mSupervisor.endDeferResume();
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ce885ab..6921816 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2357,7 +2357,7 @@
                 }
                 // Convert some windowing-mode changes into root-task reparents for split-screen.
                 if (stack.inSplitScreenWindowingMode()) {
-                    stack.getDisplay().onSplitScreenModeDismissed();
+                    stack.getDisplay().mTaskContainers.onSplitScreenModeDismissed();
 
                 } else {
                     stack.setWindowingMode(windowingMode);
@@ -2775,7 +2775,8 @@
         }
 
         if (toTop) {
-            display.positionStackAt(POSITION_TOP, primarySplitTask, false /* includingParents */);
+            display.mTaskContainers.positionStackAt(POSITION_TOP, primarySplitTask,
+                    false /* includingParents */);
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparent(task.getStack().mRemoteToken, primarySplitTask.mRemoteToken, toTop);
@@ -3244,8 +3245,8 @@
                 }
 
                 final ActivityStack stack = r.getRootTask();
-                final Task task = stack.getDisplay().createStack(stack.getWindowingMode(),
-                        stack.getActivityType(), !ON_TOP, ainfo, intent,
+                final Task task = stack.getDisplay().mTaskContainers.createStack(
+                        stack.getWindowingMode(), stack.getActivityType(), !ON_TOP, ainfo, intent,
                         false /* createdByOrganizer */);
 
                 if (!mRecentTasks.addToBottom(task)) {
@@ -3308,10 +3309,10 @@
                     throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
                 }
                 if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                    stack = stack.getDisplay().getOrCreateStack(
+                    stack = stack.getDisplay().mTaskContainers.getOrCreateStack(
                             WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
                 } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
-                    stack = stack.getDisplay().getOrCreateStack(
+                    stack = stack.getDisplay().mTaskContainers.getOrCreateStack(
                             WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP);
                 }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5900ae8..4a7edee 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -21,17 +21,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -83,10 +78,6 @@
 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.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
@@ -111,7 +102,6 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.RootWindowContainer.TAG_STATES;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
@@ -151,13 +141,9 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityOptions;
-import android.app.WindowConfiguration;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
-import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -212,7 +198,6 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -285,7 +270,7 @@
 
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
     // Contains all window containers that are related to apps (Activities)
-    private final TaskContainers mTaskContainers = new TaskContainers(this, mWmService);
+    final TaskContainers mTaskContainers = new TaskContainers(this, mWmService);
 
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -452,8 +437,6 @@
 
     private final Configuration mTmpConfiguration = new Configuration();
 
-    private ArrayList<Task> mTmpTasks = new ArrayList<>();
-
     /** Remove this display when animation on it has completed. */
     private boolean mDeferredRemoval;
 
@@ -616,7 +599,7 @@
     private boolean mRemoved;
 
     /** The display can only contain one task. */
-    private boolean mSingleTaskInstance;
+    boolean mSingleTaskInstance;
 
     /**
      * Non-null if the last size compatibility mode activity is using non-native screen
@@ -632,13 +615,6 @@
      */
     private ActivityStack mPreferredTopFocusableStack;
 
-    /**
-     * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
-     * stack has been resumed. If stacks are changing position this will hold the old stack until
-     * the new stack becomes resumed after which it will be set to current focused stack.
-     */
-    private ActivityStack mLastFocusedStack;
-
     // Used in updating the display size
     private Point mTmpDisplaySize = new Point();
 
@@ -2095,30 +2071,11 @@
         return mTaskContainers.getRootHomeTask();
     }
 
-    /**
-     * Returns the existing home stack or creates and returns a new one if it should exist for the
-     * display.
-     */
-    @Nullable
-    ActivityStack getOrCreateRootHomeTask() {
-        ActivityStack homeTask = getRootHomeTask();
-        if (homeTask == null && supportsSystemDecorations() && !isUntrustedVirtualDisplay()) {
-            homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
-                    false /* onTop */);
-        }
-        return homeTask;
-    }
-
     /** @return The primary split-screen task, and {@code null} otherwise. */
     @Nullable ActivityStack getRootSplitScreenPrimaryTask() {
         return mTaskContainers.getRootSplitScreenPrimaryTask();
     }
 
-    boolean isSplitScreenModeActivated() {
-        Task task = getRootSplitScreenPrimaryTask();
-        return task != null && task.hasChild();
-    }
-
     ActivityStack getRootPinnedTask() {
         return mTaskContainers.getRootPinnedTask();
     }
@@ -2128,14 +2085,6 @@
     }
 
     /**
-     * Returns the topmost stack on the display that is compatible with the input windowing mode.
-     * Null is no compatible stack on the display.
-     */
-    ActivityStack getTopStackInWindowingMode(int windowingMode) {
-        return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
-    }
-
-    /**
      * Returns the topmost stack on the display that is compatible with the input windowing mode and
      * activity type. Null is no compatible stack on the display.
      */
@@ -2511,11 +2460,6 @@
         positionDisplayAt(position, includingParents);
     }
 
-    void positionStackAt(int position, ActivityStack child, boolean includingParents) {
-        mTaskContainers.positionChildAt(position, child, includingParents);
-        layoutAndAssignWindowLayersIfNeeded();
-    }
-
     /**
      * Returns true if the input point is within an app window.
      */
@@ -2583,7 +2527,7 @@
         }
         amendWindowTapExcludeRegion(mTouchExcludeRegion);
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && isSplitScreenModeActivated()) {
+        if (mDisplayId == DEFAULT_DISPLAY && mTaskContainers.isSplitScreenModeActivated()) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2847,7 +2791,8 @@
         final ActivityStack focusedStack = getFocusedStack();
         if (focusedStack != null) {
             proto.write(FOCUSED_ROOT_TASK_ID, focusedStack.getRootTaskId());
-            final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
+            final ActivityRecord focusedActivity = focusedStack.getDisplay().mTaskContainers
+                    .getResumedActivity();
             if (focusedActivity != null) {
                 focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
             }
@@ -2908,8 +2853,8 @@
         if (mPreferredTopFocusableStack != null) {
             pw.println(prefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
         }
-        if (mLastFocusedStack != null) {
-            pw.println(prefix + "mLastFocusedStack=" + mLastFocusedStack);
+        if (mTaskContainers.mLastFocusedStack != null) {
+            pw.println(prefix + "mLastFocusedStack=" + mTaskContainers.mLastFocusedStack);
         }
         if (mLosingFocus.size() > 0) {
             pw.println();
@@ -3025,7 +2970,7 @@
 
     /** Returns true if the stack in the windowing mode is visible. */
     boolean isStackVisible(int windowingMode) {
-        final ActivityStack stack = getTopStackInWindowingMode(windowingMode);
+        final ActivityStack stack = mTaskContainers.getTopStackInWindowingMode(windowingMode);
         return stack != null && stack.isVisible();
     }
 
@@ -4430,7 +4375,8 @@
         private boolean skipImeWindowsDuringTraversal(DisplayContent dc) {
             // We skip IME windows so they're processed just above their target, except
             // in split-screen mode where we process the IME containers above the docked divider.
-            return dc.mInputMethodTarget != null && !dc.isSplitScreenModeActivated();
+            return dc.mInputMethodTarget != null
+                    && !dc.mTaskContainers.isSplitScreenModeActivated();
         }
 
         /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -5098,111 +5044,6 @@
         mWmService.requestTraversal();
     }
 
-    void addStack(ActivityStack stack, int position) {
-        setStackOnDisplay(stack, position);
-        positionStackAt(stack, position);
-    }
-
-    void addStackReferenceIfNeeded(ActivityStack stack) {
-        mTaskContainers.addStackReferenceIfNeeded(stack);
-    }
-
-    void removeStackReferenceIfNeeded(ActivityStack stack) {
-        mTaskContainers.removeStackReferenceIfNeeded(stack);
-    }
-
-    void onStackRemoved(ActivityStack stack) {
-        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
-            Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
-        }
-        if (mPreferredTopFocusableStack == stack) {
-            mPreferredTopFocusableStack = null;
-        }
-        releaseSelfIfNeeded();
-        onStackOrderChanged(stack);
-    }
-
-    void positionStackAtTop(ActivityStack stack, boolean includingParents) {
-        positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
-    }
-
-    void positionStackAtTop(ActivityStack stack, boolean includingParents,
-            String updateLastFocusedStackReason) {
-        positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason);
-    }
-
-    void positionStackAtBottom(ActivityStack stack) {
-        positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
-    }
-
-    void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
-        positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason);
-    }
-
-    private void positionStackAt(ActivityStack stack, int position) {
-        positionStackAt(stack, position, false /* includingParents */,
-                null /* updateLastFocusedStackReason */);
-    }
-
-    private void positionStackAt(ActivityStack stack, int position, boolean includingParents,
-            String updateLastFocusedStackReason) {
-        // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
-        //       the position internally, also update the logic here
-        final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
-                ? getFocusedStack() : null;
-        final boolean wasContained = getIndexOf(stack) >= 0;
-        if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
-            throw new IllegalStateException(
-                    "positionStackAt: Can only have one task on display=" + this);
-        }
-
-        final boolean movingToTop = wasContained && position >= getStackCount() - 1;
-        // Reset mPreferredTopFocusableStack before positioning to top or {@link
-        // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top
-        // resumed activity.
-        if (movingToTop && stack.isFocusable()) {
-            mPreferredTopFocusableStack = null;
-        }
-
-        // Since positionChildAt() is called during the creation process of pinned stacks,
-        // ActivityStack#getStack() can be null.
-        positionStackAt(position, stack, includingParents);
-
-        // The insert position may be adjusted to non-top when there is always-on-top stack. Since
-        // the original position is preferred to be top, the stack should have higher priority when
-        // we are looking for top focusable stack. The condition {@code wasContained} restricts the
-        // preferred stack is set only when moving an existing stack to top instead of adding a new
-        // stack that may be too early (e.g. in the middle of launching or reparenting).
-        if (movingToTop && stack.isFocusableAndVisible()) {
-            mPreferredTopFocusableStack = stack;
-        } else if (mPreferredTopFocusableStack == stack) {
-            mPreferredTopFocusableStack = null;
-        }
-
-        if (updateLastFocusedStackReason != null) {
-            final ActivityStack currentFocusedStack = getFocusedStack();
-            if (currentFocusedStack != prevFocusedStack) {
-                mLastFocusedStack = prevFocusedStack;
-                EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser, mDisplayId,
-                        currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(),
-                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
-                        updateLastFocusedStackReason);
-            }
-        }
-
-        onStackOrderChanged(stack);
-    }
-
-    ActivityStack getStack(int rootTaskId) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (stack.getRootTaskId() == rootTaskId) {
-                return stack;
-            }
-        }
-        return null;
-    }
-
     static boolean alwaysCreateStack(int windowingMode, int activityType) {
         // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
         // modes so that we can manage visual ordering and return types correctly.
@@ -5213,358 +5054,13 @@
                 || windowingMode == WINDOWING_MODE_MULTI_WINDOW);
     }
 
-    /**
-     * Returns an existing stack compatible with the windowing mode and activity type or creates one
-     * if a compatible stack doesn't exist.
-     * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean)
-     */
-    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
-        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
-                null /* candidateTask */, false /* createdByOrganizer */);
-    }
-
-    /**
-     * When two level tasks are required for given windowing mode and activity type, returns an
-     * existing compatible root task or creates a new one.
-     * For one level task, the candidate task would be reused to also be the root task or create
-     * a new root task if no candidate task.
-     * @see #getStack(int, int)
-     * @see #createStack(int, int, boolean)
-     */
-    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop,
-            Intent intent, Task candidateTask, boolean createdByOrganizer) {
-        if (!alwaysCreateStack(windowingMode, activityType)) {
-            ActivityStack stack = getStack(windowingMode, activityType);
-            if (stack != null) {
-                return stack;
-            }
-        } else if (candidateTask != null) {
-            final ActivityStack stack = (ActivityStack) candidateTask;
-            final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
-            if (isSplitScreenModeActivated()) {
-                final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
-                        && t.inSplitScreenSecondaryWindowingMode());
-                if (stack.getParent() == null) {
-                    splitRootSecondary.addChild(stack, position);
-                } else if (stack.getParent() != splitRootSecondary) {
-                    stack.reparent(splitRootSecondary, position);
-                }
-            } else if (stack.getDisplay() != this || !stack.isRootTask()) {
-                if (stack.getParent() == null) {
-                    addStack(stack, position);
-                } else {
-                    stack.reparent(this, onTop);
-                }
-            }
-            // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
-            if (candidateTask.getWindowingMode() != windowingMode) {
-                candidateTask.setWindowingMode(windowingMode);
-            }
-            return stack;
-        }
-        return createStack(windowingMode, activityType, onTop, null /*info*/, intent,
-                createdByOrganizer);
-    }
-
-    /**
-     * Returns an existing stack compatible with the input params or creates one
-     * if a compatible stack doesn't exist.
-     * @see #getOrCreateStack(int, int, boolean)
-     */
-    ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
-            @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
-            boolean onTop) {
-        // First preference is the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-        // Validate that our desired windowingMode will work under the current conditions.
-        // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
-        // it's display's windowing mode.
-        windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
-        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
-                candidateTask, false /* createdByOrganizer */);
-    }
-
-    @VisibleForTesting
-    int getNextStackId() {
-        return mAtmService.mStackSupervisor.getNextTaskIdForUser();
-    }
-
     ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
-        return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */,
-                false /* createdByOrganizer */);
+        return mTaskContainers.createStack(windowingMode, activityType, onTop, null /* info */,
+                null /* intent */, false /* createdByOrganizer */);
     }
 
-    /**
-     * Creates a stack matching the input windowing mode and activity type on this display.
-     * @param windowingMode The windowing mode the stack should be created in. If
-     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
-     *                      inherit its parent's windowing mode.
-     * @param activityType The activityType the stack should be created in. If
-     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
-     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
-     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
-     * @param info The started activity info.
-     * @param intent The intent that started this task.
-     * @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
-     *                          otherwise.
-     * @return The newly created stack.
-     */
-    ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
-            Intent intent, boolean createdByOrganizer) {
-        if (mSingleTaskInstance && getStackCount() > 0) {
-            // Create stack on default display instead since this display can only contain 1 stack.
-            // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
-            // this goes away once ActivityView is no longer using virtual displays.
-            return mRootWindowContainer.getDefaultDisplay().createStack(
-                    windowingMode, activityType, onTop, info, intent, createdByOrganizer);
-        }
-
-        if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) {
-            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
-            // anything else should be passing it in anyways...except for the task organizer.
-            activityType = ACTIVITY_TYPE_STANDARD;
-        }
-
-        if (activityType != ACTIVITY_TYPE_STANDARD && activityType != ACTIVITY_TYPE_UNDEFINED) {
-            // For now there can be only one stack of a particular non-standard activity type on a
-            // display. So, get that ignoring whatever windowing mode it is currently in.
-            ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
-            if (stack != null) {
-                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
-                        + activityType + " already on display=" + this + ". Can't have multiple.");
-            }
-        }
-
-        if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
-                mAtmService.mSupportsSplitScreenMultiWindow,
-                mAtmService.mSupportsFreeformWindowManagement,
-                mAtmService.mSupportsPictureInPicture, activityType)) {
-            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
-                    + windowingMode);
-        }
-
-        final int stackId = getNextStackId();
-        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent,
-                createdByOrganizer);
-    }
-
-    /** @return the root task to create the next task in. */
-    private Task updateLaunchRootTask(int windowingMode) {
-        if (!isSplitScreenWindowingMode(windowingMode)) {
-            // Only split-screen windowing modes can do this currently...
-            return null;
-        }
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final Task t = getStackAt(i);
-            if (!t.mCreatedByOrganizer || t.getRequestedOverrideWindowingMode() != windowingMode) {
-                continue;
-            }
-            // If not already set, pick a launch root which is not the one we are launching into.
-            if (mLaunchRootTask == null) {
-                for (int j = 0, n = getStackCount(); j < n; ++j) {
-                    final Task tt = getStackAt(j);
-                    if (tt.mCreatedByOrganizer && tt != t) {
-                        mLaunchRootTask = tt;
-                        break;
-                    }
-                }
-            }
-            return t;
-        }
-        return mLaunchRootTask;
-    }
-
-    @VisibleForTesting
-    ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId,
-            boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
-        if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
-            throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
-                    + "activity type.");
-        }
-        if (info == null) {
-            info = new ActivityInfo();
-            info.applicationInfo = new ApplicationInfo();
-        }
-
-        // Task created by organizer are added as root.
-        Task launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode);
-        if (launchRootTask != null) {
-            // Since this stack will be put into a root task, its windowingMode will be inherited.
-            windowingMode = WINDOWING_MODE_UNDEFINED;
-        }
-
-        final ActivityStack stack = (ActivityStack) Task.create(mAtmService, stackId, activityType,
-                info, intent, createdByOrganizer);
-        if (launchRootTask != null) {
-            launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
-            if (onTop) {
-                positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */);
-            }
-        } else {
-            addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
-            stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
-                    false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
-                    true /* creating */);
-        }
-        return stack;
-    }
-
-    /**
-     * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
-     * focusable and visible stack from the top of stacks in this display.
-     */
     ActivityStack getFocusedStack() {
-        if (mPreferredTopFocusableStack != null) {
-            return mPreferredTopFocusableStack;
-        }
-
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (stack.isFocusableAndVisible()) {
-                return stack;
-            }
-        }
-
-        return null;
-    }
-
-    ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
-        final int currentWindowingMode = currentFocus != null
-                ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        ActivityStack candidate = null;
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getStackAt(i);
-            if (ignoreCurrent && stack == currentFocus) {
-                continue;
-            }
-            if (!stack.isFocusableAndVisible()) {
-                continue;
-            }
-
-            if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
-                // If the currently focused stack is in split-screen secondary we save off the
-                // top primary split-screen stack as a candidate for focus because we might
-                // prefer focus to move to an other stack to avoid primary split-screen stack
-                // overlapping with a fullscreen stack when a fullscreen stack is higher in z
-                // than the next split-screen stack. Assistant stack, I am looking at you...
-                // We only move the focus to the primary-split screen stack if there isn't a
-                // better alternative.
-                candidate = stack;
-                continue;
-            }
-            if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
-                // Use the candidate stack since we are now at the secondary split-screen.
-                return candidate;
-            }
-            return stack;
-        }
-        return candidate;
-    }
-
-    ActivityRecord getResumedActivity() {
-        final ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack == null) {
-            return null;
-        }
-        // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
-        // Check if the focused stack has the resumed activity
-        ActivityRecord resumedActivity = focusedStack.getResumedActivity();
-        if (resumedActivity == null || resumedActivity.app == null) {
-            // If there is no registered resumed activity in the stack or it is not running -
-            // try to use previously resumed one.
-            resumedActivity = focusedStack.mPausingActivity;
-            if (resumedActivity == null || resumedActivity.app == null) {
-                // If previously resumed activity doesn't work either - find the topmost running
-                // activity that can be focused.
-                resumedActivity = focusedStack.topRunningActivity(true /* focusableOnly */);
-            }
-        }
-        return resumedActivity;
-    }
-
-    ActivityStack getLastFocusedStack() {
-        return mLastFocusedStack;
-    }
-
-    boolean allResumedActivitiesComplete() {
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
-            if (r != null && !r.isState(RESUMED)) {
-                return false;
-            }
-        }
-        final ActivityStack currentFocusedStack = getFocusedStack();
-        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
-            Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
-                    + mLastFocusedStack + " to=" + currentFocusedStack);
-        }
-        mLastFocusedStack = currentFocusedStack;
-        return true;
-    }
-
-    /**
-     * Pause all activities in either all of the stacks or just the back stacks. This is done before
-     * resuming a new activity and to make sure that previously active activities are
-     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
-     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
-     * then we should explicitly pause that stack's top activity.
-     * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
-     * @param resuming The resuming activity.
-     * @return {@code true} if any activity was paused as a result of this call.
-     */
-    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
-        boolean someActivityPaused = false;
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            final ActivityRecord resumedActivity = stack.getResumedActivity();
-            if (resumedActivity != null
-                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
-                    || !stack.isTopActivityFocusable())) {
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
-                        + " mResumedActivity=" + resumedActivity);
-                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
-                        resuming);
-            }
-        }
-        return someActivityPaused;
-    }
-
-    /**
-     * Find task for putting the Activity in.
-     */
-    void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
-            RootWindowContainer.FindTaskResult result) {
-        mTmpFindTaskResult.clear();
-        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = getStackAt(stackNdx);
-            if (!r.hasCompatibleActivityType(stack) && stack.isLeafTask()) {
-                if (DEBUG_TASKS) {
-                    Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
-                }
-                continue;
-            }
-
-            mTmpFindTaskResult.process(r, stack);
-            // It is possible to have tasks in multiple stacks with the same root affinity, so
-            // we should keep looking after finding an affinity match to see if there is a
-            // better match in another stack. Also, task affinity isn't a good enough reason
-            // to target a display which isn't the source of the intent, so skip any affinity
-            // matches not on the specified display.
-            if (mTmpFindTaskResult.mRecord != null) {
-                if (mTmpFindTaskResult.mIdealMatch) {
-                    result.setTo(mTmpFindTaskResult);
-                    return;
-                } else if (isPreferredDisplay) {
-                    // Note: since the traversing through the stacks is top down, the floating
-                    // tasks should always have lower priority than any affinity-matching tasks
-                    // in the fullscreen stacks
-                    result.setTo(mTmpFindTaskResult);
-                }
-            }
-        }
+        return mTaskContainers.getFocusedStack();
     }
 
     /**
@@ -5572,249 +5068,11 @@
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
     void removeStacksInWindowingModes(int... windowingModes) {
-        if (windowingModes == null || windowingModes.length == 0) {
-            return;
-        }
-
-        // Collect the stacks that are necessary to be removed instead of performing the removal
-        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
-        // stacks reordered.
-        final ArrayList<ActivityStack> stacks = new ArrayList<>();
-        for (int j = windowingModes.length - 1; j >= 0; --j) {
-            final int windowingMode = windowingModes[j];
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                if (!stack.isActivityTypeStandardOrUndefined()) {
-                    continue;
-                }
-                if (stack.getWindowingMode() != windowingMode) {
-                    continue;
-                }
-                stacks.add(stack);
-            }
-        }
-
-        for (int i = stacks.size() - 1; i >= 0; --i) {
-            mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
-        }
+        mTaskContainers.removeStacksInWindowingModes(windowingModes);
     }
 
     void removeStacksWithActivityTypes(int... activityTypes) {
-        if (activityTypes == null || activityTypes.length == 0) {
-            return;
-        }
-
-        // Collect the stacks that are necessary to be removed instead of performing the removal
-        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
-        // stacks reordered.
-        final ArrayList<ActivityStack> stacks = new ArrayList<>();
-        for (int j = activityTypes.length - 1; j >= 0; --j) {
-            final int activityType = activityTypes[j];
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                // Collect the root tasks that are currently being organized.
-                if (stack.isOrganized()) {
-                    for (int k = stack.getChildCount() - 1; k >= 0; --k) {
-                        final ActivityStack childStack = (ActivityStack) stack.getChildAt(k);
-                        if (childStack.getActivityType() == activityType) {
-                            stacks.add(childStack);
-                        }
-                    }
-                } else if (stack.getActivityType() == activityType) {
-                    stacks.add(stack);
-                }
-            }
-        }
-
-        for (int i = stacks.size() - 1; i >= 0; --i) {
-            mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
-        }
-    }
-
-    void onSplitScreenModeDismissed() {
-        mAtmService.deferWindowLayout();
-        try {
-            mLaunchRootTask = null;
-            moveSplitScreenTasksToFullScreen();
-        } finally {
-            final ActivityStack topFullscreenStack =
-                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            final ActivityStack homeStack = getOrCreateRootHomeTask();
-            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
-                // Whenever split-screen is dismissed we want the home stack directly behind the
-                // current top fullscreen stack so it shows up when the top stack is finished.
-                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
-                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
-                // once we have that.
-                homeStack.moveToFront("onSplitScreenModeDismissed");
-                topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
-            }
-            mAtmService.continueWindowLayout();
-        }
-    }
-
-    private void moveSplitScreenTasksToFullScreen() {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTmpTasks.clear();
-        forAllTasks(task -> {
-            if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) {
-                mTmpTasks.add(task);
-            }
-        });
-
-        for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
-            final Task root = mTmpTasks.get(i);
-            for (int j = 0; j < root.getChildCount(); j++) {
-                wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */);
-            }
-        }
-        mAtmService.mWindowOrganizerController.applyTransaction(wct);
-    }
-
-    /**
-     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
-     * @param windowingMode The windowing mode we are checking support for.
-     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
-     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
-     * @param supportsFreeform If we should consider support for freeform multi-window.
-     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
-     * @param activityType The activity type under consideration.
-     * @return true if the windowing mode is supported.
-     */
-    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
-            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
-            int activityType) {
-
-        if (windowingMode == WINDOWING_MODE_UNDEFINED
-                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            return true;
-        }
-        if (!supportsMultiWindow) {
-            return false;
-        }
-
-        if (windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
-            return true;
-        }
-
-        final int displayWindowingMode = getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen
-                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
-                    // Freeform windows and split-screen windows don't mix well, so prevent
-                    // split windowing modes on freeform displays.
-                    && displayWindowingMode != WINDOWING_MODE_FREEFORM;
-        }
-
-        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
-            return false;
-        }
-
-        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
-     * display with the provided parameters.
-     *
-     * @param r The ActivityRecord in question.
-     * @param options Options to start with.
-     * @param task The task within-which the activity would start.
-     * @param activityType The type of activity to start.
-     * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
-     */
-    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable Task task, int activityType) {
-
-        // First preference if the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        // If windowing mode is unset, then next preference is the candidate task, then the
-        // activity record.
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            if (task != null) {
-                windowingMode = task.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
-                windowingMode = r.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-                // Use the display's windowing mode.
-                windowingMode = getWindowingMode();
-            }
-        }
-        windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
-        return windowingMode != WINDOWING_MODE_UNDEFINED
-                ? windowingMode : WINDOWING_MODE_FULLSCREEN;
-    }
-
-    /**
-     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
-     * on this display.
-     *
-     * @param windowingMode The windowing-mode to validate.
-     * @param r The {@link ActivityRecord} to check against.
-     * @param task The {@link Task} to check against.
-     * @param activityType An activity type.
-     * @return The provided windowingMode or the closest valid mode which is appropriate.
-     */
-    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
-            int activityType) {
-        // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
-        boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
-        boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
-        boolean supportsPip = mAtmService.mSupportsPictureInPicture;
-        if (supportsMultiWindow) {
-            if (task != null) {
-                supportsMultiWindow = task.isResizeable();
-                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
-                // TODO: Do we need to check for freeform and Pip support here?
-            } else if (r != null) {
-                supportsMultiWindow = r.isResizeable();
-                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
-                supportsFreeform = r.supportsFreeform();
-                supportsPip = r.supportsPictureInPicture();
-            }
-        }
-
-        final boolean inSplitScreenMode = isSplitScreenModeActivated();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to the display's windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            windowingMode = WINDOWING_MODE_UNDEFINED;
-        } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_UNDEFINED)
-                && supportsSplitScreen) {
-            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-
-        if (windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                supportsFreeform, supportsPip, activityType)) {
-            return windowingMode;
-        }
-        return WINDOWING_MODE_UNDEFINED;
-    }
-
-    boolean isTopStack(ActivityStack stack) {
-        return stack == getTopStack();
-    }
-
-    boolean isTopNotPinnedStack(ActivityStack stack) {
-        for (int i = getStackCount() - 1; i >= 0; --i) {
-            final ActivityStack current = getStackAt(i);
-            if (!current.inPinnedWindowingMode()) {
-                return current == stack;
-            }
-        }
-        return false;
+        mTaskContainers.removeStacksWithActivityTypes(activityTypes);
     }
 
     ActivityRecord topRunningActivity() {
@@ -5831,37 +5089,7 @@
      * @return The top running activity. {@code null} if none is available.
      */
     ActivityRecord topRunningActivity(boolean considerKeyguardState) {
-        ActivityRecord topRunning = null;
-        final ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            topRunning = focusedStack.topRunningActivity();
-        }
-
-        // Look in other focusable stacks.
-        if (topRunning == null) {
-            for (int i = getStackCount() - 1; i >= 0; --i) {
-                final ActivityStack stack = getStackAt(i);
-                // Only consider focusable stacks other than the current focused one.
-                if (stack == focusedStack || !stack.isTopActivityFocusable()) {
-                    continue;
-                }
-                topRunning = stack.topRunningActivity();
-                if (topRunning != null) {
-                    break;
-                }
-            }
-        }
-
-        // This activity can be considered the top running activity if we are not considering
-        // the locked state, the keyguard isn't locked, or we can show when locked.
-        if (topRunning != null && considerKeyguardState
-                && mRootWindowContainer.mStackSupervisor.getKeyguardController()
-                        .isKeyguardLocked()
-                && !topRunning.canShowWhenLocked()) {
-            return null;
-        }
-
-        return topRunning;
+        return mTaskContainers.topRunningActivity(considerKeyguardState);
     }
 
     boolean updateDisplayOverrideConfigurationLocked() {
@@ -6034,7 +5262,7 @@
                     // If default display is in split-window mode, set windowing mode of the stack
                     // to split-screen secondary. Otherwise, set the windowing mode to undefined by
                     // default to let stack inherited the windowing mode from the new display.
-                    final int windowingMode = toDisplay.isSplitScreenModeActivated()
+                    final int windowingMode = toDisplay.mTaskContainers.isSplitScreenModeActivated()
                             ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                             : WINDOWING_MODE_UNDEFINED;
                     stack.reparent(toDisplay, true /* onTop */);
@@ -6066,7 +5294,7 @@
         }
     }
 
-    private void releaseSelfIfNeeded() {
+    void releaseSelfIfNeeded() {
         if (!mRemoved) {
             return;
         }
@@ -6143,80 +5371,6 @@
         return (index < wc.mChildren.size()) ? (ActivityStack) wc.mChildren.get(index) : null;
     }
 
-    /**
-     * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
-     * Generally used in conjunction with {@link #moveStackBehindStack}.
-     */
-    // TODO(b/151575894): Remove special stack movement methods.
-    void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
-        if (stack.shouldBeVisible(null)) {
-            // Skip if the stack is already visible
-            return;
-        }
-
-        final boolean isRootTask = stack.isRootTask();
-        if (isRootTask) {
-            // Move the stack to the bottom to not affect the following visibility checks
-            positionStackAtBottom(stack);
-        } else {
-            stack.getParent().positionChildAt(POSITION_BOTTOM, stack, false /* includingParents */);
-        }
-
-        // Find the next position where the stack should be placed
-        final int numStacks = isRootTask ? getStackCount() : stack.getParent().getChildCount();
-        for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
-            final ActivityStack s = isRootTask ? getStackAt(stackNdx)
-                    : (ActivityStack) stack.getParent().getChildAt(stackNdx);
-            if (s == stack) {
-                continue;
-            }
-            final int winMode = s.getWindowingMode();
-            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
-                    || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-            if (s.shouldBeVisible(null) && isValidWindowingMode) {
-                // Move the provided stack to behind this stack
-                final int position = Math.max(0, stackNdx - 1);
-                if (isRootTask) {
-                    positionStackAt(stack, position);
-                } else {
-                    stack.getParent().positionChildAt(position, stack, false /*includingParents */);
-                }
-                break;
-            }
-        }
-    }
-
-    /**
-     * Moves the {@param stack} behind the given {@param behindStack} if possible. If
-     * {@param behindStack} is not currently in the display, then then the stack is moved to the
-     * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
-     */
-    void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
-        if (behindStack == null || behindStack == stack) {
-            return;
-        }
-
-        final WindowContainer parent = stack.getParent();
-        if (parent == null || parent != behindStack.getParent()) {
-            return;
-        }
-
-        // Note that positionChildAt will first remove the given stack before inserting into the
-        // list, so we need to adjust the insertion index to account for the removed index
-        // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
-        //       position internally
-        final int stackIndex = parent.mChildren.indexOf(stack);
-        final int behindStackIndex = parent.mChildren.indexOf(behindStack);
-        final int insertIndex = stackIndex <= behindStackIndex
-                ? behindStackIndex - 1 : behindStackIndex;
-        final int position = Math.max(0, insertIndex);
-        if (stack.isRootTask()) {
-            positionStackAt(stack, position);
-        } else {
-            parent.positionChildAt(position, stack, false /* includingParents */);
-        }
-    }
-
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
         for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
@@ -6226,50 +5380,6 @@
         }
     }
 
-    void moveHomeStackToFront(String reason) {
-        final ActivityStack homeStack = getOrCreateRootHomeTask();
-        if (homeStack != null) {
-            homeStack.moveToFront(reason);
-        }
-    }
-
-    /**
-     * Moves the focusable home activity to top. If there is no such activity, the home stack will
-     * still move to top.
-     */
-    void moveHomeActivityToTop(String reason) {
-        final ActivityRecord top = getHomeActivity();
-        if (top == null) {
-            moveHomeStackToFront(reason);
-            return;
-        }
-        top.moveFocusableActivityToTop(reason);
-    }
-
-    @Nullable
-    ActivityRecord getHomeActivity() {
-        return getHomeActivityForUser(mRootWindowContainer.mCurrentUser);
-    }
-
-    @Nullable
-    ActivityRecord getHomeActivityForUser(int userId) {
-        final ActivityStack homeStack = getRootHomeTask();
-        if (homeStack == null) {
-            return null;
-        }
-
-        final PooledPredicate p = PooledLambda.obtainPredicate(
-                DisplayContent::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
-                userId);
-        final ActivityRecord r = homeStack.getActivity(p);
-        p.recycle();
-        return r;
-    }
-
-    private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
-        return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
-    }
-
     boolean isSleeping() {
         return mSleeping;
     }
@@ -6300,7 +5410,7 @@
      * Notifies of a stack order change
      * @param stack The stack which triggered the order change
      */
-    private void onStackOrderChanged(ActivityStack stack) {
+    void onStackOrderChanged(ActivityStack stack) {
         for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
             mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
         }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6b39fd2..c10b8a5 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -407,10 +407,10 @@
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
             final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
-            if (!display.isSplitScreenModeActivated()) {
+            if (!display.mTaskContainers.isSplitScreenModeActivated()) {
                 return;
             }
-            display.onSplitScreenModeDismissed();
+            display.mTaskContainers.onSplitScreenModeDismissed();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 057592c..26b263e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -208,7 +208,7 @@
         try {
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
-                mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
+                mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s",
                         targetStack, mDefaultDisplay.getStackAbove(targetStack));
 
@@ -227,7 +227,7 @@
                 targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
                         mTargetActivityType);
                 targetActivity = getTargetActivity(targetStack);
-                mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
+                mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(targetStack);
                 ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved stack=%s behind stack=%s",
                         targetStack, mDefaultDisplay.getStackAbove(targetStack));
 
@@ -352,7 +352,8 @@
                     } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
                         // Restore the target stack to its previous position
                         final DisplayContent display = targetActivity.getDisplay();
-                        display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
+                        display.mTaskContainers.moveStackBehindStack(targetStack,
+                                mRestoreTargetBehindStack);
                         if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
                             final ActivityStack aboveTargetStack =
                                     mDefaultDisplay.getStackAbove(targetStack);
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 420997a..770c088 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -257,8 +257,8 @@
             }
             if (targetTask == null) {
                 if (alwaysCreateTask) {
-                    targetTask = display.getOrCreateStack(windowingMode, activityType,
-                            false /* onTop */);
+                    targetTask = display.mTaskContainers.getOrCreateStack(windowingMode,
+                            activityType, false /* onTop */);
                 } else {
                     targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/,
                             false /*toTop*/);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 76c16d4..3427035 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1371,7 +1371,8 @@
 
         final DisplayContent defaultDisplay = getDefaultDisplay();
 
-        defaultDisplay.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        defaultDisplay.mTaskContainers.getOrCreateStack(WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_HOME, ON_TOP);
         positionChildAt(POSITION_TOP, defaultDisplay, false /* includingParents */);
     }
 
@@ -1440,7 +1441,7 @@
     }
 
     ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) {
-        return getDisplayContent(DEFAULT_DISPLAY).getHomeActivityForUser(userId);
+        return getDisplayContent(DEFAULT_DISPLAY).mTaskContainers.getHomeActivityForUser(userId);
     }
 
     boolean startHomeOnAllDisplays(int userId, String reason) {
@@ -1636,7 +1637,7 @@
             displayId = DEFAULT_DISPLAY;
         }
 
-        final ActivityRecord r = getDisplayContent(displayId).getHomeActivity();
+        final ActivityRecord r = getDisplayContent(displayId).mTaskContainers.getHomeActivity();
         final String myReason = reason + " resumeHomeActivity";
 
         // Only resume home activity if isn't finishing.
@@ -1837,7 +1838,8 @@
         // focus order.
         for (int i = getChildCount() - 1; i >= 0; --i) {
             final DisplayContent display = getChildAt(i);
-            final ActivityRecord resumedActivityOnDisplay = display.getResumedActivity();
+            final ActivityRecord resumedActivityOnDisplay = display.mTaskContainers
+                    .getResumedActivity();
             if (resumedActivityOnDisplay != null) {
                 return resumedActivityOnDisplay;
             }
@@ -1970,8 +1972,8 @@
         final int focusStackId = topFocusedStack != null
                 ? topFocusedStack.getRootTaskId() : INVALID_TASK_ID;
         // We dismiss the docked stack whenever we switch users.
-        if (getDefaultDisplay().isSplitScreenModeActivated()) {
-            getDefaultDisplay().onSplitScreenModeDismissed();
+        if (getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated()) {
+            getDefaultDisplay().mTaskContainers.onSplitScreenModeDismissed();
         }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
         // also cause all tasks to be moved to the fullscreen stack at a position that is
@@ -1993,7 +1995,7 @@
         final int restoreStackId = mUserStackInFront.get(userId);
         ActivityStack stack = getStack(restoreStackId);
         if (stack == null) {
-            stack = getDefaultDisplay().getOrCreateRootHomeTask();
+            stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask();
         }
         final boolean homeInFront = stack.isActivityTypeHome();
         if (stack.isOnHomeDisplay()) {
@@ -2016,7 +2018,7 @@
     void updateUserStack(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
             if (stack == null) {
-                stack = getDefaultDisplay().getOrCreateRootHomeTask();
+                stack = getDefaultDisplay().mTaskContainers.getOrCreateRootHomeTask();
             }
 
             mUserStackInFront.put(userId, stack.getRootTaskId());
@@ -2113,8 +2115,9 @@
             } else {
                 // In the case of multiple activities, we will create a new task for it and then
                 // move the PIP activity into the task.
-                stack = display.createStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP,
-                        r.info, r.intent, false /* createdByOrganizer */);
+                stack = display.mTaskContainers.createStack(WINDOWING_MODE_PINNED,
+                        r.getActivityType(), ON_TOP, r.info, r.intent,
+                        false /* createdByOrganizer */);
 
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
@@ -2153,7 +2156,8 @@
         // Looking up task on preferred display first
         final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId);
         if (preferredDisplay != null) {
-            preferredDisplay.findTaskLocked(r, true /* isPreferredDisplay */, mTmpFindTaskResult);
+            preferredDisplay.mTaskContainers.findTaskLocked(r, true /* isPreferredDisplay */,
+                    mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
                 return mTmpFindTaskResult.mRecord;
             }
@@ -2165,7 +2169,8 @@
                 continue;
             }
 
-            display.findTaskLocked(r, false /* isPreferredDisplay */, mTmpFindTaskResult);
+            display.mTaskContainers.findTaskLocked(r, false /* isPreferredDisplay */,
+                    mTmpFindTaskResult);
             if (mTmpFindTaskResult.mIdealMatch) {
                 return mTmpFindTaskResult.mRecord;
             }
@@ -2232,7 +2237,8 @@
                     resumedOnDisplay |= result;
                     continue;
                 }
-                if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) {
+                if (display.mTaskContainers.isTopStack(stack)
+                        && topRunningActivity.isState(RESUMED)) {
                     // Kick off any lingering app transitions form the MoveTaskToFront operation,
                     // but only consider the top task and stack on that display.
                     stack.executeAppTransition(targetOptions);
@@ -2307,7 +2313,7 @@
 
     protected ActivityStack getStack(int stackId) {
         for (int i = getChildCount() - 1; i >= 0; --i) {
-            final ActivityStack stack = getChildAt(i).getStack(stackId);
+            final ActivityStack stack = getChildAt(i).mTaskContainers.getStack(stackId);
             if (stack != null) {
                 return stack;
             }
@@ -2783,7 +2789,8 @@
             }
             final DisplayContent display = getDisplayContentOrCreate(displayId);
             if (display != null) {
-                stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
+                stack = display.mTaskContainers.getOrCreateStack(r, options, candidateTask,
+                        activityType, onTop);
                 if (stack != null) {
                     return stack;
                 }
@@ -2805,8 +2812,8 @@
             display = stack.getDisplay();
             if (display != null && canLaunchOnDisplay(r, display.mDisplayId)) {
                 if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-                    windowingMode = display.resolveWindowingMode(r, options, candidateTask,
-                            activityType);
+                    windowingMode = display.mTaskContainers.resolveWindowingMode(r, options,
+                            candidateTask, activityType);
                 }
                 // Always allow organized tasks that created by organizer since the activity type
                 // of an organized task is decided by the activity type of its top child, which
@@ -2830,12 +2837,13 @@
         if (display == null || !canLaunchOnDisplay(r, display.mDisplayId)) {
             display = getDefaultDisplay();
             if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
-                windowingMode = display.resolveWindowingMode(r, options, candidateTask,
-                        activityType);
+                windowingMode = display.mTaskContainers.resolveWindowingMode(r, options,
+                        candidateTask, activityType);
             }
         }
 
-        return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
+        return display.mTaskContainers.getOrCreateStack(r, options, candidateTask, activityType,
+                onTop);
     }
 
     /** @return true if activity record is null or can be launched on provided display. */
@@ -2895,8 +2903,8 @@
             windowingMode = options != null ? options.getLaunchWindowingMode()
                     : r.getWindowingMode();
         }
-        windowingMode = displayContent.validateWindowingMode(windowingMode, r, candidateTask,
-                r.getActivityType());
+        windowingMode = displayContent.mTaskContainers.validateWindowingMode(windowingMode, r,
+                candidateTask, r.getActivityType());
 
         // Return the topmost valid stack on the display.
         for (int i = displayContent.getStackCount() - 1; i >= 0; --i) {
@@ -2984,8 +2992,8 @@
             // was on.
             preferredDisplay = getDisplayContent(currentFocus.mPrevDisplayId);
         }
-        final ActivityStack preferredFocusableStack = preferredDisplay.getNextFocusableStack(
-                currentFocus, ignoreCurrent);
+        final ActivityStack preferredFocusableStack = preferredDisplay.mTaskContainers
+                .getNextFocusableStack(currentFocus, ignoreCurrent);
         if (preferredFocusableStack != null) {
             return preferredFocusableStack;
         }
@@ -3003,8 +3011,8 @@
                 // We've already checked this one
                 continue;
             }
-            final ActivityStack nextFocusableStack = display.getNextFocusableStack(currentFocus,
-                    ignoreCurrent);
+            final ActivityStack nextFocusableStack = display.mTaskContainers
+                    .getNextFocusableStack(currentFocus, ignoreCurrent);
             if (nextFocusableStack != null) {
                 return nextFocusableStack;
             }
@@ -3416,7 +3424,8 @@
             boolean allFocusedProcessesDiffer = true;
             for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
                 final DisplayContent displayContent = getChildAt(displayNdx);
-                final ActivityRecord resumedActivity = displayContent.getResumedActivity();
+                final ActivityRecord resumedActivity = displayContent.mTaskContainers
+                        .getResumedActivity();
                 final WindowProcessController resumedActivityProcess =
                         resumedActivity == null ? null : resumedActivity.app;
 
@@ -3517,8 +3526,8 @@
                 printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep);
                 needSep = printed;
             }
-            printThisActivity(pw, displayContent.getResumedActivity(), dumpPackage, needSep,
-                    " ResumedActivity:");
+            printThisActivity(pw, displayContent.mTaskContainers.getResumedActivity(), dumpPackage,
+                    needSep, " ResumedActivity:");
         }
 
         printed |= dumpHistoryList(fd, pw, mStackSupervisor.mFinishingActivities, "  ",
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 91b4ec9..10be11a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1386,7 +1386,7 @@
         // A rootable task that is now being added to be the child of an organized task. Making
         // sure the stack references is keep updated.
         if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) {
-            mDisplayContent.addStackReferenceIfNeeded((ActivityStack) child);
+            mDisplayContent.mTaskContainers.addStackReferenceIfNeeded((ActivityStack) child);
         }
 
         // Make sure the list of display UID whitelists is updated
@@ -1432,7 +1432,7 @@
         // A rootable child task that is now being removed from an organized task. Making sure
         // the stack references is keep updated.
         if (mTaskOrganizer != null && mCreatedByOrganizer && child.asTask() != null) {
-            mDisplayContent.removeStackReferenceIfNeeded((ActivityStack) child);
+            mDisplayContent.mTaskContainers.removeStackReferenceIfNeeded((ActivityStack) child);
         }
         removeChild(child, "removeChild");
     }
diff --git a/services/core/java/com/android/server/wm/TaskContainers.java b/services/core/java/com/android/server/wm/TaskContainers.java
index a959942..540bc9b 100644
--- a/services/core/java/com/android/server/wm/TaskContainers.java
+++ b/services/core/java/com/android/server/wm/TaskContainers.java
@@ -17,23 +17,49 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
 
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
+import static com.android.server.wm.DisplayContent.alwaysCreateStack;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.RootWindowContainer.TAG_STATES;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.protolog.common.ProtoLog;
 
 import java.util.ArrayList;
@@ -76,9 +102,37 @@
     private final ArrayList<ActivityStack> mTmpNormalStacks = new ArrayList<>();
     private final ArrayList<ActivityStack> mTmpHomeStacks = new ArrayList<>();
 
+    private ArrayList<Task> mTmpTasks = new ArrayList<>();
+
+    private ActivityTaskManagerService mAtmService;
+
+    private RootWindowContainer mRootWindowContainer;
+
+    // When non-null, new tasks get put into this root task.
+    private Task mLaunchRootTask = null;
+
+    /**
+     * A focusable stack that is purposely to be positioned at the top. Although the stack may not
+     * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
+     * target stack properly when there are other focusable always-on-top stacks.
+     */
+    private ActivityStack mPreferredTopFocusableStack;
+
+    private final RootWindowContainer.FindTaskResult
+            mTmpFindTaskResult = new RootWindowContainer.FindTaskResult();
+
+    /**
+     * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
+     * stack has been resumed. If stacks are changing position this will hold the old stack until
+     * the new stack becomes resumed after which it will be set to current focused stack.
+     */
+    ActivityStack mLastFocusedStack;
+
     TaskContainers(DisplayContent displayContent, WindowManagerService service) {
         super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
         mDisplayContent = displayContent;
+        mRootWindowContainer = service.mRoot;
+        mAtmService = service.mAtmService;
     }
 
     /**
@@ -201,7 +255,7 @@
         position = findPositionForStack(position, stack, true /* adding */);
 
         super.addChild(stack, position);
-        mDisplayContent.mAtmService.updateSleepIfNeededLocked();
+        mAtmService.updateSleepIfNeededLocked();
 
         // The reparenting case is handled in WindowContainer.
         if (!stack.mReparenting) {
@@ -212,8 +266,8 @@
     @Override
     protected void removeChild(ActivityStack stack) {
         super.removeChild(stack);
-        mDisplayContent.onStackRemoved(stack);
-        mDisplayContent.mAtmService.updateSleepIfNeededLocked();
+        onStackRemoved(stack);
+        mAtmService.updateSleepIfNeededLocked();
         removeStackReferenceIfNeeded(stack);
     }
 
@@ -567,4 +621,913 @@
             mSplitScreenDividerAnchor = null;
         }
     }
+
+    void addStack(ActivityStack stack, int position) {
+        mDisplayContent.setStackOnDisplay(stack, position);
+        positionStackAt(stack, position);
+    }
+
+    void onStackRemoved(ActivityStack stack) {
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+            Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId="
+                    + mDisplayContent.mDisplayId);
+        }
+        if (mPreferredTopFocusableStack == stack) {
+            mPreferredTopFocusableStack = null;
+        }
+        mDisplayContent.releaseSelfIfNeeded();
+        mDisplayContent.onStackOrderChanged(stack);
+    }
+
+    void positionStackAt(int position, ActivityStack child, boolean includingParents) {
+        positionChildAt(position, child, includingParents);
+        mDisplayContent.layoutAndAssignWindowLayersIfNeeded();
+    }
+
+    void positionStackAtTop(ActivityStack stack, boolean includingParents) {
+        positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
+    }
+
+    void positionStackAtTop(ActivityStack stack, boolean includingParents,
+            String updateLastFocusedStackReason) {
+        positionStackAt(stack, getStackCount(), includingParents,
+                updateLastFocusedStackReason);
+    }
+
+    void positionStackAtBottom(ActivityStack stack) {
+        positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
+    }
+
+    void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
+        positionStackAt(stack, 0, false /* includingParents */,
+                updateLastFocusedStackReason);
+    }
+
+    void positionStackAt(ActivityStack stack, int position) {
+        positionStackAt(stack, position, false /* includingParents */,
+                null /* updateLastFocusedStackReason */);
+    }
+
+    void positionStackAt(ActivityStack stack, int position, boolean includingParents,
+            String updateLastFocusedStackReason) {
+        // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
+        //       the position internally, also update the logic here
+        final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
+                ? getFocusedStack() : null;
+        final boolean wasContained = getIndexOf(stack) >= 0;
+        if (mDisplayContent.mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
+            throw new IllegalStateException(
+                    "positionStackAt: Can only have one task on display=" + this);
+        }
+
+        final boolean movingToTop = wasContained && position >= getStackCount() - 1;
+        // Reset mPreferredTopFocusableStack before positioning to top or {@link
+        // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top
+        // resumed activity.
+        if (movingToTop && stack.isFocusable()) {
+            mPreferredTopFocusableStack = null;
+        }
+
+        // Since positionChildAt() is called during the creation process of pinned stacks,
+        // ActivityStack#getStack() can be null.
+        positionStackAt(position, stack, includingParents);
+
+        // The insert position may be adjusted to non-top when there is always-on-top stack. Since
+        // the original position is preferred to be top, the stack should have higher priority when
+        // we are looking for top focusable stack. The condition {@code wasContained} restricts the
+        // preferred stack is set only when moving an existing stack to top instead of adding a new
+        // stack that may be too early (e.g. in the middle of launching or reparenting).
+        if (movingToTop && stack.isFocusableAndVisible()) {
+            mPreferredTopFocusableStack = stack;
+        } else if (mPreferredTopFocusableStack == stack) {
+            mPreferredTopFocusableStack = null;
+        }
+
+        if (updateLastFocusedStackReason != null) {
+            final ActivityStack currentFocusedStack = getFocusedStack();
+            if (currentFocusedStack != prevFocusedStack) {
+                mLastFocusedStack = prevFocusedStack;
+                EventLogTags.writeWmFocusedStack(mRootWindowContainer.mCurrentUser,
+                        mDisplayContent.mDisplayId,
+                        currentFocusedStack == null ? -1 : currentFocusedStack.getRootTaskId(),
+                        mLastFocusedStack == null ? -1 : mLastFocusedStack.getRootTaskId(),
+                        updateLastFocusedStackReason);
+            }
+        }
+
+        mDisplayContent.onStackOrderChanged(stack);
+    }
+
+    ActivityStack getStack(int rootTaskId) {
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (stack.getRootTaskId() == rootTaskId) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns an existing stack compatible with the windowing mode and activity type or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean, Intent, Task, boolean)
+     */
+    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
+        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
+                null /* candidateTask */, false /* createdByOrganizer */);
+    }
+
+    /**
+     * When two level tasks are required for given windowing mode and activity type, returns an
+     * existing compatible root task or creates a new one.
+     * For one level task, the candidate task would be reused to also be the root task or create
+     * a new root task if no candidate task.
+     * @see #getStack(int, int)
+     * @see #createStack(int, int, boolean)
+     */
+    ActivityStack getOrCreateStack(int windowingMode, int activityType, boolean onTop,
+            Intent intent, Task candidateTask, boolean createdByOrganizer) {
+        if (!alwaysCreateStack(windowingMode, activityType)) {
+            ActivityStack stack = getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        } else if (candidateTask != null) {
+            final ActivityStack stack = (ActivityStack) candidateTask;
+            final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
+            if (isSplitScreenModeActivated()) {
+                final Task splitRootSecondary = getTask(t -> t.mCreatedByOrganizer && t.isRootTask()
+                        && t.inSplitScreenSecondaryWindowingMode());
+                if (stack.getParent() == null) {
+                    splitRootSecondary.addChild(stack, position);
+                } else if (stack.getParent() != splitRootSecondary) {
+                    stack.reparent(splitRootSecondary, position);
+                }
+            } else if (stack.getDisplay() != mDisplayContent || !stack.isRootTask()) {
+                if (stack.getParent() == null) {
+                    addStack(stack, position);
+                } else {
+                    stack.reparent(mDisplayContent, onTop);
+                }
+            }
+            // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
+            if (candidateTask.getWindowingMode() != windowingMode) {
+                candidateTask.setWindowingMode(windowingMode);
+            }
+            return stack;
+        }
+        return createStack(windowingMode, activityType, onTop, null /*info*/, intent,
+                createdByOrganizer);
+    }
+
+    /**
+     * Returns an existing stack compatible with the input params or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean)
+     */
+    ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
+            boolean onTop) {
+        // First preference is the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+        // Validate that our desired windowingMode will work under the current conditions.
+        // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
+        // it's display's windowing mode.
+        windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
+        return getOrCreateStack(windowingMode, activityType, onTop, null /* intent */,
+                candidateTask, false /* createdByOrganizer */);
+    }
+
+    @VisibleForTesting
+    int getNextStackId() {
+        return mAtmService.mStackSupervisor.getNextTaskIdForUser();
+    }
+
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+        return createStack(windowingMode, activityType, onTop, null /* info */, null /* intent */,
+                false /* createdByOrganizer */);
+    }
+
+    /**
+     * Creates a stack matching the input windowing mode and activity type on this display.
+     * @param windowingMode The windowing mode the stack should be created in. If
+     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+     *                      inherit its parent's windowing mode.
+     * @param activityType The activityType the stack should be created in. If
+     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @param info The started activity info.
+     * @param intent The intent that started this task.
+     * @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
+     *                          otherwise.
+     * @return The newly created stack.
+     */
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
+            Intent intent, boolean createdByOrganizer) {
+        if (mDisplayContent.mSingleTaskInstance && getStackCount() > 0) {
+            // Create stack on default display instead since this display can only contain 1 stack.
+            // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
+            // this goes away once ActivityView is no longer using virtual displays.
+            return mRootWindowContainer.getDefaultDisplay().mTaskContainers.createStack(
+                    windowingMode, activityType, onTop, info, intent, createdByOrganizer);
+        }
+
+        if (activityType == ACTIVITY_TYPE_UNDEFINED && !createdByOrganizer) {
+            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+            // anything else should be passing it in anyways...except for the task organizer.
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+
+        if (activityType != ACTIVITY_TYPE_STANDARD && activityType != ACTIVITY_TYPE_UNDEFINED) {
+            // For now there can be only one stack of a particular non-standard activity type on a
+            // display. So, get that ignoring whatever windowing mode it is currently in.
+            ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+            if (stack != null) {
+                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+                        + activityType + " already on display=" + this + ". Can't have multiple.");
+            }
+        }
+
+        if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
+                mAtmService.mSupportsSplitScreenMultiWindow,
+                mAtmService.mSupportsFreeformWindowManagement,
+                mAtmService.mSupportsPictureInPicture, activityType)) {
+            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+                    + windowingMode);
+        }
+
+        final int stackId = getNextStackId();
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent,
+                createdByOrganizer);
+    }
+
+    /** @return the root task to create the next task in. */
+    private Task updateLaunchRootTask(int windowingMode) {
+        if (!isSplitScreenWindowingMode(windowingMode)) {
+            // Only split-screen windowing modes can do this currently...
+            return null;
+        }
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final Task t = getStackAt(i);
+            if (!t.mCreatedByOrganizer || t.getRequestedOverrideWindowingMode() != windowingMode) {
+                continue;
+            }
+            // If not already set, pick a launch root which is not the one we are launching into.
+            if (mLaunchRootTask == null) {
+                for (int j = 0, n = getStackCount(); j < n; ++j) {
+                    final Task tt = getStackAt(j);
+                    if (tt.mCreatedByOrganizer && tt != t) {
+                        mLaunchRootTask = tt;
+                        break;
+                    }
+                }
+            }
+            return t;
+        }
+        return mLaunchRootTask;
+    }
+
+    @VisibleForTesting
+    ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId,
+            boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {
+        if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
+            throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
+                    + "activity type.");
+        }
+        if (info == null) {
+            info = new ActivityInfo();
+            info.applicationInfo = new ApplicationInfo();
+        }
+
+        // Task created by organizer are added as root.
+        Task launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode);
+        if (launchRootTask != null) {
+            // Since this stack will be put into a root task, its windowingMode will be inherited.
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+        }
+
+        final ActivityStack stack = (ActivityStack) Task.create(mAtmService, stackId, activityType,
+                info, intent, createdByOrganizer);
+        if (launchRootTask != null) {
+            launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
+            if (onTop) {
+                positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */);
+            }
+        } else {
+            addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
+            stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
+                    false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
+                    true /* creating */);
+        }
+        return stack;
+    }
+
+    /**
+     * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
+     * focusable and visible stack from the top of stacks in this display.
+     */
+    ActivityStack getFocusedStack() {
+        if (mPreferredTopFocusableStack != null) {
+            return mPreferredTopFocusableStack;
+        }
+
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (stack.isFocusableAndVisible()) {
+                return stack;
+            }
+        }
+
+        return null;
+    }
+
+    ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
+        final int currentWindowingMode = currentFocus != null
+                ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        ActivityStack candidate = null;
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getStackAt(i);
+            if (ignoreCurrent && stack == currentFocus) {
+                continue;
+            }
+            if (!stack.isFocusableAndVisible()) {
+                continue;
+            }
+
+            if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
+                // If the currently focused stack is in split-screen secondary we save off the
+                // top primary split-screen stack as a candidate for focus because we might
+                // prefer focus to move to an other stack to avoid primary split-screen stack
+                // overlapping with a fullscreen stack when a fullscreen stack is higher in z
+                // than the next split-screen stack. Assistant stack, I am looking at you...
+                // We only move the focus to the primary-split screen stack if there isn't a
+                // better alternative.
+                candidate = stack;
+                continue;
+            }
+            if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
+                // Use the candidate stack since we are now at the secondary split-screen.
+                return candidate;
+            }
+            return stack;
+        }
+        return candidate;
+    }
+
+    ActivityRecord getResumedActivity() {
+        final ActivityStack focusedStack = getFocusedStack();
+        if (focusedStack == null) {
+            return null;
+        }
+        // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
+        // Check if the focused stack has the resumed activity
+        ActivityRecord resumedActivity = focusedStack.getResumedActivity();
+        if (resumedActivity == null || resumedActivity.app == null) {
+            // If there is no registered resumed activity in the stack or it is not running -
+            // try to use previously resumed one.
+            resumedActivity = focusedStack.mPausingActivity;
+            if (resumedActivity == null || resumedActivity.app == null) {
+                // If previously resumed activity doesn't work either - find the topmost running
+                // activity that can be focused.
+                resumedActivity = focusedStack.topRunningActivity(true /* focusableOnly */);
+            }
+        }
+        return resumedActivity;
+    }
+
+    ActivityStack getLastFocusedStack() {
+        return mLastFocusedStack;
+    }
+
+    boolean allResumedActivitiesComplete() {
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
+            if (r != null && !r.isState(RESUMED)) {
+                return false;
+            }
+        }
+        final ActivityStack currentFocusedStack = getFocusedStack();
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+            Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
+                    + mLastFocusedStack + " to=" + currentFocusedStack);
+        }
+        mLastFocusedStack = currentFocusedStack;
+        return true;
+    }
+
+    /**
+     * Pause all activities in either all of the stacks or just the back stacks. This is done before
+     * resuming a new activity and to make sure that previously active activities are
+     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+     * then we should explicitly pause that stack's top activity.
+     * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
+     * @param resuming The resuming activity.
+     * @return {@code true} if any activity was paused as a result of this call.
+     */
+    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
+        boolean someActivityPaused = false;
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            final ActivityRecord resumedActivity = stack.getResumedActivity();
+            if (resumedActivity != null
+                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
+                    || !stack.isTopActivityFocusable())) {
+                if (DEBUG_STATES) {
+                    Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+                            + " mResumedActivity=" + resumedActivity);
+                }
+                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
+                        resuming);
+            }
+        }
+        return someActivityPaused;
+    }
+
+    /**
+     * Find task for putting the Activity in.
+     */
+    void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
+            RootWindowContainer.FindTaskResult result) {
+        mTmpFindTaskResult.clear();
+        for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getStackAt(stackNdx);
+            if (!r.hasCompatibleActivityType(stack) && stack.isLeafTask()) {
+                if (DEBUG_TASKS) {
+                    Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
+                }
+                continue;
+            }
+
+            mTmpFindTaskResult.process(r, stack);
+            // It is possible to have tasks in multiple stacks with the same root affinity, so
+            // we should keep looking after finding an affinity match to see if there is a
+            // better match in another stack. Also, task affinity isn't a good enough reason
+            // to target a display which isn't the source of the intent, so skip any affinity
+            // matches not on the specified display.
+            if (mTmpFindTaskResult.mRecord != null) {
+                if (mTmpFindTaskResult.mIdealMatch) {
+                    result.setTo(mTmpFindTaskResult);
+                    return;
+                } else if (isPreferredDisplay) {
+                    // Note: since the traversing through the stacks is top down, the floating
+                    // tasks should always have lower priority than any affinity-matching tasks
+                    // in the fullscreen stacks
+                    result.setTo(mTmpFindTaskResult);
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        if (windowingModes == null || windowingModes.length == 0) {
+            return;
+        }
+
+        // Collect the stacks that are necessary to be removed instead of performing the removal
+        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+        // stacks reordered.
+        final ArrayList<ActivityStack> stacks = new ArrayList<>();
+        for (int j = windowingModes.length - 1; j >= 0; --j) {
+            final int windowingMode = windowingModes[j];
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    continue;
+                }
+                if (stack.getWindowingMode() != windowingMode) {
+                    continue;
+                }
+                stacks.add(stack);
+            }
+        }
+
+        for (int i = stacks.size() - 1; i >= 0; --i) {
+            mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        if (activityTypes == null || activityTypes.length == 0) {
+            return;
+        }
+
+        // Collect the stacks that are necessary to be removed instead of performing the removal
+        // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+        // stacks reordered.
+        final ArrayList<ActivityStack> stacks = new ArrayList<>();
+        for (int j = activityTypes.length - 1; j >= 0; --j) {
+            final int activityType = activityTypes[j];
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                // Collect the root tasks that are currently being organized.
+                if (stack.isOrganized()) {
+                    for (int k = stack.getChildCount() - 1; k >= 0; --k) {
+                        final ActivityStack childStack = (ActivityStack) stack.getChildAt(k);
+                        if (childStack.getActivityType() == activityType) {
+                            stacks.add(childStack);
+                        }
+                    }
+                } else if (stack.getActivityType() == activityType) {
+                    stacks.add(stack);
+                }
+            }
+        }
+
+        for (int i = stacks.size() - 1; i >= 0; --i) {
+            mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
+        }
+    }
+
+    void onSplitScreenModeDismissed() {
+        mAtmService.deferWindowLayout();
+        try {
+            mLaunchRootTask = null;
+            moveSplitScreenTasksToFullScreen();
+        } finally {
+            final ActivityStack topFullscreenStack =
+                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            final ActivityStack homeStack = getOrCreateRootHomeTask();
+            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
+                // Whenever split-screen is dismissed we want the home stack directly behind the
+                // current top fullscreen stack so it shows up when the top stack is finished.
+                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+                // once we have that.
+                homeStack.moveToFront("onSplitScreenModeDismissed");
+                topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
+            }
+            mAtmService.continueWindowLayout();
+        }
+    }
+
+    private void moveSplitScreenTasksToFullScreen() {
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTmpTasks.clear();
+        forAllTasks(task -> {
+            if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) {
+                mTmpTasks.add(task);
+            }
+        });
+
+        for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
+            final Task root = mTmpTasks.get(i);
+            for (int j = 0; j < root.getChildCount(); j++) {
+                wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */);
+            }
+        }
+        mAtmService.mWindowOrganizerController.applyTransaction(wct);
+    }
+
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+            return true;
+        }
+
+        final int displayWindowingMode = getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen
+                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
+                    // Freeform windows and split-screen windows don't mix well, so prevent
+                    // split windowing modes on freeform displays.
+                    && displayWindowingMode != WINDOWING_MODE_FREEFORM;
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
+     * display with the provided parameters.
+     *
+     * @param r The ActivityRecord in question.
+     * @param options Options to start with.
+     * @param task The task within-which the activity would start.
+     * @param activityType The type of activity to start.
+     * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
+     */
+    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable Task task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Use the display's windowing mode.
+                windowingMode = getWindowingMode();
+            }
+        }
+        windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
+        return windowingMode != WINDOWING_MODE_UNDEFINED
+                ? windowingMode : WINDOWING_MODE_FULLSCREEN;
+    }
+
+    /**
+     * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+     * on this display.
+     *
+     * @param windowingMode The windowing-mode to validate.
+     * @param r The {@link ActivityRecord} to check against.
+     * @param task The {@link Task} to check against.
+     * @param activityType An activity type.
+     * @return The provided windowingMode or the closest valid mode which is appropriate.
+     */
+    int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+            int activityType) {
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
+        boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
+        boolean supportsPip = mAtmService.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        final boolean inSplitScreenMode = isSplitScreenModeActivated();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to the display's windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+        } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_UNDEFINED)
+                && supportsSplitScreen) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        return WINDOWING_MODE_UNDEFINED;
+    }
+
+    boolean isTopStack(ActivityStack stack) {
+        return stack == getTopStack();
+    }
+
+    boolean isTopNotPinnedStack(ActivityStack stack) {
+        for (int i = getStackCount() - 1; i >= 0; --i) {
+            final ActivityStack current = getStackAt(i);
+            if (!current.inPinnedWindowingMode()) {
+                return current == stack;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the top running activity in the focused stack. In the case the focused stack has no
+     * such activity, the next focusable stack on this display is returned.
+     *
+     * @param considerKeyguardState Indicates whether the locked state should be considered. if
+     *                              {@code true} and the keyguard is locked, only activities that
+     *                              can be shown on top of the keyguard will be considered.
+     * @return The top running activity. {@code null} if none is available.
+     */
+    ActivityRecord topRunningActivity(boolean considerKeyguardState) {
+        ActivityRecord topRunning = null;
+        final ActivityStack focusedStack = getFocusedStack();
+        if (focusedStack != null) {
+            topRunning = focusedStack.topRunningActivity();
+        }
+
+        // Look in other focusable stacks.
+        if (topRunning == null) {
+            for (int i = getStackCount() - 1; i >= 0; --i) {
+                final ActivityStack stack = getStackAt(i);
+                // Only consider focusable stacks other than the current focused one.
+                if (stack == focusedStack || !stack.isTopActivityFocusable()) {
+                    continue;
+                }
+                topRunning = stack.topRunningActivity();
+                if (topRunning != null) {
+                    break;
+                }
+            }
+        }
+
+        // This activity can be considered the top running activity if we are not considering
+        // the locked state, the keyguard isn't locked, or we can show when locked.
+        if (topRunning != null && considerKeyguardState
+                && mRootWindowContainer.mStackSupervisor.getKeyguardController()
+                .isKeyguardLocked()
+                && !topRunning.canShowWhenLocked()) {
+            return null;
+        }
+
+        return topRunning;
+    }
+
+    protected int getStackCount() {
+        return mChildren.size();
+    }
+
+    protected ActivityStack getStackAt(int index) {
+        return mChildren.get(index);
+    }
+
+    /**
+     * Returns the existing home stack or creates and returns a new one if it should exist for the
+     * display.
+     */
+    @Nullable
+    ActivityStack getOrCreateRootHomeTask() {
+        ActivityStack homeTask = getRootHomeTask();
+        if (homeTask == null && mDisplayContent.supportsSystemDecorations()
+                && !mDisplayContent.isUntrustedVirtualDisplay()) {
+            homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
+                    false /* onTop */);
+        }
+        return homeTask;
+    }
+
+    boolean isSplitScreenModeActivated() {
+        Task task = getRootSplitScreenPrimaryTask();
+        return task != null && task.hasChild();
+    }
+
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode.
+     * Null is no compatible stack on the display.
+     */
+    ActivityStack getTopStackInWindowingMode(int windowingMode) {
+        return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    void moveHomeStackToFront(String reason) {
+        final ActivityStack homeStack = getOrCreateRootHomeTask();
+        if (homeStack != null) {
+            homeStack.moveToFront(reason);
+        }
+    }
+
+    /**
+     * Moves the focusable home activity to top. If there is no such activity, the home stack will
+     * still move to top.
+     */
+    void moveHomeActivityToTop(String reason) {
+        final ActivityRecord top = getHomeActivity();
+        if (top == null) {
+            moveHomeStackToFront(reason);
+            return;
+        }
+        top.moveFocusableActivityToTop(reason);
+    }
+
+    @Nullable
+    ActivityRecord getHomeActivity() {
+        return getHomeActivityForUser(mRootWindowContainer.mCurrentUser);
+    }
+
+    @Nullable
+    ActivityRecord getHomeActivityForUser(int userId) {
+        final ActivityStack homeStack = getRootHomeTask();
+        if (homeStack == null) {
+            return null;
+        }
+
+        final PooledPredicate p = PooledLambda.obtainPredicate(
+                TaskContainers::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
+                userId);
+        final ActivityRecord r = homeStack.getActivity(p);
+        p.recycle();
+        return r;
+    }
+
+    private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
+        return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
+    }
+
+    /**
+     * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
+     * Generally used in conjunction with {@link #moveStackBehindStack}.
+     */
+    // TODO(b/151575894): Remove special stack movement methods.
+    void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
+        if (stack.shouldBeVisible(null)) {
+            // Skip if the stack is already visible
+            return;
+        }
+
+        final boolean isRootTask = stack.isRootTask();
+        if (isRootTask) {
+            // Move the stack to the bottom to not affect the following visibility checks
+            positionStackAtBottom(stack);
+        } else {
+            stack.getParent().positionChildAt(POSITION_BOTTOM, stack, false /* includingParents */);
+        }
+
+        // Find the next position where the stack should be placed
+        final int numStacks = isRootTask ? getStackCount() : stack.getParent().getChildCount();
+        for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+            final ActivityStack s = isRootTask ? getStackAt(stackNdx)
+                    : (ActivityStack) stack.getParent().getChildAt(stackNdx);
+            if (s == stack) {
+                continue;
+            }
+            final int winMode = s.getWindowingMode();
+            final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
+                    || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            if (s.shouldBeVisible(null) && isValidWindowingMode) {
+                // Move the provided stack to behind this stack
+                final int position = Math.max(0, stackNdx - 1);
+                if (isRootTask) {
+                    positionStackAt(stack, position);
+                } else {
+                    stack.getParent().positionChildAt(position, stack, false /*includingParents */);
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Moves the {@param stack} behind the given {@param behindStack} if possible. If
+     * {@param behindStack} is not currently in the display, then then the stack is moved to the
+     * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
+     */
+    void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
+        if (behindStack == null || behindStack == stack) {
+            return;
+        }
+
+        final WindowContainer parent = stack.getParent();
+        if (parent == null || parent != behindStack.getParent()) {
+            return;
+        }
+
+        // Note that positionChildAt will first remove the given stack before inserting into the
+        // list, so we need to adjust the insertion index to account for the removed index
+        // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
+        //       position internally
+        final int stackIndex = parent.mChildren.indexOf(stack);
+        final int behindStackIndex = parent.mChildren.indexOf(behindStack);
+        final int insertIndex = stackIndex <= behindStackIndex
+                ? behindStackIndex - 1 : behindStackIndex;
+        final int position = Math.max(0, insertIndex);
+        if (stack.isRootTask()) {
+            positionStackAt(stack, position);
+        } else {
+            parent.positionChildAt(position, stack, false /* includingParents */);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8a896f5..15b483c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -255,9 +255,9 @@
                     return null;
                 }
 
-                final Task task = display.getOrCreateStack(windowingMode, ACTIVITY_TYPE_UNDEFINED,
-                        false /* onTop */, new Intent(), null /* candidateTask */,
-                        true /* createdByOrganizer */);
+                final Task task = display.mTaskContainers.getOrCreateStack(windowingMode,
+                        ACTIVITY_TYPE_UNDEFINED, false /* onTop */, new Intent(),
+                        null /* candidateTask */, true /* createdByOrganizer */);
                 RunningTaskInfo out = task.getTaskInfo();
                 mLastSentTaskInfos.put(task, out);
                 return out;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5f21e17..7eccf08 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -252,9 +252,10 @@
                     final ActivityStack rootTask =
                             (ActivityStack) (newParent != null ? newParent : task.getRootTask());
                     if (hop.getToTop()) {
-                        as.getDisplay().positionStackAtTop(rootTask, false /* includingParents */);
+                        as.getDisplay().mTaskContainers.positionStackAtTop(rootTask,
+                                false /* includingParents */);
                     } else {
-                        as.getDisplay().positionStackAtBottom(rootTask);
+                        as.getDisplay().mTaskContainers.positionStackAtBottom(rootTask);
                     }
                 }
             } else {
@@ -264,9 +265,9 @@
             // Ugh, of course ActivityStack has its own special reorder logic...
             if (task.isRootTask()) {
                 if (hop.getToTop()) {
-                    dc.positionStackAtTop(as, false /* includingParents */);
+                    dc.mTaskContainers.positionStackAtTop(as, false /* includingParents */);
                 } else {
-                    dc.positionStackAtBottom(as);
+                    dc.mTaskContainers.positionStackAtBottom(as);
                 }
             } else {
                 task.getParent().positionChildAt(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 4f84ee1..05604b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -69,11 +69,11 @@
         stack.moveToFront("moveStackToFront");
         // After moving the stack to front, the previous focused should be the last focused.
         assertTrue(stack.isFocusedStackOnDisplay());
-        assertEquals(prevFocusedStack, display.getLastFocusedStack());
+        assertEquals(prevFocusedStack, display.mTaskContainers.getLastFocusedStack());
 
         stack.moveToBack("moveStackToBack", null /* task */);
         // After moving the stack to back, the stack should be the last focused.
-        assertEquals(stack, display.getLastFocusedStack());
+        assertEquals(stack, display.mTaskContainers.getLastFocusedStack());
     }
 
     /**
@@ -225,7 +225,7 @@
         final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
                 .setStack(alwaysOnTopStack).build();
         alwaysOnTopStack.setAlwaysOnTop(true);
-        display.positionStackAtTop(alwaysOnTopStack, false /* includingParents */);
+        display.mTaskContainers.positionStackAtTop(alwaysOnTopStack, false /* includingParents */);
         assertTrue(alwaysOnTopStack.isAlwaysOnTop());
         // Ensure always on top state is synced to the children of the stack.
         assertTrue(alwaysOnTopStack.getTopNonFinishingActivity().isAlwaysOnTop());
@@ -239,7 +239,8 @@
         final ActivityStack anotherAlwaysOnTopStack = display.createStack(
                 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         anotherAlwaysOnTopStack.setAlwaysOnTop(true);
-        display.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */);
+        display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack,
+                false /* includingParents */);
         assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
         int topPosition = display.getStackCount() - 1;
         // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the
@@ -255,7 +256,8 @@
         assertEquals(nonAlwaysOnTopStack, display.getStackAt(topPosition - 3));
 
         anotherAlwaysOnTopStack.setAlwaysOnTop(false);
-        display.positionStackAtTop(anotherAlwaysOnTopStack, false /* includingParents */);
+        display.mTaskContainers.positionStackAtTop(anotherAlwaysOnTopStack,
+                false /* includingParents */);
         assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
         // Ensure, when always on top is turned off for a stack, the stack is put just below all
         // other always on top stacks.
@@ -300,7 +302,7 @@
 
         // Reordering stacks while removing stacks.
         doAnswer(invocation -> {
-            display.positionStackAtTop(stack3, false);
+            display.mTaskContainers.positionStackAtTop(stack3, false);
             return true;
         }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 12934ee..71ca878 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -613,7 +613,7 @@
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
         assertEquals(fullscreenStack, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
     }
 
@@ -632,7 +632,7 @@
         // Ensure that we don't move the home stack if it is already behind the top fullscreen stack
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
         assertEquals(fullscreenStack, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
     }
 
@@ -651,7 +651,7 @@
         // Ensure we don't move the home stack if it is already on top
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
         assertNull(mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
     }
 
@@ -677,7 +677,7 @@
         // Ensure that we move the home stack behind the bottom most fullscreen stack, ignoring the
         // pinned stack
         assertEquals(fullscreenStack1, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(fullscreenStack2, mDefaultDisplay.getStackAbove(homeStack));
     }
 
@@ -702,7 +702,7 @@
         // Ensure that we move the home stack behind the bottom most non-translucent fullscreen
         // stack
         assertEquals(fullscreenStack1, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindBottomMostVisibleStack(homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindBottomMostVisibleStack(homeStack);
         assertEquals(fullscreenStack1, mDefaultDisplay.getStackAbove(homeStack));
     }
 
@@ -725,7 +725,7 @@
 
         // Ensure we don't move the home stack behind itself
         int homeStackIndex = mDefaultDisplay.getIndexOf(homeStack);
-        mDefaultDisplay.moveStackBehindStack(homeStack, homeStack);
+        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, homeStack);
         assertEquals(homeStackIndex, mDefaultDisplay.getIndexOf(homeStack));
     }
 
@@ -748,13 +748,13 @@
         final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
 
-        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack1);
+        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack1);
         assertEquals(fullscreenStack1, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2);
+        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2);
         assertEquals(fullscreenStack2, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack4);
+        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack4);
         assertEquals(fullscreenStack4, mDefaultDisplay.getStackAbove(homeStack));
-        mDefaultDisplay.moveStackBehindStack(homeStack, fullscreenStack2);
+        mDefaultDisplay.mTaskContainers.moveStackBehindStack(homeStack, fullscreenStack2);
         assertEquals(fullscreenStack2, mDefaultDisplay.getStackAbove(homeStack));
     }
 
@@ -845,9 +845,10 @@
             // Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
             stack = mDefaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
             if (onTop) {
-                mDefaultDisplay.positionStackAtTop(stack, false /* includingParents */);
+                mDefaultDisplay.mTaskContainers.positionStackAtTop(stack,
+                        false /* includingParents */);
             } else {
-                mDefaultDisplay.positionStackAtBottom(stack);
+                mDefaultDisplay.mTaskContainers.positionStackAtBottom(stack);
             }
         } else {
             stack = new StackBuilder(mRootWindowContainer)
@@ -1090,7 +1091,7 @@
         mDefaultDisplay.registerStackOrderChangedListener(listener);
         try {
             mStack.mReparenting = true;
-            mDefaultDisplay.addStack(mStack, 0);
+            mDefaultDisplay.mTaskContainers.addStack(mStack, 0);
         } finally {
             mDefaultDisplay.unregisterStackOrderChangedListener(listener);
         }
@@ -1105,7 +1106,7 @@
                     mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
                     true /* onTop */);
             mDefaultDisplay.registerStackOrderChangedListener(listener);
-            mDefaultDisplay.positionStackAtBottom(fullscreenStack1);
+            mDefaultDisplay.mTaskContainers.positionStackAtBottom(fullscreenStack1);
         } finally {
             mDefaultDisplay.unregisterStackOrderChangedListener(listener);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 716369d..9240b22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -462,9 +462,11 @@
         }
 
         ActivityStack build() {
-            final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
-            final ActivityStack stack = mDisplay.createStackUnchecked(mWindowingMode,
-                    mActivityType, stackId, mOnTop, mInfo, mIntent, false /* createdByOrganizer */);
+            final int stackId = mStackId >= 0 ? mStackId
+                    : mDisplay.mTaskContainers.getNextStackId();
+            final ActivityStack stack = mDisplay.mTaskContainers.createStackUnchecked(
+                    mWindowingMode, mActivityType, stackId, mOnTop, mInfo, mIntent,
+                    false /* createdByOrganizer */);
             final ActivityStackSupervisor supervisor = mRootWindowContainer.mStackSupervisor;
 
             if (mCreateActivity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 218c816..5b96c43 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1088,7 +1088,7 @@
         }
         assertNull(defaultDisplay.getRootHomeTask());
 
-        assertNotNull(defaultDisplay.getOrCreateRootHomeTask());
+        assertNotNull(defaultDisplay.mTaskContainers.getOrCreateRootHomeTask());
     }
 
     @Test
@@ -1104,7 +1104,7 @@
         }
         assertNull(display.getRootHomeTask());
 
-        assertNotNull(display.getOrCreateRootHomeTask());
+        assertNotNull(display.mTaskContainers.getOrCreateRootHomeTask());
     }
 
     @Test
@@ -1113,7 +1113,7 @@
         doReturn(false).when(display).supportsSystemDecorations();
 
         assertNull(display.getRootHomeTask());
-        assertNull(display.getOrCreateRootHomeTask());
+        assertNull(display.mTaskContainers.getOrCreateRootHomeTask());
     }
 
     @Test
@@ -1122,7 +1122,7 @@
         doReturn(true).when(display).isUntrustedVirtualDisplay();
 
         assertNull(display.getRootHomeTask());
-        assertNull(display.getOrCreateRootHomeTask());
+        assertNull(display.mTaskContainers.getOrCreateRootHomeTask());
     }
 
     private boolean isOptionsPanelAtRight(int displayId) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index b3a25302..cfb5bc7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -119,7 +119,7 @@
         final DisplayContent defaultDisplay = mRootWindowContainer.getDefaultDisplay();
         final ActivityStack homeStack =
                 defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
-        defaultDisplay.positionStackAtTop(homeStack, false /* includingParents */);
+        defaultDisplay.mTaskContainers.positionStackAtTop(homeStack, false /* includingParents */);
         ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
         if (topRunningHomeActivity == null) {
             topRunningHomeActivity = new ActivityBuilder(mService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index e841e43..dc354a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -325,7 +325,8 @@
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
                 false);
 
-        verify(display).moveHomeStackToFront(contains(reason));
+        final TaskContainers taskContainers = display.mTaskContainers;
+        verify(taskContainers).moveHomeStackToFront(contains(reason));
     }
 
     /**
@@ -352,7 +353,8 @@
         mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason,
                 false);
 
-        verify(display, never()).moveHomeStackToFront(contains(reason));
+        final TaskContainers taskContainers = display.mTaskContainers;
+        verify(taskContainers, never()).moveHomeStackToFront(contains(reason));
     }
 
     /**
@@ -367,7 +369,7 @@
                 ACTIVITY_TYPE_STANDARD, false /* onTop */));
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
         final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
-        display.positionStackAtBottom(targetStack);
+        display.mTaskContainers.positionStackAtBottom(targetStack);
 
         // Assume the stack is not at the topmost position (e.g. behind always-on-top stacks) but it
         // is the current top focused stack.
@@ -470,7 +472,7 @@
         final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
         final ActivityRecord activity = new ActivityBuilder(mService).setTask(task).build();
         activity.setState(ActivityState.RESUMED, "test");
-        display.positionStackAtBottom(targetStack);
+        display.mTaskContainers.positionStackAtBottom(targetStack);
 
         // Assume the stack is at the topmost position
         assertFalse(targetStack.isTopStackOnDisplay());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 091f493..8c8d3f1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -316,7 +316,9 @@
         // that the default display is in fullscreen mode.
         display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(display);
-        final ActivityStack homeStack = display.getStack(
+        final TaskContainers taskContainer = display.mTaskContainers;
+        spyOn(taskContainer);
+        final ActivityStack homeStack = taskContainer.getStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
         spyOn(homeStack);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index fc8cc96..18737c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -43,6 +43,7 @@
         // hard-code to FULLSCREEN for tests.
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(this);
+        spyOn(mTaskContainers);
 
         final DisplayRotation displayRotation = getDisplayRotation();
         spyOn(displayRotation);