Unify ActivityStack extend Task (84/n)

Step towards merging stacks into tasks.

Test: Existing tests pass.
Bug: 80414790
Change-Id: Ifb167129c89a3aba11796daa21a9dee7200913ca
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b596d2a..f911f2e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -745,7 +745,8 @@
             synchronized (mAtmService.mGlobalLock) {
                 Slog.w(TAG, "Activity stop timeout for " + ActivityRecord.this);
                 if (isInHistory()) {
-                    activityStopped(null /*icicle*/, null /*persistentState*/, null /*description*/);
+                    activityStopped(
+                            null /*icicle*/, null /*persistentState*/, null /*description*/);
                 }
             }
         }
@@ -1286,17 +1287,11 @@
 
         updateColorTransform();
 
-        final ActivityStack oldStack = (oldTask != null) ? oldTask.getStack() : null;
-        final ActivityStack newStack = (newTask != null) ? newTask.getStack() : null;
-        // Inform old stack (if present) of activity removal and new stack (if set) of activity
-        // addition.
-        if (oldStack != newStack) {
-            if (oldStack !=  null) {
-                oldStack.onActivityRemovedFromStack(this);
-            }
-            if (newStack !=  null) {
-                newStack.onActivityAddedToStack(this);
-            }
+        if (oldTask != null) {
+            oldTask.cleanUpActivityReferences(this);
+        }
+        if (newTask != null && isState(RESUMED)) {
+            newTask.setResumedActivity(this, "onParentChanged");
         }
     }
 
@@ -2904,8 +2899,7 @@
      * Note: Call before {@link #removeFromHistory(String)}.
      */
     void cleanUp(boolean cleanServices, boolean setState) {
-        final ActivityStack stack = getActivityStack();
-        stack.onActivityRemovedFromStack(this);
+        task.cleanUpActivityReferences(this);
 
         deferRelaunchUntilPaused = false;
         frozenBeforeDestroy = false;
@@ -5832,7 +5826,7 @@
     @Override
     boolean isWaitingForTransitionStart() {
         final DisplayContent dc = getDisplayContent();
-        return dc.mAppTransition.isTransitionSet()
+        return dc != null && dc.mAppTransition.isTransitionSet()
                 && (dc.mOpeningApps.contains(this)
                 || dc.mClosingApps.contains(this)
                 || dc.mChangingApps.contains(this));
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index f5fba8e..60e0f51e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -80,7 +80,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
@@ -119,7 +118,6 @@
 import static com.android.server.wm.StackProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import static java.lang.Integer.MAX_VALUE;
@@ -153,6 +151,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -162,7 +161,6 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.ITaskOrganizer;
-import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 
 import com.android.internal.annotations.GuardedBy;
@@ -190,7 +188,7 @@
 /**
  * State and management of a single stack of activities.
  */
-class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAnimationTarget {
+class ActivityStack extends Task implements BoundsAnimationTarget {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM;
     static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -251,33 +249,6 @@
         RESTARTING_PROCESS
     }
 
-    final ActivityTaskManagerService mAtmService;
-
-    /**
-     * When we are in the process of pausing an activity, before starting the
-     * next one, this variable holds the activity that is currently being paused.
-     */
-    ActivityRecord mPausingActivity = null;
-
-    /**
-     * This is the last activity that we put into the paused state.  This is
-     * used to determine if we need to do an activity transition while sleeping,
-     * when we normally hold the top activity paused.
-     */
-    ActivityRecord mLastPausedActivity = null;
-
-    /**
-     * Activities that specify No History must be removed once the user navigates away from them.
-     * If the device goes to sleep with such an activity in the paused state then we save it here
-     * and finish it later if another activity replaces it on wakeup.
-     */
-    ActivityRecord mLastNoHistoryActivity = null;
-
-    /**
-     * Current activity that is resumed, or null if there is none.
-     */
-    ActivityRecord mResumedActivity = null;
-
     // The topmost Activity passed to convertToTranslucent(). When non-null it means we are
     // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
     // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
@@ -294,11 +265,6 @@
     boolean mConfigWillChange;
 
     /**
-     * When set, will force the stack to report as invisible.
-     */
-    boolean mForceHidden = false;
-
-    /**
      * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
      */
     boolean mInResumeTopActivity = false;
@@ -311,18 +277,12 @@
 
     int mCurrentUser;
 
-    /** The attached Display's unique identifier, or -1 if detached */
-    private int mDisplayId;
-    // Id of the previous display the stack was on.
-    int mPrevDisplayId = INVALID_DISPLAY;
-
     /** Unique identifier */
     final int mStackId;
 
     /** For comparison with DisplayContent bounds. */
     private Rect mTmpRect = new Rect();
     private Rect mTmpRect2 = new Rect();
-    private Rect mTmpRect3 = new Rect();
 
     /** For Pinned stack controlling. */
     private Rect mTmpToBounds = new Rect();
@@ -336,9 +296,6 @@
      */
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
-    /** ActivityRecords that are exiting, but still on screen for animations. */
-    final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
-
     /** Detach this stack from its display when animation completes. */
     // TODO: maybe tie this to WindowContainer#removeChild some how...
     private boolean mDeferRemoval;
@@ -367,8 +324,6 @@
 
     Rect mPreAnimationBounds = new Rect();
 
-    private Dimmer mDimmer = new Dimmer(this);
-
     /**
      * For {@link #prepareSurfaces}.
      */
@@ -384,10 +339,6 @@
     /** List for processing through a set of activities */
     private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>();
 
-    /** Run all ActivityStacks through this */
-    protected final ActivityStackSupervisor mStackSupervisor;
-    protected final RootWindowContainer mRootWindowContainer;
-
     private boolean mTopActivityOccludesKeyguard;
     private ActivityRecord mTopDismissingKeyguardActivity;
 
@@ -638,52 +589,68 @@
         }
     }
 
-    ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor,
-            int activityType) {
-        super(supervisor.mService.mWindowManager);
-        mStackId = stackId;
-        mDockedStackMinimizeThickness =
-                supervisor.mService.mWindowManager.mContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.docked_stack_minimize_thickness);
-        EventLogTags.writeWmStackCreated(stackId);
-        mStackSupervisor = supervisor;
-        mAtmService = supervisor.mService;
-        mRootWindowContainer = mAtmService.mRootWindowContainer;
-        mHandler = new ActivityStackHandler(supervisor.mLooper);
-        mRemoteToken = new RemoteToken(this);
-        mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
-        // Set display id before setting activity and window type to make sure it won't affect
-        // stacks on a wrong display.
-        mDisplayId = display.mDisplayId;
+    ActivityStack(DisplayContent display, int id, ActivityStackSupervisor supervisor,
+            int activityType, ActivityInfo info, Intent intent) {
+        this(supervisor.mService, id, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
+                null /*taskDescription*/, null /*stack*/);
+
         setActivityType(activityType);
     }
 
-    /**
-     * This should be called when an activity in a child task changes state. This should only
-     * be called from
-     * {@link Task#onActivityStateChanged(ActivityRecord, ActivityState, String)}.
-     * @param record The {@link ActivityRecord} whose state has changed.
-     * @param state The new state.
-     * @param reason The reason for the change.
-     */
-    void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
-        if (record == mResumedActivity && state != RESUMED) {
-            setResumedActivity(null, reason + " - onActivityStateChanged");
-        }
+    ActivityStack(ActivityTaskManagerService atmService, int id, ActivityInfo info, Intent _intent,
+            IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
+            ActivityManager.TaskDescription _taskDescription, ActivityStack stack) {
+        this(atmService, id, _intent,  null /*_affinityIntent*/, null /*_affinity*/,
+                null /*_rootAffinity*/, null /*_realActivity*/, null /*_origActivity*/,
+                false /*_rootWasReset*/, false /*_autoRemoveRecents*/, false /*_askedCompatMode*/,
+                UserHandle.getUserId(info.applicationInfo.uid), 0 /*_effectiveUid*/,
+                null /*_lastDescription*/, System.currentTimeMillis(),
+                true /*neverRelinquishIdentity*/,
+                _taskDescription != null ? _taskDescription : new ActivityManager.TaskDescription(),
+                id, INVALID_TASK_ID, INVALID_TASK_ID, 0 /*taskAffiliationColor*/,
+                info.applicationInfo.uid, info.packageName, info.resizeMode,
+                info.supportsPictureInPicture(), false /*_realActivitySuspended*/,
+                false /*userSetupComplete*/, INVALID_MIN_SIZE, INVALID_MIN_SIZE, info,
+                _voiceSession, _voiceInteractor, stack);
+    }
 
-        if (state == RESUMED) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:"
-                    + reason);
-            setResumedActivity(record, reason + " - onActivityStateChanged");
-            if (record == mRootWindowContainer.getTopResumedActivity()) {
-                mAtmService.setResumedActivityUncheckLocked(record, reason);
-            }
-            mStackSupervisor.mRecentTasks.add(record.getTask());
-        }
+    ActivityStack(ActivityTaskManagerService atmService, int id, Intent _intent,
+            Intent _affinityIntent, String _affinity, String _rootAffinity,
+            ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
+            boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+            String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
+            ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
+            int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid,
+            String callingPackage, int resizeMode, boolean supportsPictureInPicture,
+            boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
+            ActivityInfo info, IVoiceInteractionSession _voiceSession,
+            IVoiceInteractor _voiceInteractor, ActivityStack stack) {
+        super(atmService, id, _intent, _affinityIntent, _affinity, _rootAffinity,
+                _realActivity, _origActivity, _rootWasReset, _autoRemoveRecents, _askedCompatMode,
+                _userId, _effectiveUid, _lastDescription, lastTimeMoved, neverRelinquishIdentity,
+                _lastTaskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
+                callingUid, callingPackage, resizeMode, supportsPictureInPicture,
+                _realActivitySuspended, userSetupComplete, minWidth, minHeight, info, _voiceSession,
+                _voiceInteractor, stack);
+
+        mStackId = mTaskId;
+        mDockedStackMinimizeThickness = mWmService.mContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_minimize_thickness);
+        EventLogTags.writeWmStackCreated(id);
+        mHandler = new ActivityStackHandler(mStackSupervisor.mLooper);
+        mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
+        // Calling Task#onConfigurationChanged() for leaf task since the ops in this method are
+        // particularly for ActivityStack, like preventing bounds changes when inheriting certain
+        // windowing mode.
+        if (!isRootTask()) {
+            super.onConfigurationChanged(newParentConfig);
+            return;
+        }
+
         final int prevWindowingMode = getWindowingMode();
         final boolean prevIsAlwaysOnTop = isAlwaysOnTop();
         final int prevRotation = getWindowConfiguration().getRotation();
@@ -801,10 +768,16 @@
 
     @Override
     public void setWindowingMode(int windowingMode) {
+        // Calling Task#setWindowingMode() for leaf task since this is the a specialization of
+        // {@link #setWindowingMode(int)} for ActivityStack.
+        if (!isRootTask()) {
+            super.setWindowingMode(windowingMode);
+            return;
+        }
+
         setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
                 false /* creating */);
-
         windowingMode = getWindowingMode();
         /*
          * Different windowing modes may be managed by different task organizers. If
@@ -893,7 +866,8 @@
                 // Looks like we can't launch in split screen mode or the stack we are launching
                 // doesn't support split-screen mode, go ahead an dismiss split-screen and display a
                 // warning toast about it.
-                mAtmService.getTaskChangeNotificationController().notifyActivityDismissingDockedStack();
+                mAtmService.getTaskChangeNotificationController()
+                        .notifyActivityDismissingDockedStack();
                 final ActivityStack primarySplitStack = display.getSplitScreenPrimaryStack();
                 primarySplitStack.setWindowingModeInSurfaceTransaction(WINDOWING_MODE_UNDEFINED,
                         false /* animate */, false /* showRecents */,
@@ -972,7 +946,7 @@
                         false /* preserveWindows */, true /* deferResume */);
             }
         } finally {
-            if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
+            if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay()
                     && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // Make sure recents stack exist when creating a dock stack as it normally needs to
                 // be on the other side of the docked stack and we make visibility decisions based
@@ -1029,10 +1003,6 @@
         return getDisplayContent();
     }
 
-    int getDisplayId() {
-        return mDisplayId;
-    }
-
     /**
      * Defers updating the bounds of the stack. If the stack was resized/repositioned while
      * deferring, the bounds will update in {@link #continueUpdateBounds()}.
@@ -1138,10 +1108,6 @@
         return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning();
     }
 
-    ActivityRecord getTopNonFinishingActivity() {
-        return getTopActivity(false /*includeFinishing*/, true /*includeOverlays*/);
-    }
-
     ActivityRecord isInStackLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         return isInStackLocked(r);
@@ -1172,11 +1138,7 @@
     }
 
     final boolean isOnHomeDisplay() {
-        return mDisplayId == DEFAULT_DISPLAY;
-    }
-
-    private boolean returnsToHomeStack() {
-        return !inMultiWindowMode() && hasChild() && getBottomMostTask().returnsToHomeStack();
+        return getDisplayId() == DEFAULT_DISPLAY;
     }
 
     void moveToFront(String reason) {
@@ -1280,11 +1242,11 @@
 
         super.switchUser(userId);
         forAllTasks((t) -> {
-            if (t.mWmService.isCurrentProfile(t.mUserId) || t.showForAllUsers()) {
+            if (t.showToCurrentUser()) {
                 mChildren.remove(t);
                 mChildren.add(t);
             }
-        });
+        }, true /* traverseTopToBottom */, this);
     }
 
     void minimalResumeActivityLocked(ActivityRecord r) {
@@ -1609,45 +1571,6 @@
         mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
     }
 
-    /**
-     * Returns true if the stack is translucent and can have other contents visible behind it if
-     * needed. A stack is considered translucent if it don't contain a visible or
-     * starting (about to be visible) activity that is fullscreen (opaque).
-     * @param starting The currently starting activity or null if there is none.
-     */
-    @VisibleForTesting
-    boolean isStackTranslucent(ActivityRecord starting) {
-        if (!isAttached() || mForceHidden) {
-            return true;
-        }
-        final PooledPredicate p = PooledLambda.obtainPredicate(ActivityStack::isOpaqueActivity,
-                PooledLambda.__(ActivityRecord.class), starting);
-        final ActivityRecord opaque = getActivity(p);
-        p.recycle();
-        return opaque == null;
-    }
-
-    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
-        if (r.finishing) {
-            // We don't factor in finishing activities when determining translucency since
-            // they will be gone soon.
-            return false;
-        }
-
-        if (!r.visibleIgnoringKeyguard && r != starting) {
-            // Also ignore invisible activities that are not the currently starting
-            // activity (about to be visible).
-            return false;
-        }
-
-        if (r.occludesParent() || r.hasWallpaper) {
-            // Stack isn't translucent if it has at least one fullscreen activity
-            // that is visible.
-            return true;
-        }
-        return false;
-    }
-
     boolean isTopStackOnDisplay() {
         final DisplayContent display = getDisplay();
         return display != null && display.isTopStack(this);
@@ -1667,15 +1590,6 @@
         return topActivity != null && topActivity.mVisibleRequested;
     }
 
-    /**
-     * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't
-     * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking
-     * the first one is ok.
-     */
-    boolean isControlledByTaskOrganizer() {
-        return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null;
-    }
-
     private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
         tr.setTaskOrganizer(organizer);
     }
@@ -1690,7 +1604,7 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityStack::transferSingleTaskToOrganizer,
                 PooledLambda.__(Task.class), organizer);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -1699,6 +1613,7 @@
      *
      * @param starting The currently starting activity or null if there is none.
      */
+    @Override
     boolean shouldBeVisible(ActivityRecord starting) {
         return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE;
     }
@@ -1756,7 +1671,7 @@
                         break;
                     }
                 }
-                if (other.isStackTranslucent(starting)) {
+                if (other.isTranslucent(starting)) {
                     // Can be visible behind a translucent fullscreen stack.
                     gotTranslucentFullscreen = true;
                     continue;
@@ -1765,7 +1680,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && !gotOpaqueSplitScreenPrimary) {
                 gotSplitScreenStack = true;
-                gotTranslucentSplitScreenPrimary = other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenPrimary = other.isTranslucent(starting);
                 gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                         && gotOpaqueSplitScreenPrimary) {
@@ -1775,7 +1690,7 @@
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     && !gotOpaqueSplitScreenSecondary) {
                 gotSplitScreenStack = true;
-                gotTranslucentSplitScreenSecondary = other.isStackTranslucent(starting);
+                gotTranslucentSplitScreenSecondary = other.isTranslucent(starting);
                 gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && gotOpaqueSplitScreenSecondary) {
@@ -1876,13 +1791,7 @@
         return inPinnedWindowingMode();
     }
 
-    @Override
-    public boolean supportsSplitScreenWindowingMode() {
-        final Task topTask = getTopMostTask();
-        return super.supportsSplitScreenWindowingMode()
-                && (topTask == null || topTask.supportsSplitScreenWindowingMode());
-    }
-
+    // TODO(NOW!)
     /**
      * Returns {@code true} if this is the top-most split-screen-primary or
      * split-screen-secondary stack, {@code false} otherwise.
@@ -1918,7 +1827,9 @@
      * @return true if {@param r} is visible taken Keyguard state into account, false otherwise
      */
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible, boolean isTop) {
-        final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY;
+        int displayId = getDisplayId();
+        if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
+
         final boolean keyguardOrAodShowing = mStackSupervisor.getKeyguardController()
                 .isKeyguardOrAodShowing(displayId);
         final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked();
@@ -2084,24 +1995,6 @@
         return result;
     }
 
-    /**
-     * Returns the currently resumed activity.
-     */
-    protected ActivityRecord getResumedActivity() {
-        return mResumedActivity;
-    }
-
-    private void setResumedActivity(ActivityRecord r, String reason) {
-        if (mResumedActivity == r) {
-            return;
-        }
-
-        if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: "
-                + mResumedActivity + " to:" + r + " reason:" + reason);
-        mResumedActivity = r;
-        mStackSupervisor.updateTopResumedActivityIfNeeded();
-    }
-
     @GuardedBy("mService")
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
         if (!mAtmService.isBooting() && !mAtmService.isBooted()) {
@@ -2412,7 +2305,7 @@
                 // result of invisible window resize.
                 // TODO: Remove this once visibilities are set correctly immediately when
                 // starting an activity.
-                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayId,
+                notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
                         true /* markFrozenIfConfigChanged */, false /* deferResume */);
             }
 
@@ -2553,7 +2446,7 @@
         ActivityOptions.abort(options);
         if (DEBUG_STATES) Slog.d(TAG_STATES,
                 "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home");
-        return mRootWindowContainer.resumeHomeActivity(prev, reason, mDisplayId);
+        return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayId());
     }
 
     void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
@@ -2827,7 +2720,7 @@
     void finishVoiceTask(IVoiceInteractionSession session) {
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::finishIfVoiceTask,
                 PooledLambda.__(Task.class), session.asBinder());
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -3020,29 +2913,6 @@
         return foundParentInTask;
     }
 
-    /**
-     * Remove any state associated with the {@link ActivityRecord}. This should be called whenever
-     * an activity moves away from the stack.
-     */
-    void onActivityRemovedFromStack(ActivityRecord r) {
-        r.removeTimeouts();
-
-        mExitingActivities.remove(r);
-
-        if (mResumedActivity != null && mResumedActivity == r) {
-            setResumedActivity(null, "onActivityRemovedFromStack");
-        }
-        if (mPausingActivity != null && mPausingActivity == r) {
-            mPausingActivity = null;
-        }
-    }
-
-    void onActivityAddedToStack(ActivityRecord r) {
-        if (r.isState(RESUMED)) {
-            setResumedActivity(r, "onActivityAddedToStack");
-        }
-    }
-
     void removeLaunchTickMessages() {
         forAllActivities(ActivityRecord::removeLaunchTickRunnable);
     }
@@ -3141,7 +3011,8 @@
                 mRootWindowContainer.resumeFocusedStacksTopActivities();
             }
             EventLogTags.writeWmTaskToFront(tr.mUserId, tr.mTaskId);
-            mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(tr.getTaskInfo());
+            mAtmService.getTaskChangeNotificationController()
+                    .notifyTaskMovedToFront(tr.getTaskInfo());
         } finally {
             getDisplay().continueUpdateImeTarget();
         }
@@ -3239,7 +3110,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class),
                     taskBounds, tempTaskInsetBounds);
-            forAllTasks(c);
+            forAllTasks(c, true /* traverseTopToBottom */, this);
             c.recycle();
 
             setBounds(bounds);
@@ -3276,7 +3147,7 @@
 
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds,
                 PooledLambda.__(Task.class), bounds);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -3292,7 +3163,7 @@
 
         final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds,
                 PooledLambda.__(Task.class), bounds);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
@@ -3384,7 +3255,6 @@
 
     private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
             boolean dumpClient, String dumpPackage, boolean needSep) {
-
         if (!hasChild()) {
             return false;
         }
@@ -3403,11 +3273,11 @@
             final ArrayList<ActivityRecord> activities = new ArrayList<>();
             // Add activities by traversing the hierarchy from bottom to top, since activities
             // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}.
-            forAllActivities((Consumer<ActivityRecord>) activities::add,
+            task.forAllActivities((Consumer<ActivityRecord>) activities::add,
                     false /* traverseTopToBottom */);
             dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
                     dumpPackage, false, null, task);
-        });
+        }, true /* traverseTopToBottom */, this);
         return true;
     }
 
@@ -3458,49 +3328,9 @@
         }
     }
 
-    /**
-     * Removes the input task from this stack.
-     *
-     * @param child to remove.
-     * @param reason for removal.
-     */
-    void removeChild(WindowContainer child, String reason) {
-        if (!mChildren.contains(child)) {
-            // Not really in this stack anymore...
-            return;
-        }
-
-        final DisplayContent display = getDisplay();
-        if (DEBUG_TASK_MOVEMENT) {
-            Slog.d(TAG_WM, "removeChild: task=" + child + " reason=" + reason);
-        }
-
-        super.removeChild(child);
-
-        EventLogTags.writeWmRemoveTask(((Task) child).mTaskId, mStackId);
-
-        if (display.isSingleTaskInstance()) {
-            mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId);
-        }
-
-        display.mDisplayContent.setLayoutNeeded();
-
-        if (!hasChild()) {
-            // Stack is now empty...
-          removeIfPossible();
-        }
-    }
-
-    @Override
-    void removeChild(WindowContainer child) {
-        removeChild(child, "removeChild");
-    }
-
-    Task createTask(int taskId, ActivityInfo info, Intent intent,
-            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
-            boolean toTop) {
-        return createTask(taskId, info, intent, voiceSession, voiceInteractor, toTop,
-                null /*activity*/, null /*source*/, null /*options*/);
+    Task createTask(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
+        return createTask(taskId, info, intent, null /*voiceSession*/, null /*voiceInteractor*/,
+                toTop, null /*activity*/, null /*source*/, null /*options*/);
     }
 
     Task createTask(int taskId, ActivityInfo info, Intent intent,
@@ -3511,7 +3341,8 @@
                 mAtmService, taskId, info, intent, voiceSession, voiceInteractor, this);
         // add the task to stack first, mTaskPositioner might need the stack association
         addChild(task, toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
-        final int displayId = mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY;
+        int displayId = getDisplayId();
+        if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
         final boolean isLockscreenShown = mAtmService.mStackSupervisor.getKeyguardController()
                 .isKeyguardOrAodShowing(displayId);
         if (!mStackSupervisor.getLaunchParamsController()
@@ -3522,14 +3353,25 @@
         return task;
     }
 
-    void addChild(final Task task, final boolean toTop, boolean showForAllUsers) {
+    void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) {
         if (isSingleTaskInstance() && hasChild()) {
             throw new IllegalStateException("Can only have one child on stack=" + this);
         }
 
-        // We only want to move the parents to the parents if we are creating this task at the
-        // top of its stack.
-        addChild(task, toTop ? MAX_VALUE : 0, showForAllUsers, toTop /*moveParents*/);
+        Task task = child.asTask();
+        try {
+
+            if (task != null) {
+                task.setForceShowForAllUsers(showForAllUsers);
+            }
+            // We only want to move the parents to the parents if we are creating this task at the
+            // top of its stack.
+            addChild(child, toTop ? MAX_VALUE : 0, toTop /*moveParents*/);
+        } finally {
+            if (task != null) {
+                task.setForceShowForAllUsers(false);
+            }
+        }
     }
 
     void positionChildAt(Task task, int position) {
@@ -3747,16 +3589,12 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor,
                 PooledLambda.__(Task.class), targetStackBounds, forceUpdate);
-        forAllTasks(c);
+        forAllTasks(c, true /* traverseTopToBottom */, this);
         c.recycle();
     }
 
-    public int getStackId() {
-        return mStackId;
-    }
-
     void prepareFreezingTaskBounds() {
-        forAllTasks(Task::prepareFreezingBounds);
+        forAllTasks(Task::prepareFreezingBounds, true /* traverseTopToBottom */, this);
     }
 
     /**
@@ -3788,7 +3626,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds,
                     PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(),
                     insetBounds, alignBottom);
-            forAllTasks(c);
+            forAllTasks(c, true /* traverseTopToBottom */, this);
             c.recycle();
         }
 
@@ -3798,7 +3636,13 @@
 
     @Override
     public int setBounds(Rect bounds) {
-        return setBounds(getRequestedOverrideBounds(), bounds);
+        // Calling Task#setBounds() for leaf task since this is the a specialization of
+        // {@link #setBounds(int)} for ActivityStack.
+        if (!isRootTask()) {
+            return super.setBounds(bounds);
+        } else {
+            return setBounds(getRequestedOverrideBounds(), bounds);
+        }
     }
 
     private int setBounds(Rect existing, Rect bounds) {
@@ -4038,30 +3882,15 @@
      * Put a Task in this stack. Used for adding only.
      * When task is added to top of the stack, the entire branch of the hierarchy (including stack
      * and display) will be brought to top.
-     * @param task The task to add.
+     * @param child The child to add.
      * @param position Target position to add the task to.
-     * @param showForAllUsers Whether to show the task regardless of the current user.
      */
-    private void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) {
-        try {
-            // Force show for all user so task can be position correctly based on which user is
-            // active. We clear the force show below.
-            task.setForceShowForAllUsers(showForAllUsers);
-            // Add child task.
-            addChild(task, null);
+    private void addChild(WindowContainer child, int position, boolean moveParents) {
+        // Add child task.
+        addChild(child, null);
 
-            // Move child to a proper position, as some restriction for position might apply.
-            positionChildAt(position, task, moveParents /* includingParents */);
-
-        } finally {
-            task.setForceShowForAllUsers(false);
-        }
-    }
-
-    @Override
-    void addChild(WindowContainer child, int position) {
-        final Task task = (Task) child;
-        addChild(task, position, task.showForAllUsers(), false /* includingParents */);
+        // Move child to a proper position, as some restriction for position might apply.
+        positionChildAt(position, child, moveParents /* includingParents */);
     }
 
     void positionChildAtTop(Task child) {
@@ -4099,29 +3928,18 @@
     }
 
     @Override
-    void positionChildAt(int position, WindowContainer child, boolean includingParents) {
-        final Task task = (Task) child;
-        final int targetPosition = findPositionForTask(task, position);
-        super.positionChildAt(targetPosition, child, includingParents);
-
-        // Log positioning.
-        if (DEBUG_TASK_MOVEMENT) {
-            Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position);
-        }
-
-        final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0;
-        EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, targetPosition);
-    }
-
-    @Override
     void onChildPositionChanged(WindowContainer child) {
         if (!mChildren.contains(child)) {
             return;
         }
 
-        final Task task = (Task) child;
-        final boolean isTop = getTopChild() == task;
-        task.updateTaskMovement(isTop);
+        final boolean isTop = getTopChild() == child;
+
+        final Task task = child.asTask();
+        if (task != null) {
+            task.updateTaskMovement(isTop);
+        }
+
         if (isTop) {
             final DisplayContent displayContent = getDisplayContent();
             displayContent.layoutAndAssignWindowLayersIfNeeded();
@@ -4129,34 +3947,17 @@
     }
 
     @Override
-    protected void onParentChanged(
-            ConfigurationContainer newParent, ConfigurationContainer oldParent) {
+    void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
         final DisplayContent display = newParent != null
                 ? ((WindowContainer) newParent).getDisplayContent() : null;
         final DisplayContent oldDisplay = oldParent != null
                 ? ((WindowContainer) oldParent).getDisplayContent() : null;
 
-        mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY;
-        mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
-
-        if (display != null) {
-            // Rotations are relative to the display. This means if there are 2 displays rotated
-            // differently (eg. 2 monitors with one landscape and one portrait), moving a stack
-            // from one to the other could look like a rotation change. To prevent this
-            // apparent rotation change (and corresponding bounds rotation), pretend like our
-            // current rotation is already the same as the new display.
-            // Note, if ActivityStack or related logic ever gets nested, this logic will need
-            // to move to onConfigurationChanged.
-            getConfiguration().windowConfiguration.setRotation(
-                    display.getWindowConfiguration().getRotation());
-        }
         super.onParentChanged(newParent, oldParent);
-        if (getParent() == null && mDisplayContent != null) {
-            EventLogTags.writeWmStackRemoved(mStackId);
-            mDisplayContent = null;
-            mWmService.mWindowPlacerLocked.requestTraversal();
-        }
-        if (display != null && inSplitScreenPrimaryWindowingMode()) {
+
+        if (display != null && inSplitScreenPrimaryWindowingMode()
+                // only do this for the base stack
+                && !newParent.inSplitScreenPrimaryWindowingMode()) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
             getStackDockedModeBounds(null /* dockedBounds */, null /* currentTempTaskBounds */,
@@ -4164,7 +3965,6 @@
             mStackSupervisor.resizeDockedStackLocked(getRequestedOverrideBounds(), mTmpRect,
                     mTmpRect2, null, null, PRESERVE_WINDOWS);
         }
-        mRootWindowContainer.updateUIDsPresentOnDisplay();
 
         // Resume next focusable stack after reparenting to another display if we aren't removing
         // the prevous display.
@@ -4178,68 +3978,6 @@
         newParent.moveStackToDisplay(this, onTop);
     }
 
-    // TODO: We should really have users as a window container in the hierarchy so that we don't
-    // have to do complicated things like we are doing in this method.
-    int findPositionForTask(Task task, int targetPosition) {
-        final boolean canShowTask = task.showToCurrentUser();
-
-        final int stackSize = mChildren.size();
-        int minPosition = 0;
-        int maxPosition = stackSize - 1;
-
-        if (canShowTask) {
-            minPosition = computeMinPosition(minPosition, stackSize);
-        } else {
-            maxPosition = computeMaxPosition(maxPosition);
-        }
-
-        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
-        if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
-            return POSITION_BOTTOM;
-        } else if (targetPosition == POSITION_TOP && maxPosition == (stackSize - 1)) {
-            return POSITION_TOP;
-        }
-        // Reset position based on minimum/maximum possible positions.
-        return Math.min(Math.max(targetPosition, minPosition), maxPosition);
-    }
-
-    /** Calculate the minimum possible position for a task that can be shown to the user.
-     *  The minimum position will be above all other tasks that can't be shown.
-     *  @param minPosition The minimum position the caller is suggesting.
-     *                  We will start adjusting up from here.
-     *  @param size The size of the current task list.
-     */
-    // TODO(task-hierarchy): Move user to their own window container.
-    private int computeMinPosition(int minPosition, int size) {
-        while (minPosition < size) {
-            final Task tmpTask = (Task) mChildren.get(minPosition);
-            final boolean canShowTmpTask = tmpTask.showToCurrentUser();
-            if (canShowTmpTask) {
-                break;
-            }
-            minPosition++;
-        }
-        return minPosition;
-    }
-
-    /** Calculate the maximum possible position for a task that can't be shown to the user.
-     *  The maximum position will be below all other tasks that can be shown.
-     *  @param maxPosition The maximum position the caller is suggesting.
-     *                  We will start adjusting down from here.
-     */
-    // TODO(task-hierarchy): Move user to their own window container.
-    private int computeMaxPosition(int maxPosition) {
-        while (maxPosition > 0) {
-            final Task tmpTask = (Task) mChildren.get(maxPosition);
-            final boolean canShowTmpTask = tmpTask.showToCurrentUser();
-            if (!canShowTmpTask) {
-                break;
-            }
-            maxPosition--;
-        }
-        return maxPosition;
-    }
-
     private void updateSurfaceBounds() {
         updateSurfaceSize(getPendingTransaction());
         updateSurfacePosition();
@@ -4456,15 +4194,6 @@
                 false /* deferResume */);
     }
 
-    @Override
-    void removeIfPossible() {
-        if (isAnimating(TRANSITION | CHILDREN)) {
-            mDeferRemoval = true;
-            return;
-        }
-        removeImmediately();
-    }
-
     /**
      * Adjusts the stack bounds if the IME is visible.
      *
@@ -4568,12 +4297,14 @@
                 t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
                 t.setWaitingForDrawnIfResizingChanged();
             }
-        });
+        }, true /* traverseTopToBottom */, this);
     }
 
     /** Resets the resizing state of all windows. */
     void endImeAdjustAnimation() {
-        forAllTasks((t) -> { t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); });
+        forAllTasks((t) -> {
+            t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+        }, true /* traverseTopToBottom */, this);
     }
 
     private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
@@ -4751,14 +4482,6 @@
         return mMinimizeAmount != 0f;
     }
 
-    /**
-     * @return {@code true} if we have a {@link Task} that is animating (currently only used for the
-     *         recents animation); {@code false} otherwise.
-     */
-    boolean isTaskAnimating() {
-        return getTask(Task::isTaskAnimating) != null;
-    }
-
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "mStackId=" + mStackId);
@@ -4792,11 +4515,6 @@
         mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
     }
 
-    @Override
-    boolean fillsParent() {
-        return matchParentBounds();
-    }
-
     String getName() {
         return toShortString();
     }
@@ -4946,18 +4664,6 @@
         }
     }
 
-    @Override
-    protected void onAnimationFinished() {
-        super.onAnimationFinished();
-        // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
-        //  identify if the callback is for launch animation finish and then calling
-        //  activity#onAnimationFinished.
-        final ActivityRecord activity = getTopMostActivity();
-        if (activity != null) {
-            activity.onAnimationFinished();
-        }
-    }
-
     /**
      * Sets the current picture-in-picture aspect ratio.
      */
@@ -5008,7 +4714,7 @@
     /** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */
     void onPipAnimationEndResize() {
         mBoundsAnimating = false;
-        forAllTasks(Task::clearPreserveNonFloatingState, false);
+        forAllTasks(Task::clearPreserveNonFloatingState, false /* traverseTopToBottom */, this);
         mWmService.requestTraversal();
     }
 
@@ -5108,24 +4814,6 @@
     }
 
     @Override
-    Dimmer getDimmer() {
-        return mDimmer;
-    }
-
-    @Override
-    void prepareSurfaces() {
-        mDimmer.resetDimStates();
-        super.prepareSurfaces();
-        getDimBounds(mTmpDimBoundsRect);
-
-        // Bounds need to be relative, as the dim layer is a child.
-        mTmpDimBoundsRect.offsetTo(0, 0);
-        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
-            scheduleAnimation();
-        }
-    }
-
-    @Override
     public boolean setPinnedStackAlpha(float alpha) {
         // Hold the lock since this is called from the BoundsAnimator running on the UiThread
         synchronized (mWmService.mGlobalLock) {
@@ -5144,49 +4832,10 @@
         return mDisplayContent.getDisplayInfo();
     }
 
-    void dim(float alpha) {
-        mDimmer.dimAbove(getPendingTransaction(), alpha);
-        scheduleAnimation();
-    }
-
-    void stopDimming() {
-        mDimmer.stopDim(getPendingTransaction());
-        scheduleAnimation();
-    }
-
     AnimatingActivityRegistry getAnimatingActivityRegistry() {
         return mAnimatingActivityRegistry;
     }
 
-    @Override
-    void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
-            Rect outSurfaceInsets) {
-        final Task task = getTopMostTask();
-        if (task != null) {
-            task.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
-        } else {
-            super.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
-        }
-    }
-
-    @Override
-    RemoteAnimationTarget createRemoteAnimationTarget(
-            RemoteAnimationController.RemoteAnimationRecord record) {
-        final Task task = getTopMostTask();
-        return task != null ? task.createRemoteAnimationTarget(record) : null;
-    }
-
-    @Override
-    public String toString() {
-        return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
-                + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
-                + " mode=" + windowingModeToString(getWindowingMode())
-                + " visible=" + shouldBeVisible(null /* starting */)
-                + " translucent=" + isStackTranslucent(null /* starting */)
-                + ", "
-                + getChildCount() + " tasks}";
-    }
-
     void executeAppTransition(ActivityOptions options) {
         getDisplay().mDisplayContent.executeAppTransition();
         ActivityOptions.abort(options);
@@ -5209,6 +4858,7 @@
         return shouldSleepActivities() || mAtmService.mShuttingDown;
     }
 
+    @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
         final long token = proto.start(fieldId);
@@ -5216,12 +4866,12 @@
         proto.write(com.android.server.am.ActivityStackProto.ID, mStackId);
 
         forAllTasks((t) -> {
-            t.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
-        });
+            t.dumpDebugInner(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
+        }, true /* traverseTopToBottom */, this);
         if (mResumedActivity != null) {
             mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
         }
-        proto.write(DISPLAY_ID, mDisplayId);
+        proto.write(DISPLAY_ID, getDisplayId());
         if (!matchParentBounds()) {
             final Rect bounds = getRequestedOverrideBounds();
             bounds.dumpDebug(proto, com.android.server.am.ActivityStackProto.BOUNDS);
@@ -5242,7 +4892,9 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(StackProto.ID, mStackId);
-        forAllTasks((t) -> { t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); });
+        forAllTasks((t) -> {
+            t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel);
+        }, true /* traverseTopToBottom */, this);
         proto.write(FILLS_PARENT, matchParentBounds());
         getRawBounds().dumpDebug(proto, StackProto.BOUNDS);
         proto.write(DEFER_REMOVAL, mDeferRemoval);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 8ef01e3..f2ce7e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -419,7 +419,7 @@
 
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class));
-            fromStack.forAllTasks(c, false);
+            fromStack.forAllTasks(c, false /* traverseTopToBottom */, fromStack);
             c.recycle();
             mToDisplay = null;
             mTopTask = null;
@@ -1724,7 +1724,7 @@
         } else {
             final PooledConsumer c = PooledLambda.obtainConsumer(
                     ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
-            stack.forAllTasks(c);
+            stack.forAllTasks(c, true /* traverseTopToBottom */, stack);
             c.recycle();
         }
     }
@@ -1849,14 +1849,14 @@
     boolean restoreRecentTaskLocked(Task task, ActivityOptions aOptions, boolean onTop) {
         final ActivityStack stack =
                 mRootWindowContainer.getLaunchStack(null, aOptions, task, onTop);
-        final ActivityStack currentStack = task.getStack();
+        final WindowContainer parent = task.getParent();
 
-        if (currentStack == stack) {
+        if (parent == stack) {
             // Nothing else to do since it is already restored in the right stack.
             return true;
         }
 
-        if (currentStack != null) {
+        if (parent != null) {
             // Task has already been restored once. Just re-parent it to the new stack.
             task.reparent(stack, POSITION_TOP, true /*moveParents*/, "restoreRecentTaskLocked");
             return true;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 47e8b87..8491bc2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -306,7 +306,7 @@
  */
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+    static final String TAG_STACK = TAG + POSTFIX_STACK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
@@ -2057,8 +2057,9 @@
     public int getDisplayId(IBinder activityToken) throws RemoteException {
         synchronized (mGlobalLock) {
             final ActivityStack stack = ActivityRecord.getStackLocked(activityToken);
-            if (stack != null && stack.getDisplayId() != INVALID_DISPLAY) {
-                return stack.getDisplayId();
+            if (stack != null) {
+                final int displayId = stack.getDisplayId();
+                return displayId != INVALID_DISPLAY ? displayId : DEFAULT_DISPLAY;
             }
             return DEFAULT_DISPLAY;
         }
@@ -3216,8 +3217,7 @@
 
                 final ActivityStack stack = r.getActivityStack();
                 final Task task = stack.createTask(
-                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent,
-                        null /* voiceSession */, null /* voiceInteractor */, !ON_TOP);
+                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), ainfo, intent, !ON_TOP);
                 if (!mRecentTasks.addToBottom(task)) {
                     // The app has too many tasks already and we can't add any more
                     stack.removeChild(task, "addAppTask");
@@ -4385,7 +4385,7 @@
 
         if (params.hasSetAspectRatio()
                 && !mWindowManager.isValidPictureInPictureAspectRatio(
-                        r.getDisplayId(), params.getAspectRatio())) {
+                        r.getDisplay(), params.getAspectRatio())) {
             final float minAspectRatio = mContext.getResources().getFloat(
                     com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
             final float maxAspectRatio = mContext.getResources().getFloat(
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 908c4f1..94c606b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -168,8 +168,10 @@
 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;
@@ -4241,7 +4243,7 @@
         ArrayList<Task> getVisibleTasks() {
             final ArrayList<Task> visibleTasks = new ArrayList<>();
             forAllTasks(task -> {
-                if (task.isVisible()) {
+                if (!task.isRootTask() && task.isVisible()) {
                     visibleTasks.add(task);
                 }
             });
@@ -4260,36 +4262,48 @@
         private void addStackReferenceIfNeeded(ActivityStack stack) {
             if (stack.isActivityTypeHome()) {
                 if (mHomeStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
-                            + mHomeStack + " already exist on display=" + this + " stack=" + stack);
-
+                    if (!stack.isDescendantOf(mHomeStack)) {
+                        throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                                + mHomeStack + " already exist on display=" + this
+                                + " stack=" + stack);
+                    }
+                } else {
+                    mHomeStack = stack;
                 }
-                mHomeStack = stack;
             } else if (stack.isActivityTypeRecents()) {
                 if (mRecentsStack != null && mRecentsStack != stack) {
-                    throw new IllegalArgumentException(
-                        "addStackReferenceIfNeeded: recents stack=" + mRecentsStack
-                            + " already exist on display=" + this + " stack=" + stack);
+                    if (!stack.isDescendantOf(mRecentsStack)) {
+                        throw new IllegalArgumentException(
+                                "addStackReferenceIfNeeded: recents stack=" + mRecentsStack
+                                        + " already exist on display=" + this + " stack=" + stack);
+                    }
+                } else {
+                    mRecentsStack = stack;
                 }
-                mRecentsStack = stack;
             }
             final int windowingMode = stack.getWindowingMode();
             if (windowingMode == WINDOWING_MODE_PINNED) {
                 if (mPinnedStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
-                            + mPinnedStack + " already exist on display=" + this
-                            + " stack=" + stack);
+                    if (!stack.isDescendantOf(mPinnedStack)) {
+                        throw new IllegalArgumentException(
+                                "addStackReferenceIfNeeded: pinned stack=" + mPinnedStack
+                                        + " already exist on display=" + this + " stack=" + stack);
+                    }
+                } else {
+                    mPinnedStack = stack;
                 }
-                mPinnedStack = stack;
             } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 if (mSplitScreenPrimaryStack != null) {
-                    throw new IllegalArgumentException("addStackReferenceIfNeeded:"
-                            + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
-                            + " already exist on display=" + this + " stack=" + stack);
+                    if (!stack.isDescendantOf(mSplitScreenPrimaryStack)) {
+                        throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                                + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+                                + " already exist on display=" + this + " stack=" + stack);
+                    }
+                } else {
+                    mSplitScreenPrimaryStack = stack;
+                    mDisplayContent.onSplitScreenModeActivated();
+                    mDividerControllerLocked.notifyDockedStackExistsChanged(true);
                 }
-                mSplitScreenPrimaryStack = stack;
-                mDisplayContent.onSplitScreenModeActivated();
-                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
             }
         }
 
@@ -5738,6 +5752,10 @@
         return mAtmService.mStackSupervisor.getNextTaskIdForUser();
     }
 
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+        return createStack(windowingMode, activityType, onTop, null /*info*/, null /*intent*/);
+    }
+
     /**
      * 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
@@ -5749,13 +5767,14 @@
      * @param onTop If true the stack will be created at the top of the display, else at the bottom.
      * @return The newly created stack.
      */
-    ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+    ActivityStack createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
+            Intent intent) {
         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);
+                    windowingMode, activityType, onTop, info, intent);
         }
 
         if (activityType == ACTIVITY_TYPE_UNDEFINED) {
@@ -5783,18 +5802,23 @@
         }
 
         final int stackId = getNextStackId();
-        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop, info, intent);
     }
 
     @VisibleForTesting
     ActivityStack createStackUnchecked(int windowingMode, int activityType,
-            int stackId, boolean onTop) {
+            int stackId, boolean onTop, ActivityInfo info, Intent intent) {
         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();
+        }
+
         final ActivityStack stack = new ActivityStack(this, stackId,
-                mRootWindowContainer.mStackSupervisor, activityType);
+                mRootWindowContainer.mStackSupervisor, activityType, info, intent);
         addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);
         stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 5df80fc..e1dfc17 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -32,7 +32,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Process.SYSTEM_UID;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
@@ -1382,7 +1381,7 @@
 
         // Ignore tasks from different displays
         // TODO (b/115289124): No Recents on non-default displays.
-        if (stack.getDisplayId() != DEFAULT_DISPLAY) {
+        if (!stack.isOnHomeDisplay()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index b255b5e..6148095 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -374,7 +374,7 @@
             final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
 	            { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
                     visibleTasks);
-            targetStack.forAllTasks(c);
+            targetStack.forAllTasks(c, true /* traverseTopToBottom */, targetStack);
             c.recycle();
         }
 
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index e310fc1..2f61ca0 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -247,8 +247,7 @@
             } else {
                 targetTask = mTargetStack.createTask(
                         atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
-                        null /* intent */, null /* voiceSession */, null /* voiceInteractor */,
-                        false /* toTop */);
+                        null /* intent */, false /* toTop */);
                 targetTask.affinityIntent = r.intent;
                 createdTasks.add(targetTask);
                 if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2f726e9..aa4255f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2132,23 +2132,13 @@
                 // We will then perform a windowing mode change for both scenarios.
                 stack = display.createStack(
                         r.getActivityStack().getRequestedOverrideWindowingMode(),
-                        r.getActivityType(), ON_TOP);
+                        r.getActivityType(), ON_TOP, r.info, r.intent);
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
 
-                // Currently, we don't support reparenting activities across tasks in two different
-                // stacks, so instead, just create a new task in the same stack, reparent the
-                // activity into that task, and then reparent the whole task to the new stack. This
-                // ensures that all the necessary work to migrate states in the old and new stacks
-                // is also done.
-                final Task newTask = task.getStack().createTask(
-                        mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
-                        r.intent, null, null, true);
+                Task newTask = stack.createTask(mStackSupervisor.getNextTaskIdForUser(r.mUserId),
+                        r.info, r.intent, true);
                 r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
-
-                // Defer resume until below, and do not schedule PiP changes until we animate below
-                newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                        DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
             }
 
             stack.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -2417,17 +2407,17 @@
         info.position = display != null ? display.getIndexOf(stack) : 0;
         info.configuration.setTo(stack.getConfiguration());
 
-        final int numTasks = stack.getChildCount();
+        final int numTasks = stack.getDescendantTaskCount();
         info.taskIds = new int[numTasks];
         info.taskNames = new String[numTasks];
         info.taskBounds = new Rect[numTasks];
         info.taskUserIds = new int[numTasks];
-        final int[] currenIndex = {0};
+        final int[] currentIndex = {0};
 
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 RootWindowContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info,
-                currenIndex);
-        stack.forAllTasks(c, false);
+                currentIndex);
+        stack.forAllTasks(c, false /* traverseTopToBottom */, stack);
         c.recycle();
 
         final ActivityRecord top = stack.topRunningActivity();
@@ -2570,7 +2560,7 @@
     }
 
     ActivityStack findStackBehind(ActivityStack stack) {
-        final DisplayContent display = getDisplayContent(stack.getDisplayId());
+        final DisplayContent display = stack.getDisplayContent();
         if (display != null) {
             for (int i = display.getStackCount() - 1; i >= 0; i--) {
                 if (display.getStackAt(i) == stack && i > 0) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 98127ab..6ebbf77 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -93,6 +93,9 @@
     }
 
     private void processTask(Task task) {
+        if (task.isRootTask()) {
+            return;
+        }
         if (task.getTopNonFinishingActivity() == null) {
             // Skip if there are no activities in the task
             return;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5cb7091..9fbef34 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -30,6 +30,8 @@
 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.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
@@ -69,6 +71,7 @@
 import static com.android.server.am.TaskRecordProto.STACK_ID;
 import static com.android.server.am.TaskRecordProto.TASK;
 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
@@ -82,6 +85,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS;
@@ -93,6 +97,7 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
@@ -121,7 +126,6 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
-import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -203,9 +207,9 @@
 
     // Current version of the task record we persist. Used to check if we need to run any upgrade
     // code.
-    private static final int PERSIST_TASK_VERSION = 1;
+    static final int PERSIST_TASK_VERSION = 1;
 
-    private static final int INVALID_MIN_SIZE = -1;
+    static final int INVALID_MIN_SIZE = -1;
 
     /**
      * The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -332,6 +336,8 @@
     final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport();
 
     final ActivityTaskManagerService mAtmService;
+    final ActivityStackSupervisor mStackSupervisor;
+    final RootWindowContainer mRootWindowContainer;
 
     /* Unique identifier for this task. */
     final int mTaskId;
@@ -346,8 +352,12 @@
     // TODO(b/119687367): This member is temporary.
     private final Rect mOverrideDisplayedBounds = new Rect();
 
+    // Id of the previous display the stack was on.
+    int mPrevDisplayId = INVALID_DISPLAY;
+
     /** ID of the display which rotation {@link #mRotation} has. */
     private int mLastRotationDisplayId = INVALID_DISPLAY;
+
     /**
      * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was
      * moved to a new display.
@@ -389,8 +399,37 @@
 
     private static Exception sTmpException;
 
+    /** ActivityRecords that are exiting, but still on screen for animations. */
+    final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
+
+    /**
+     * When we are in the process of pausing an activity, before starting the
+     * next one, this variable holds the activity that is currently being paused.
+     */
+    ActivityRecord mPausingActivity = null;
+
+    /**
+     * This is the last activity that we put into the paused state.  This is
+     * used to determine if we need to do an activity transition while sleeping,
+     * when we normally hold the top activity paused.
+     */
+    ActivityRecord mLastPausedActivity = null;
+
+    /**
+     * Activities that specify No History must be removed once the user navigates away from them.
+     * If the device goes to sleep with such an activity in the paused state then we save it here
+     * and finish it later if another activity replaces it on wakeup.
+     */
+    ActivityRecord mLastNoHistoryActivity = null;
+
+    /** Current activity that is resumed, or null if there is none. */
+    ActivityRecord mResumedActivity = null;
+
     private boolean mForceShowForAllUsers;
 
+    /** When set, will force the task to report as invisible. */
+    boolean mForceHidden = false;
+
     private final FindRootHelper mFindRootHelper = new FindRootHelper();
     private class FindRootHelper {
         private ActivityRecord mRoot;
@@ -489,6 +528,8 @@
 
         EventLogTags.writeWmTaskCreated(_taskId, stack != null ? stack.mStackId : INVALID_STACK_ID);
         mAtmService = atmService;
+        mStackSupervisor = atmService.mStackSupervisor;
+        mRootWindowContainer = mAtmService.mRootWindowContainer;
         mTaskId = _taskId;
         mUserId = _userId;
         mResizeMode = resizeMode;
@@ -552,7 +593,7 @@
         if (autoRemoveFromRecents() || isVoiceSession) {
             // Task creator asked to remove this when done, or this task was a voice
             // interaction, so it should not remain on the recent tasks list.
-            mAtmService.mStackSupervisor.mRecentTasks.remove(this);
+            mStackSupervisor.mRecentTasks.remove(this);
         }
 
         removeIfPossible();
@@ -561,13 +602,18 @@
     @VisibleForTesting
     @Override
     void removeIfPossible() {
-        mAtmService.getLockTaskController().clearLockedTask(this);
+        final boolean isRootTask = isRootTask();
+        if (!isRootTask) {
+            mAtmService.getLockTaskController().clearLockedTask(this);
+        }
         if (shouldDeferRemoval()) {
             if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
             return;
         }
         removeImmediately();
-        mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
+        if (!isRootTask) {
+            mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
+        }
     }
 
     void setResizeMode(int resizeMode) {
@@ -575,8 +621,8 @@
             return;
         }
         mResizeMode = resizeMode;
-        mAtmService.mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-        mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities();
+        mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+        mRootWindowContainer.resumeFocusedStacksTopActivities();
         updateTaskDescription();
     }
 
@@ -593,7 +639,7 @@
                 setBounds(bounds);
                 if (!inFreeformWindowingMode()) {
                     // re-restore the task so it can have the proper stack association.
-                    mAtmService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+                    mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
                 }
                 return true;
             }
@@ -629,10 +675,9 @@
                     // this won't cause tons of irrelevant windows being preserved because only
                     // activities in this task may experience a bounds change. Configs for other
                     // activities stay the same.
-                    mAtmService.mRootWindowContainer.ensureActivitiesVisible(r, 0,
-                            preserveWindow);
+                    mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
                     if (!kept) {
-                        mAtmService.mRootWindowContainer.resumeFocusedStacksTopActivities();
+                        mRootWindowContainer.resumeFocusedStacksTopActivities();
                     }
                 }
             }
@@ -696,8 +741,8 @@
     boolean reparent(ActivityStack preferredStack, int position,
             @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
             boolean schedulePictureInPictureModeChange, String reason) {
-        final ActivityStackSupervisor supervisor = mAtmService.mStackSupervisor;
-        final RootWindowContainer root = mAtmService.mRootWindowContainer;
+        final ActivityStackSupervisor supervisor = mStackSupervisor;
+        final RootWindowContainer root = mRootWindowContainer;
         final WindowManagerService windowManager = mAtmService.mWindowManager;
         final ActivityStack sourceStack = getStack();
         final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
@@ -766,7 +811,7 @@
                         wasPaused, reason);
             }
             if (!animate) {
-                mAtmService.mStackSupervisor.mNoAnimActivities.add(topActivity);
+                mStackSupervisor.mNoAnimActivities.add(topActivity);
             }
 
             // We might trigger a configuration change. Save the current task bounds for freezing.
@@ -785,7 +830,7 @@
             } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
-                    mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
+                    mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
                     bounds = configBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
@@ -793,7 +838,7 @@
                 if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
                     // Move recents to front so it is not behind home stack when going into docked
                     // mode
-                    mAtmService.mStackSupervisor.moveRecentsStackToFront(reason);
+                    mStackSupervisor.moveRecentsStackToFront(reason);
                 }
                 kept = resize(toStack.getRequestedOverrideBounds(), RESIZE_MODE_SYSTEM,
                         !mightReplaceWindow, deferResume);
@@ -859,6 +904,14 @@
         mCallingPackage = r.launchedFromPackage;
         setIntent(r.intent, r.info);
         setLockTaskAuth(r);
+
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Task t = parent.asTask();
+            if (t != null) {
+                t.setIntent(r);
+            }
+        }
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
@@ -972,8 +1025,13 @@
     }
 
     boolean returnsToHomeStack() {
-        final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
-        return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+        if (inMultiWindowMode() || !hasChild()) return false;
+        if (intent != null) {
+            final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+            return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+        }
+        final Task bottomTask = getBottomMostTask();
+        return bottomTask != this && bottomTask.returnsToHomeStack();
     }
 
     void setPrevAffiliate(Task prevAffiliate) {
@@ -988,37 +1046,72 @@
 
     @Override
     void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
-        final ActivityStack oldStack = ((ActivityStack) oldParent);
-        final ActivityStack newStack = ((ActivityStack) newParent);
+        final DisplayContent display = newParent != null
+                ? ((WindowContainer) newParent).getDisplayContent() : null;
+        final DisplayContent oldDisplay = oldParent != null
+                ? ((WindowContainer) oldParent).getDisplayContent() : null;
+
+        mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
 
         // Task is going to be removed, clean it up before detaching from hierarchy.
         if (oldParent != null && newParent == null) {
             cleanUpResourcesForDestroy();
         }
 
+        if (display != null) {
+            // TODO(NOW!): Chat with the erosky@ of this code to see if this really makes sense here...
+            // Rotations are relative to the display. This means if there are 2 displays rotated
+            // differently (eg. 2 monitors with one landscape and one portrait), moving a stack
+            // from one to the other could look like a rotation change. To prevent this
+            // apparent rotation change (and corresponding bounds rotation), pretend like our
+            // current rotation is already the same as the new display.
+            // Note, if ActivityStack or related logic ever gets nested, this logic will need
+            // to move to onConfigurationChanged.
+            getConfiguration().windowConfiguration.setRotation(
+                    display.getWindowConfiguration().getRotation());
+        }
+
         super.onParentChanged(newParent, oldParent);
 
-        if (oldStack != null) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    ActivityStack::onActivityRemovedFromStack, oldStack,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(c);
-            c.recycle();
+        // TODO(NOW): The check for null display content and setting it to null doesn't really
+        //  make sense here...
 
-            if (oldStack.inPinnedWindowingMode()
-                    && (newStack == null || !newStack.inPinnedWindowingMode())) {
+        // TODO(stack-merge): This is mostly taking care of the case where the stask is removing from
+        // the display, so we should probably consolidate it there instead.
+
+        if (getParent() == null && mDisplayContent != null) {
+            EventLogTags.writeWmStackRemoved(getStackId());
+            mDisplayContent = null;
+            mWmService.mWindowPlacerLocked.requestTraversal();
+        }
+
+        if (oldParent != null) {
+            final Task oldParentTask = ((WindowContainer) oldParent).asTask();
+            if (oldParentTask != null) {
+                final PooledConsumer c = PooledLambda.obtainConsumer(
+                        Task::cleanUpActivityReferences, oldParentTask,
+                        PooledLambda.__(ActivityRecord.class));
+                forAllActivities(c);
+                c.recycle();
+            }
+
+            if (oldParent.inPinnedWindowingMode()
+                    && (newParent == null || !newParent.inPinnedWindowingMode())) {
                 // Notify if a task from the pinned stack is being removed
                 // (or moved depending on the mode).
                 mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned();
             }
         }
 
-        if (newStack != null) {
-            final PooledConsumer c = PooledLambda.obtainConsumer(
-                    ActivityStack::onActivityAddedToStack, newStack,
-                    PooledLambda.__(ActivityRecord.class));
-            forAllActivities(c);
-            c.recycle();
+        if (newParent != null) {
+            final Task newParentTask = ((WindowContainer) newParent).asTask();
+            if (newParentTask != null) {
+                final ActivityRecord top = newParentTask.getTopNonFinishingActivity(
+                        false /* includeOverlays */);
+                if (top != null && top.isState(RESUMED)) {
+                    newParentTask.setResumedActivity(top, "addedToTask");
+                }
+            }
 
             // TODO: Ensure that this is actually necessary here
             // Notify the voice session if required
@@ -1049,7 +1142,41 @@
             forceWindowsScaleable(false /* force */);
         }
 
-        mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay();
+        mRootWindowContainer.updateUIDsPresentOnDisplay();
+    }
+
+    void cleanUpActivityReferences(ActivityRecord r) {
+        final WindowContainer parent = getParent();
+        if (parent != null && parent.asTask() != null) {
+            parent.asTask().cleanUpActivityReferences(r);
+            return;
+        }
+        r.removeTimeouts();
+        mExitingActivities.remove(r);
+
+        if (mResumedActivity != null && mResumedActivity == r) {
+            setResumedActivity(null, "cleanUpActivityReferences");
+        }
+        if (mPausingActivity != null && mPausingActivity == r) {
+            mPausingActivity = null;
+        }
+    }
+
+    /** @return the currently resumed activity. */
+    ActivityRecord getResumedActivity() {
+        return mResumedActivity;
+    }
+
+    void setResumedActivity(ActivityRecord r, String reason) {
+        if (mResumedActivity == r) {
+            return;
+        }
+
+        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) Slog.d(TAG_STACK,
+                "setResumedActivity stack:" + this + " + from: "
+                + mResumedActivity + " to:" + r + " reason:" + reason);
+        mResumedActivity = r;
+        mStackSupervisor.updateTopResumedActivityIfNeeded();
     }
 
     void updateTaskMovement(boolean toFront) {
@@ -1062,7 +1189,7 @@
                 mLastTimeMoved *= -1;
             }
         }
-        mAtmService.mRootWindowContainer.invalidateTaskLayers();
+        mRootWindowContainer.invalidateTaskLayers();
     }
 
     // Close up recents linked list.
@@ -1115,7 +1242,11 @@
 
     /** Returns the intent for the root activity for this task */
     Intent getBaseIntent() {
-        return intent != null ? intent : affinityIntent;
+        if (intent != null) return intent;
+        if (affinityIntent != null) return affinityIntent;
+        // Probably a task that contains other tasks, so return the intent for the top task?
+        final Task topTask = getTopMostTask();
+        return topTask != null ? topTask.getBaseIntent() : null;
     }
 
     /** Returns the first non-finishing activity from the bottom. */
@@ -1200,11 +1331,18 @@
         // If this task had any child before we added this one.
         boolean hadChild = hasChild();
 
-        final ActivityRecord r = (ActivityRecord) child;
-        index = getAdjustedAddPosition(r, index);
-        super.addChild(r, index);
+        index = getAdjustedChildPosition(child, index);
+        super.addChild(child, index);
 
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addChild: %s at top.", this);
+
+        // Make sure the list of display UID whitelists is updated
+        // now that this record is in a new task.
+        mRootWindowContainer.updateUIDsPresentOnDisplay();
+
+        final ActivityRecord r = child.asActivityRecord();
+        if (r == null) return;
+
         r.inHistory = true;
 
         // Only set this based on the first activity
@@ -1229,10 +1367,6 @@
         }
 
         updateEffectiveIntent();
-
-        // Make sure the list of display UID whitelists is updated
-        // now that this record is in a new task.
-        mAtmService.mRootWindowContainer.updateUIDsPresentOnDisplay();
     }
 
     void addChild(ActivityRecord r) {
@@ -1240,12 +1374,19 @@
     }
 
     @Override
-    void removeChild(WindowContainer r) {
+    void removeChild(WindowContainer child) {
+        removeChild(child, "removeChild");
+    }
+
+    void removeChild(WindowContainer r, String reason) {
         if (!mChildren.contains(r)) {
             Slog.e(TAG, "removeChild: r=" + r + " not found in t=" + this);
             return;
         }
 
+        if (DEBUG_TASK_MOVEMENT) {
+            Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason);
+        }
         super.removeChild(r);
 
         if (inPinnedWindowingMode()) {
@@ -1255,7 +1396,15 @@
             mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
         }
 
-        final String reason = "removeChild";
+        final boolean isRootTask = isRootTask();
+        if (isRootTask) {
+            final DisplayContent display = getDisplayContent();
+            if (display.isSingleTaskInstance()) {
+                mAtmService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+            }
+            display.mDisplayContent.setLayoutNeeded();
+        }
+
         if (hasChild()) {
             updateEffectiveIntent();
 
@@ -1270,12 +1419,14 @@
                 // work.
                 // TODO: If the callers to removeChild() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeChild()
-                mAtmService.mStackSupervisor.removeTask(this, false /* killProcess */,
+                mStackSupervisor.removeTask(this, false /* killProcess */,
                         !REMOVE_FROM_RECENTS, reason);
             }
         } else if (!mReuseTask) {
             // Remove entire task if it doesn't have any activity left and it isn't marked for reuse
-            getStack().removeChild(this, reason);
+            if (!isRootTask) {
+                getStack().removeChild(this, reason);
+            }
             EventLogTags.writeWmTaskRemoved(mTaskId,
                     "removeChild: last r=" + r + " in t=" + this);
             removeIfPossible();
@@ -1446,11 +1597,15 @@
 
     @Override
     public boolean supportsSplitScreenWindowingMode() {
+        final Task topTask = getTopMostTask();
+        return super.supportsSplitScreenWindowingMode()
+                && (topTask == null || topTask.supportsSplitScreenWindowingModeInner());
+    }
+
+    private boolean supportsSplitScreenWindowingModeInner() {
         // A task can not be docked even if it is considered resizeable because it only supports
         // picture-in-picture mode but has a non-resizeable resizeMode
         return super.supportsSplitScreenWindowingMode()
-                // TODO(task-group): Probably makes sense to move this and associated code into
-                // WindowContainer so it affects every node.
                 && mAtmService.mSupportsSplitScreenMultiWindow
                 && (mAtmService.mForceResizableActivities
                         || (isResizeable(false /* checkSupportsPip */)
@@ -1465,7 +1620,7 @@
      *         secondary display.
      */
     boolean canBeLaunchedOnDisplay(int displayId) {
-        return mAtmService.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
+        return mStackSupervisor.canPlaceEntityOnDisplay(displayId,
                 -1 /* don't check PID */, -1 /* don't check UID */, null /* activityInfo */);
     }
 
@@ -1535,6 +1690,14 @@
         }
         mAtmService.getTaskChangeNotificationController().notifyTaskDescriptionChanged(
                 getTaskInfo());
+
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            final Task t = parent.asTask();
+            if (t != null) {
+                t.updateTaskDescription();
+            }
+        }
     }
 
     private static boolean setTaskDescriptionFromActivityAboveRoot(
@@ -1577,9 +1740,11 @@
     @VisibleForTesting
     void updateEffectiveIntent() {
         final ActivityRecord root = getRootActivity(true /*setToBottomIfNone*/);
-        setIntent(root);
-        // Update the task description when the activities change
-        updateTaskDescription();
+        if (root != null) {
+            setIntent(root);
+            // Update the task description when the activities change
+            updateTaskDescription();
+        }
     }
 
     void adjustForMinimalTaskDimensions(Rect bounds, Rect previousBounds) {
@@ -1593,9 +1758,8 @@
         // If the task has no requested minimal size, we'd like to enforce a minimal size
         // so that the user can not render the task too small to manipulate. We don't need
         // to do this for the pinned stack as the bounds are controlled by the system.
-        if (!inPinnedWindowingMode() && getDisplayContent() != null) {
-            final int defaultMinSizeDp =
-                    mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+        if (!inPinnedWindowingMode() && getStack() != null) {
+            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
             final DisplayContent display = getDisplayContent();
             final float density =
                     (float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT;
@@ -1659,10 +1823,25 @@
      * @param reason The reason for the change.
      */
     void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
-        final ActivityStack parent = getStack();
+        final Task parentTask = getParent().asTask();
+        if (parentTask != null) {
+            parentTask.onActivityStateChanged(record, state, reason);
+            return;
+        }
 
-        if (parent != null) {
-            parent.onActivityStateChanged(record, state, reason);
+        if (record == mResumedActivity && state != RESUMED) {
+            setResumedActivity(null, reason + " - onActivityStateChanged");
+        }
+
+        if (state == RESUMED) {
+            if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+                Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason);
+            }
+            setResumedActivity(record, reason + " - onActivityStateChanged");
+            if (record == mRootWindowContainer.getTopResumedActivity()) {
+                mAtmService.setResumedActivityUncheckLocked(record, reason);
+            }
+            mStackSupervisor.mRecentTasks.add(record.getTask());
         }
     }
 
@@ -1684,7 +1863,7 @@
         final boolean wasInMultiWindowMode = inMultiWindowMode();
         super.onConfigurationChanged(newParentConfig);
         if (wasInMultiWindowMode != inMultiWindowMode()) {
-            mAtmService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
+            mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
 
         // If the configuration supports persistent bounds (eg. Freeform), keep track of the
@@ -1725,7 +1904,7 @@
         }
 
         // Saves the new state so that we can launch the activity at the same location.
-        mAtmService.mStackSupervisor.mLaunchParamsPersister.saveTask(this);
+        mStackSupervisor.mLaunchParamsPersister.saveTask(this);
     }
 
     /**
@@ -1987,6 +2166,10 @@
 
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
+        if (isRootTask()) {
+            super.resolveOverrideConfiguration(newParentConfig);
+            return;
+        }
         mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
         super.resolveOverrideConfiguration(newParentConfig);
         int windowingMode =
@@ -2011,12 +2194,12 @@
             final Rect parentBounds =
                     new Rect(newParentConfig.windowConfiguration.getBounds());
             final DisplayContent display = getDisplayContent();
-            if (display != null && display.mDisplayContent != null) {
+            if (display != null) {
                 // If a freeform window moves below system bar, there is no way to move it again
                 // by touch. Because its caption is covered by system bar. So we exclude them
                 // from stack bounds. and then caption will be shown inside stable area.
                 final Rect stableBounds = new Rect();
-                display.mDisplayContent.getStableRect(stableBounds);
+                display.getStableRect(stableBounds);
                 parentBounds.intersect(stableBounds);
             }
 
@@ -2091,6 +2274,7 @@
      * input stack. */
     void updateOverrideConfigurationForStack(ActivityStack inStack) {
         final ActivityStack stack = getStack();
+
         if (stack != null && stack == inStack) {
             return;
         }
@@ -2106,7 +2290,7 @@
             if (mLastNonFullscreenBounds != null) {
                 setBounds(mLastNonFullscreenBounds);
             } else {
-                mAtmService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
+                mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
             }
         } else {
             setBounds(inStack.getRequestedOverrideBounds());
@@ -2149,7 +2333,10 @@
 
     @Override
     DisplayContent getDisplayContent() {
-        return getStack() != null ? getStack().getDisplayContent() : null;
+        // TODO: Why aren't we just using our own display content vs. parent's???
+        final ActivityStack stack = getStack();
+        return stack != null && stack != this
+                ? stack.getDisplayContent() : super.getDisplayContent();
     }
 
     int getDisplayId() {
@@ -2158,7 +2345,8 @@
     }
 
     ActivityStack getStack() {
-        return (ActivityStack) getParent();
+        final WindowContainer parent = getParent();
+        return (ActivityStack) (parent instanceof ActivityStack ? parent : this);
     }
 
     /**
@@ -2169,27 +2357,99 @@
         return stack != null ? stack.mStackId : INVALID_STACK_ID;
     }
 
-    // TODO(task-hierarchy): Needs to take a generic WindowManager when task contains other tasks.
-    int getAdjustedAddPosition(ActivityRecord r, int suggestedPosition) {
-        int maxPosition = mChildren.size();
-        if (!r.isTaskOverlay()) {
-            // We want to place all non-overlay activities below overlays.
-            final ActivityRecord bottomMostOverlay = getActivity((ar) -> ar.isTaskOverlay(), false);
-            if (bottomMostOverlay != null) {
-                maxPosition = Math.max(mChildren.indexOf(bottomMostOverlay) - 1, 0);
+    int getDescendantTaskCount() {
+        final int[] currentCount = {0};
+        final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
+                PooledLambda.__(Task.class), currentCount);
+        forAllTasks(c, false /* traverseTopToBottom */, this);
+        c.recycle();
+        return currentCount[0];
+    }
+
+    /** Calculate the minimum possible position for a task that can be shown to the user.
+     *  The minimum position will be above all other tasks that can't be shown.
+     *  @param minPosition The minimum position the caller is suggesting.
+     *                  We will start adjusting up from here.
+     *  @param size The size of the current task list.
+     */
+    // TODO: Move user to their own window container.
+    private int computeMinUserPosition(int minPosition, int size) {
+        while (minPosition < size) {
+            final WindowContainer child = mChildren.get(minPosition);
+            final boolean canShow = child.showToCurrentUser();
+            if (canShow) {
+                break;
+            }
+            minPosition++;
+        }
+        return minPosition;
+    }
+
+    /** Calculate the maximum possible position for a task that can't be shown to the user.
+     *  The maximum position will be below all other tasks that can be shown.
+     *  @param maxPosition The maximum position the caller is suggesting.
+     *                  We will start adjusting down from here.
+     */
+    // TODO: Move user to their own window container.
+    private int computeMaxUserPosition(int maxPosition) {
+        while (maxPosition > 0) {
+            final WindowContainer child = mChildren.get(maxPosition);
+            final boolean canShow = child.showToCurrentUser();
+            if (!canShow) {
+                break;
+            }
+            maxPosition--;
+        }
+        return maxPosition;
+    }
+
+    private int getAdjustedChildPosition(WindowContainer wc, int suggestedPosition) {
+        final boolean canShowChild = wc.showToCurrentUser();
+
+        final int size = mChildren.size();
+
+        // Figure-out min/max possible position depending on if child can show for current user.
+        int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0;
+        int maxPosition = (canShowChild) ? size : computeMaxUserPosition(size - 1);
+
+        // Factor in always-on-top children in max possible position.
+        if (!wc.isAlwaysOnTop()) {
+
+            // We want to place all non-always-on-top containers below always-on-top ones.
+            while (maxPosition > minPosition) {
+                if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break;
+                --maxPosition;
             }
         }
 
-        return Math.min(maxPosition, suggestedPosition);
+        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+        if (suggestedPosition == POSITION_BOTTOM && minPosition == 0) {
+            return POSITION_BOTTOM;
+        } else if (suggestedPosition == POSITION_TOP && maxPosition == (size - 1)) {
+            return POSITION_TOP;
+        }
+        // Reset position based on minimum/maximum possible positions.
+        return Math.min(Math.max(suggestedPosition, minPosition), maxPosition);
     }
 
     @Override
     void positionChildAt(int position, WindowContainer child, boolean includingParents) {
-        position = getAdjustedAddPosition((ActivityRecord) child, position);
+        position = getAdjustedChildPosition(child, position);
         super.positionChildAt(position, child, includingParents);
+
+        // Log positioning.
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "positionChildAt: child=" + child
+                + " position=" + position + " parent=" + this);
+
+        final int toTop = position >= (mChildren.size() - 1) ? 1 : 0;
+        final Task task = child.asTask();
+        if (task != null) {
+            EventLogTags.writeWmTaskMoved(task.mTaskId, toTop, position);
+        }
     }
 
-    private boolean hasWindowsAlive() {
+    @VisibleForTesting
+    boolean hasWindowsAlive() {
         return getActivity(ActivityRecord::hasWindowsAlive) != null;
     }
 
@@ -2219,8 +2479,6 @@
                 + " from stack=" + getStack());
         EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
 
-        position = stack.findPositionForTask(this, position);
-
         reparent(stack, position);
 
         stack.positionChildAt(position, this, moveParents);
@@ -2286,11 +2544,16 @@
 
     @Override
     void onDisplayChanged(DisplayContent dc) {
-        adjustBoundsForDisplayChangeIfNeeded(dc);
+        final boolean isRootTask = isRootTask();
+        if (!isRootTask) {
+            adjustBoundsForDisplayChangeIfNeeded(dc);
+        }
         super.onDisplayChanged(dc);
-        final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
-        mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
-                mTaskId, displayId);
+        if (!isRootTask) {
+            final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
+            mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
+                    mTaskId, displayId);
+        }
     }
 
     /**
@@ -2432,7 +2695,7 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    public void getDimBounds(Rect out) {
+    void getDimBounds(Rect out) {
         final DisplayContent displayContent = getStack().getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
         // a DimLayer anyway if we weren't visible.
@@ -2565,6 +2828,11 @@
         mForceShowForAllUsers = forceShowForAllUsers;
     }
 
+    public boolean isAttached() {
+        final DisplayContent display = getDisplayContent();
+        return display != null && !display.isRemoved();
+    }
+
     /**
      * When we are in a floating stack (Freeform, Pinned, ...) we calculate
      * insets differently. However if we are animating to the fullscreen stack
@@ -2576,6 +2844,45 @@
                 && !getStack().isAnimatingBoundsToFullscreen() && !mPreserveNonFloatingState;
     }
 
+    /**
+     * Returns true if the stack is translucent and can have other contents visible behind it if
+     * needed. A stack is considered translucent if it don't contain a visible or
+     * starting (about to be visible) activity that is fullscreen (opaque).
+     * @param starting The currently starting activity or null if there is none.
+     */
+    @VisibleForTesting
+    boolean isTranslucent(ActivityRecord starting) {
+        if (!isAttached() || mForceHidden) {
+            return true;
+        }
+        final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity,
+                PooledLambda.__(ActivityRecord.class), starting);
+        final ActivityRecord opaque = getActivity(p);
+        p.recycle();
+        return opaque == null;
+    }
+
+    private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
+        if (r.finishing) {
+            // We don't factor in finishing activities when determining translucency since
+            // they will be gone soon.
+            return false;
+        }
+
+        if (!r.visibleIgnoringKeyguard && r != starting) {
+            // Also ignore invisible activities that are not the currently starting
+            // activity (about to be visible).
+            return false;
+        }
+
+        if (r.occludesParent() || r.hasWallpaper) {
+            // Stack isn't translucent if it has at least one fullscreen activity
+            // that is visible.
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public SurfaceControl getAnimationLeashParent() {
         if (WindowManagerService.sHierarchicalAnimations) {
@@ -2639,7 +2946,7 @@
                 return true;
             }
         }
-        return false;
+        return forAllTasks((t) -> { return t != this && t.isTaskAnimating(); });
     }
 
     /**
@@ -2715,26 +3022,45 @@
         return mTaskDescription;
     }
 
+    // TODO(task-merge): Figure out what's the right thing to do for places that used it.
+    boolean isRootTask() {
+        return getParent() == null || getParent().asTask() == null;
+    }
+
     @Override
     boolean fillsParent() {
-        return matchParentBounds() || !getWindowConfiguration().canResizeTask();
+        return matchParentBounds();
+    }
+
+    void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom, Task excludedTask) {
+        if (traverseTopToBottom) {
+            super.forAllTasks(callback, traverseTopToBottom);
+            if (excludedTask != this) {
+                callback.accept(this);
+            }
+        } else {
+            super.forAllTasks(callback, traverseTopToBottom);
+            if (excludedTask != this) {
+                callback.accept(this);
+            }
+        }
     }
 
     @Override
     void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
-        // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks.
-        callback.accept(this);
+        forAllTasks(callback, traverseTopToBottom, null /* excludedTask */);
     }
 
     @Override
     boolean forAllTasks(Function<Task, Boolean> callback) {
+        if (super.forAllTasks(callback)) return true;
         return callback.apply(this);
     }
 
     @Override
     Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
-        // I'm a task!
-        // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks.
+        final Task t = super.getTask(callback, traverseTopToBottom);
+        if (t != null) return t;
         return callback.test(this) ? this : null;
     }
 
@@ -2770,6 +3096,16 @@
         return mDimmer;
     }
 
+    void dim(float alpha) {
+        mDimmer.dimAbove(getPendingTransaction(), alpha);
+        scheduleAnimation();
+    }
+
+    void stopDimming() {
+        mDimmer.stopDim(getPendingTransaction());
+        scheduleAnimation();
+    }
+
     boolean isTaskForUser(int userId) {
         return mUserId == userId;
     }
@@ -2817,7 +3153,7 @@
     }
 
     @Override
-    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
 
@@ -2877,6 +3213,17 @@
         return mTaskId == taskId;
     }
 
+    @Override
+    Task asTask() {
+        // I'm a task!
+        return this;
+    }
+
+    // TODO(task-merge): Figure-out how this should work with hierarchy tasks.
+    boolean shouldBeVisible(ActivityRecord starting) {
+        return true;
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("userId="); pw.print(mUserId);
         pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2963,7 +3310,7 @@
         if (mRootProcess != null) {
             pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
         }
-        pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
+        pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" stackId=" + getStackId());
         pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
         pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
         pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
@@ -2990,6 +3337,10 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" #");
         sb.append(mTaskId);
+        sb.append(" visible=" + shouldBeVisible(null /* starting */));
+        sb.append(" type=" + activityTypeToString(getActivityType()));
+        sb.append(" mode=" + windowingModeToString(getWindowingMode()));
+        sb.append(" translucent=" + isTranslucent(null /* starting */));
         if (affinity != null) {
             sb.append(" A=");
             sb.append(affinity);
@@ -3006,8 +3357,7 @@
         return toString();
     }
 
-    @Override
-    public void dumpDebug(ProtoOutputStream proto, long fieldId,
+    void dumpDebugInner(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
         if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
             return;
@@ -3218,13 +3568,13 @@
         Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
                 Intent intent, IVoiceInteractionSession voiceSession,
                 IVoiceInteractor voiceInteractor, ActivityStack stack) {
-            return new Task(service, taskId, info, intent, voiceSession, voiceInteractor,
+            return new ActivityStack(service, taskId, info, intent, voiceSession, voiceInteractor,
                     null /*taskDescription*/, stack);
         }
 
         Task create(ActivityTaskManagerService service, int taskId, ActivityInfo info,
                 Intent intent, TaskDescription taskDescription, ActivityStack stack) {
-            return new Task(service, taskId, info, intent, null /*voiceSession*/,
+            return new ActivityStack(service, taskId, info, intent, null /*voiceSession*/,
                     null /*voiceInteractor*/, taskDescription, stack);
         }
 
@@ -3241,7 +3591,7 @@
                 int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
                 int resizeMode, boolean supportsPictureInPicture, boolean realActivitySuspended,
                 boolean userSetupComplete, int minWidth, int minHeight, ActivityStack stack) {
-            return new Task(service, taskId, intent, affinityIntent, affinity,
+            return new ActivityStack(service, taskId, intent, affinityIntent, affinity,
                     rootAffinity, realActivity, origActivity, rootWasReset, autoRemoveRecents,
                     askedCompatMode, userId, effectiveUid, lastDescription,
                     lastTimeMoved, neverRelinquishIdentity, lastTaskDescription, taskAffiliation,
@@ -3483,7 +3833,9 @@
     }
 
     boolean isControlledByTaskOrganizer() {
-        return mTaskOrganizer != null;
+        // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally
+        //  we only give control of the root task.
+        return getTopMostTask().mTaskOrganizer != null;
     }
 
     @Override
@@ -3559,13 +3911,16 @@
     public void setWindowingMode(int windowingMode) {
         super.setWindowingMode(windowingMode);
         windowingMode = getWindowingMode();
-        /*
-         * Different windowing modes may be managed by different task organizers. If
-         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
-         * make sure we clear it.
-         */
-        final ITaskOrganizer org =
-            mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
-        setTaskOrganizer(org);
+
+        // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally
+        //  we only give control of the root task.
+        // Different windowing modes may be managed by different task organizers. If
+        // getTaskOrganizer returns null, we still call transferToTaskOrganizer to make sure we
+        // clear it.
+        if (!isRootTask()) {
+            final ITaskOrganizer org =
+                    mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+            setTaskOrganizer(org);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 3b2d519..c94b8c9 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -562,6 +562,13 @@
         return false;
     }
 
+    /** @return true if this window container is a descendant of the input container. */
+    boolean isDescendantOf(WindowContainer ancestor) {
+        final WindowContainer parent = getParent();
+        if (parent == ancestor) return true;
+        return (parent != null) && parent.isDescendantOf(ancestor);
+    }
+
     /**
      * Move a child from it's current place in siblings list to the specified position,
      * with an option to move all its parents to top.
@@ -2235,4 +2242,14 @@
     void setSurfaceControl(SurfaceControl sc) {
         mSurfaceControl = sc;
     }
+
+    /** Cheap way of doing cast and instanceof. */
+    Task asTask() {
+        return null;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
+    ActivityRecord asActivityRecord() {
+        return null;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e3b593e9..faad17b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2692,8 +2692,7 @@
         displayContent.getDockedDividerController().checkMinimizeChanged(animate);
     }
 
-    boolean isValidPictureInPictureAspectRatio(int displayId, float aspectRatio) {
-        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+    boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
         return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio(
                 aspectRatio);
     }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 2a1e980..70774c8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -228,12 +228,6 @@
         return false;
     }
 
-    ActivityRecord asActivityRecord() {
-        // TODO: Not sure if this is the best way to handle this vs. using instanceof and casting.
-        // I am not an app window token!
-        return null;
-    }
-
     @Override
     void removeImmediately() {
         if (mDisplayContent != null) {