Added StackWindowContainerController

For linking ActivityStack in AMS to TaskStack window container in WMS.

Change-Id: I8b9eaef49e62854d59b22d27f80f5935a5a4d7fc
Bug: 30060889
Test: bit FrameworksServicesTests:com.android.server.wm.StackWindowContainerControllerTests
Test: bit FrameworksServicesTests:com.android.server.wm.TaskWindowContainerControllerTests
Test: Existing test pass and manual testing.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6fbaf9..128536f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7625,12 +7625,11 @@
                     final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                     final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
                     final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
-                            ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
-                                    aspectRatio)
+                            ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio)
                             : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
                             bounds, true /* moveHomeStackToFront */);
-                    mWindowManager.setPictureInPictureActions(actions);
+                    mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions);
                 };
 
                 if (isKeyguardLocked()) {
@@ -7678,12 +7677,11 @@
 
                 // Only update the saved args from the args that are set
                 r.pictureInPictureArgs.copyOnlySet(args);
-                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                final ActivityStack stack = r.getStack();
+                if (stack.getStackId() == PINNED_STACK_ID) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
-                    mWindowManager.setPictureInPictureAspectRatio(
-                            r.pictureInPictureArgs.getAspectRatio());
-                    mWindowManager.setPictureInPictureActions(
-                            r.pictureInPictureArgs.getActions());
+                    stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio());
+                    stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
                 }
             }
         } finally {
@@ -10221,7 +10219,9 @@
             synchronized (this) {
                 if (animate) {
                     if (stackId == PINNED_STACK_ID) {
-                        mWindowManager.animateResizePinnedStack(bounds, animationDuration);
+                        final ActivityStack pinnedStack =
+                                mStackSupervisor.getStack(PINNED_STACK_ID);
+                        pinnedStack.animateResizePinnedStack(bounds, animationDuration);
                     } else {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
@@ -19593,7 +19593,7 @@
 
     /** Helper method that requests bounds from WM and applies them to stack. */
     private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
-        final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
+        final Rect newBounds = mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration();
         mStackSupervisor.resizeStackLocked(
                 stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                 false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7bfea9a..973951e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -80,6 +80,7 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
+import android.app.RemoteAction;
 import android.app.ResultInfo;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -114,6 +115,8 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
 import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import com.android.server.wm.StackWindowController;
+import com.android.server.wm.StackWindowListener;
 import com.android.server.wm.WindowManagerService;
 
 import java.io.FileDescriptor;
@@ -128,7 +131,7 @@
 /**
  * State and management of a single stack of activities.
  */
-final class ActivityStack extends ConfigurationContainer {
+final class ActivityStack extends ConfigurationContainer implements StackWindowListener {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -240,6 +243,7 @@
 
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
+    private StackWindowController mWindowContainerController;
     private final RecentTasks mRecentTasks;
 
     /**
@@ -328,7 +332,7 @@
     private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
-    private final Rect tempRect2 = new Rect();
+    private final Rect mTmpRect2 = new Rect();
 
     /** Run all ActivityStacks through this */
     private final ActivityStackSupervisor mStackSupervisor;
@@ -441,7 +445,7 @@
     }
 
     ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
-            RecentTasks recentTasks) {
+            RecentTasks recentTasks, boolean onTop) {
         mActivityContainer = activityContainer;
         mStackSupervisor = activityContainer.getOuter();
         mService = mStackSupervisor.mService;
@@ -452,13 +456,25 @@
         mRecentTasks = recentTasks;
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
+        final ActivityStackSupervisor.ActivityDisplay display = mActivityContainer.mActivityDisplay;
+        mTmpRect2.setEmpty();
+        mWindowContainerController = new StackWindowController(mStackId, this,
+                display.mDisplayId, onTop, mTmpRect2);
+        activityContainer.mStack = this;
+        mStackSupervisor.mActivityContainers.put(mStackId, activityContainer);
+        postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
+    }
+
+    StackWindowController getWindowContainerController() {
+        return mWindowContainerController;
     }
 
     /** Adds the stack to specified display and calls WindowManager to do the same. */
-    void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
-        final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
-                onTop);
-        postAddToDisplay(activityDisplay, bounds);
+    void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+        removeFromDisplay();
+        mTmpRect2.setEmpty();
+        mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2);
+        postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
     }
 
     /**
@@ -467,10 +483,10 @@
      * @param bounds Updated bounds.
      */
     private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
-            Rect bounds) {
+            Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
         mStacks = activityDisplay.mStacks;
-        mBounds = bounds;
+        mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
             mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -478,6 +494,7 @@
         }
         onParentChanged();
 
+        activityDisplay.attachStack(this, onTop);
         if (mStackId == DOCKED_STACK_ID) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
@@ -487,16 +504,6 @@
     }
 
     /**
-     * Moves the stack to specified display.
-     * @param activityDisplay Target display to move the stack to.
-     */
-    void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
-        removeFromDisplay();
-        final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
-        postAddToDisplay(activityDisplay, bounds);
-    }
-
-    /**
      * Updates the inner state of the stack to remove it from its current parent, so it can be
      * either destroyed completely or re-parented.
      */
@@ -518,7 +525,8 @@
     void remove() {
         removeFromDisplay();
         mStackSupervisor.deleteActivityContainerRecord(mStackId);
-        mWindowManager.removeStack(mStackId);
+        mWindowContainerController.removeContainer();
+        mWindowContainerController = null;
         onParentChanged();
     }
 
@@ -526,6 +534,42 @@
         mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
     }
 
+    void animateResizePinnedStack(Rect bounds, int animationDuration) {
+        mWindowContainerController.animateResizePinnedStack(bounds, animationDuration);
+    }
+
+    void setPictureInPictureAspectRatio(float aspectRatio) {
+        mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio);
+    }
+
+    void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+        mWindowContainerController.getStackDockedModeBounds(outBounds, ignoreVisibility);
+    }
+
+    void prepareFreezingTaskBounds() {
+        mWindowContainerController.prepareFreezingTaskBounds();
+    }
+
+    void setPictureInPictureActions(List<RemoteAction> actions) {
+        mWindowContainerController.setPictureInPictureActions(actions);
+    }
+
+    void getWindowContainerBounds(Rect outBounds) {
+        if (mWindowContainerController != null) {
+            mWindowContainerController.getBounds(outBounds);
+        }
+        outBounds.setEmpty();
+    }
+
+    Rect getBoundsForNewConfiguration() {
+        return mWindowContainerController.getBoundsForNewConfiguration();
+    }
+
+    void positionChildWindowContainerAtTop(TaskRecord child) {
+        mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
+                true /* includingParents */);
+    }
+
     /**
      * Defers updating the bounds of the stack. If the stack was resized/repositioned while
      * deferring, the bounds will update in {@link #continueUpdateBounds()}.
@@ -546,8 +590,7 @@
         final boolean wasDeferred = mUpdateBoundsDeferred;
         mUpdateBoundsDeferred = false;
         if (wasDeferred && mUpdateBoundsDeferredCalled) {
-            mStackSupervisor.resizeStackUncheckedLocked(this,
-                    mDeferredBounds.isEmpty() ? null : mDeferredBounds,
+            resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds,
                     mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
                     mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
         }
@@ -765,7 +808,8 @@
 
         task = topTask();
         if (task != null) {
-            task.moveWindowContainerToTop(true /* includingParents */);
+            mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                    true /* includingParents */);
         }
     }
 
@@ -784,7 +828,7 @@
             mTaskHistory.remove(task);
             mTaskHistory.add(0, task);
             updateTaskMovement(task, false);
-            task.moveWindowContainerToBottom();
+            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
         }
     }
 
@@ -2578,7 +2622,8 @@
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
         mTaskHistory.add(position, task);
-        task.positionWindowContainerAt(position);
+        mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
+                task.mBounds, task.getOverrideConfiguration());
         updateTaskMovement(task, true);
     }
 
@@ -2590,7 +2635,8 @@
         final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting);
         mTaskHistory.add(position, task);
         updateTaskMovement(task, true);
-        task.moveWindowContainerToTop(true /* includingParents */);
+        mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                true /* includingParents */);
     }
 
     private void updateTaskReturnToForTopInsertion(TaskRecord task) {
@@ -2618,7 +2664,7 @@
                 // This also makes sure that non-home activities are visible under a transparent
                 // non-home activity.
                 task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
-            } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask != task)) {
+            } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
                 // If it's a last task over home - we default to keep its return to type not to
                 // make underlying task focused when this one will be finished.
                 int returnToType = isLastTaskOverHome
@@ -2868,7 +2914,8 @@
                     targetTask.addActivityAtBottom(p);
                 }
 
-                targetTask.moveWindowContainerToBottom();
+                mWindowContainerController.positionChildAtBottom(
+                        targetTask.getWindowContainerController());
                 replyChainEnd = -1;
             } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
                 // If the activity should just be removed -- either
@@ -3004,7 +3051,8 @@
                         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
                                 + " from " + srcPos + " in to resetting task " + task);
                     }
-                    task.moveWindowContainerToTop(true /* includingParents */);
+                    mWindowContainerController.positionChildAtTop(
+                            task.getWindowContainerController(), true /* includingParents */);
 
                     // Now we've moved it in to place...  but what if this is
                     // a singleTop activity and we have put it on top of another
@@ -4373,7 +4421,7 @@
         }
 
         mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-        tr.moveWindowContainerToBottom();
+        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
 
         final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
         if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
@@ -4436,11 +4484,24 @@
         }
     }
 
-    /** Update override configurations of all tasks in the stack. */
-    void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds) {
+    // TODO: Figure-out a way to consolidate with resize() method below.
+    @Override
+    public void requestResize(Rect bounds) {
+        mService.resizeStack(mStackId, bounds, true /* allowResizeInDockedMode */,
+                false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+    }
 
-        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds;
+    // TODO: Can only be called from special methods in ActivityStackSupervisor.
+    // Need to consolidate those calls points into this resize method so anyone can call directly.
+    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
+        bounds = TaskRecord.validateBounds(bounds);
+
+        if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+            return;
+        }
+
+        // Update override configurations of all tasks in the stack.
+        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
         final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
 
         mTmpBounds.clear();
@@ -4454,9 +4515,9 @@
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
-                    tempRect2.set(task.mBounds);
-                    fitWithinBounds(tempRect2, stackBounds);
-                    task.updateOverrideConfiguration(tempRect2);
+                    mTmpRect2.set(task.mBounds);
+                    fitWithinBounds(mTmpRect2, bounds);
+                    task.updateOverrideConfiguration(mTmpRect2);
                 } else {
                     task.updateOverrideConfiguration(taskBounds, insetBounds);
                 }
@@ -4469,11 +4530,9 @@
             }
         }
 
-        // We might trigger a configuration change. Save the current task bounds for freezing.
-        mWindowManager.prepareFreezingTaskBounds(mStackId);
-        mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds,
+        mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
                 mTmpInsetBounds);
-        setBounds(stackBounds);
+        setBounds(bounds);
     }
 
 
@@ -4897,7 +4956,8 @@
         addTask(task, toTop ? MAX_VALUE : 0, reason);
         if (toTop) {
             // TODO: figure-out a way to remove this call.
-            task.moveWindowContainerToTop(true /* includingParents */);
+            mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                    true /* includingParents */);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 24ff1d7..29032f8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -376,7 +376,7 @@
 
     // TODO: Add listener for removal of references.
     /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
+    SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
 
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -2198,7 +2198,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            resizeStackUncheckedLocked(stack, bounds, tempTaskBounds, tempTaskInsetBounds);
+            stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);
             if (!deferResume) {
                 stack.ensureVisibleActivitiesConfigurationLocked(
                         stack.topRunningActivityLocked(), preserveWindows);
@@ -2236,17 +2236,6 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds) {
-        bounds = TaskRecord.validateBounds(bounds);
-
-        if (!stack.updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
-            return;
-        }
-
-        stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds);
-    }
-
     void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
         final ActivityStack stack = getStack(fromStackId);
         if (stack == null) {
@@ -2340,8 +2329,7 @@
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
             mAllowDockedStackResize = false;
             ActivityRecord r = stack.topRunningActivityLocked();
-            resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
-                    tempDockedTaskInsetBounds);
+            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);
 
             // TODO: Checking for isAttached might not be needed as if the user passes in null
             // dockedBounds then they want the docked stack to be dismissed.
@@ -2358,10 +2346,10 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
-                mWindowManager.getStackDockedModeBounds(
-                        HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
+                    final ActivityStack current = getStack(i);
+                    if (current != null && StackId.isResizeableByDockedStack(i)) {
+                        current.getStackDockedModeBounds(tempRect, true /* ignoreVisibility */);
                         resizeStackLocked(i, tempRect, tempOtherTaskBounds,
                                 tempOtherTaskInsetBounds, preserveWindows,
                                 true /* allowResizeInDockedMode */, deferResume);
@@ -2394,8 +2382,7 @@
         mWindowManager.deferSurfaceLayout();
         try {
             ActivityRecord r = stack.topRunningActivityLocked();
-            resizeStackUncheckedLocked(stack, pinnedBounds, tempPinnedTaskBounds,
-                    null);
+            stack.resize(pinnedBounds, tempPinnedTaskBounds, null);
             stack.ensureVisibleActivitiesConfigurationLocked(r, false);
         } finally {
             mWindowManager.continueSurfaceLayout();
@@ -2404,14 +2391,13 @@
     }
 
     ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
-        ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay == null) {
             return null;
         }
 
-        ActivityContainer activityContainer = new ActivityContainer(stackId);
-        mActivityContainers.put(stackId, activityContainer);
-        activityContainer.addToDisplayLocked(activityDisplay, onTop);
+        final ActivityContainer activityContainer =
+                new ActivityContainer(stackId, activityDisplay, onTop);
         return activityContainer.mStack;
     }
 
@@ -2770,7 +2756,8 @@
             }
 
             // We might trigger a configuration change. Save the current task bounds for freezing.
-            mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
+            // TODO: Should this call be moved inside the resize method in WM?
+            stack.prepareFreezingTaskBounds();
 
             // Make sure the task has the appropriate bounds/size for the stack it is in.
             if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
@@ -2839,7 +2826,11 @@
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
             boolean moveHomeStackToFront) {
+
         mWindowManager.deferSurfaceLayout();
+        // Need to make sure the pinned stack exist so we can resize it below...
+        final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
         try {
             final TaskRecord task = r.task;
 
@@ -2849,9 +2840,6 @@
                 requestVisibleBehindLocked(r, false);
             }
 
-            // Need to make sure the pinned stack exist so we can resize it below...
-            final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
@@ -2891,7 +2879,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mWindowManager.animateResizePinnedStack(bounds, -1);
+        stack.animateResizePinnedStack(bounds, -1);
         mService.mTaskChangeNotificationController.notifyActivityPinned();
     }
 
@@ -3356,7 +3344,7 @@
                 stack.switchUserLocked(userId);
                 TaskRecord task = stack.topTask();
                 if (task != null) {
-                    task.moveWindowContainerToTop(true /* includingParents */);
+                    stack.positionChildWindowContainerAtTop(task);
                 }
             }
         }
@@ -3794,7 +3782,7 @@
     private StackInfo getStackInfoLocked(ActivityStack stack) {
         final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
         StackInfo info = new StackInfo();
-        mWindowManager.getStackBounds(stack.mStackId, info.bounds);
+        stack.getWindowContainerBounds(info.bounds);
         info.displayId = DEFAULT_DISPLAY;
         info.stackId = stack.mStackId;
         info.userId = stack.mCurrentUser;
@@ -4320,7 +4308,7 @@
                 FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
         final int mStackId;
         IActivityContainerCallback mCallback = null;
-        final ActivityStack mStack;
+        ActivityStack mStack;
         ActivityRecord mParentActivity = null;
         String mIdString;
 
@@ -4334,10 +4322,11 @@
         final static int CONTAINER_STATE_FINISHING = 2;
         int mContainerState = CONTAINER_STATE_HAS_SURFACE;
 
-        ActivityContainer(int stackId) {
+        ActivityContainer(int stackId, ActivityDisplay activityDisplay, boolean onTop) {
             synchronized (mService) {
                 mStackId = stackId;
-                mStack = new ActivityStack(this, mRecentTasks);
+                mActivityDisplay = activityDisplay;
+                new ActivityStack(this, mRecentTasks, onTop);
                 mIdString = "ActivtyContainer{" + mStackId + "}";
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this);
             }
@@ -4345,21 +4334,18 @@
 
         /**
          * Adds the stack to specified display. Also calls WindowManager to do the same from
-         * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+         * {@link ActivityStack#reparent(ActivityDisplay, boolean)}.
          * @param activityDisplay The display to add the stack to.
-         * @param onTop If true the stack will be place at the top of the display, else at the
-         *              bottom.
          */
-        void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+        void addToDisplayLocked(ActivityDisplay activityDisplay) {
             if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
-                    + " to display=" + activityDisplay + " onTop=" + onTop);
+                    + " to display=" + activityDisplay);
             if (mActivityDisplay != null) {
                 throw new IllegalStateException("ActivityContainer is already attached, " +
                         "displayId=" + mActivityDisplay.mDisplayId);
             }
             mActivityDisplay = activityDisplay;
-            mStack.addToDisplay(activityDisplay, onTop);
-            activityDisplay.attachActivities(mStack, onTop);
+            mStack.reparent(activityDisplay, true /* onTop */);
         }
 
         @Override
@@ -4369,7 +4355,7 @@
                 if (activityDisplay == null) {
                     return;
                 }
-                addToDisplayLocked(activityDisplay, true /* onTop */);
+                addToDisplayLocked(activityDisplay);
             }
         }
 
@@ -4446,7 +4432,7 @@
             if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
                     + " current displayId=" + mActivityDisplay.mDisplayId);
 
-            mActivityDisplay.detachActivitiesLocked(mStack);
+            mActivityDisplay.detachStack(mStack);
             mActivityDisplay = null;
         }
 
@@ -4462,8 +4448,7 @@
             removeFromDisplayLocked();
 
             mActivityDisplay = activityDisplay;
-            mStack.moveToDisplay(activityDisplay);
-            activityDisplay.attachActivities(mStack, ON_TOP);
+            mStack.reparent(activityDisplay, ON_TOP);
         }
 
         @Override
@@ -4553,7 +4538,8 @@
         boolean mDrawn = false;
 
         VirtualActivityContainer(ActivityRecord parent, IActivityContainerCallback callback) {
-            super(getNextStackId());
+            super(getNextStackId(), parent.getStack().mActivityContainer.mActivityDisplay,
+                    true /* onTop */);
             mParentActivity = parent;
             mCallback = callback;
             mContainerState = CONTAINER_STATE_NO_SURFACE;
@@ -4585,7 +4571,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
+                addToDisplayLocked(virtualActivityDisplay);
             }
 
             if (mSurface != null) {
@@ -4674,10 +4660,9 @@
             mDisplayId = display.getDisplayId();
         }
 
-        void attachActivities(ActivityStack stack, boolean onTop) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK,
-                    "attachActivities: attaching " + stack + " to displayId=" + mDisplayId
-                    + " onTop=" + onTop);
+        void attachStack(ActivityStack stack, boolean onTop) {
+            if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
+                    + " to displayId=" + mDisplayId + " onTop=" + onTop);
             if (onTop) {
                 mStacks.add(stack);
             } else {
@@ -4685,8 +4670,8 @@
             }
         }
 
-        void detachActivitiesLocked(ActivityStack stack) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack
+        void detachStack(ActivityStack stack) {
+            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
                     + " from displayId=" + mDisplayId);
             mStacks.remove(stack);
         }
@@ -4764,8 +4749,8 @@
         }
 
         @Override
-        void detachActivitiesLocked(ActivityStack stack) {
-            super.detachActivitiesLocked(stack);
+        void detachStack(ActivityStack stack) {
+            super.detachStack(stack);
             if (mVirtualDisplay != null) {
                 mVirtualDisplay.release();
                 mVirtualDisplay = null;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 8d32778..b913a23 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1835,7 +1835,7 @@
                 mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
                 mIntent, null, null, true, mStartActivity.mActivityType);
         mStartActivity.setTask(task, null);
-        mStartActivity.task.moveWindowContainerToTop(true /* includingParents */);
+        mStartActivity.task.getStack().positionChildWindowContainerAtTop(mStartActivity.task);
         if (DEBUG_TASKS) Slog.v(TAG_TASKS,
                 "Starting new activity " + mStartActivity + " in new guessed " + mStartActivity.task);
     }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 2373ea8..f12d7b7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -421,9 +421,10 @@
 
         final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
         final Configuration overrideConfig = getOverrideConfiguration();
-        mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
-                userId, bounds, overrideConfig, mResizeMode, mSupportsPictureInPicture,
-                isHomeTask(), isOnTopLauncher(), onTop, showForAllUsers, lastTaskDescription);
+        mWindowContainerController = new TaskWindowContainerController(taskId, this,
+                getStack().getWindowContainerController(), userId, bounds, overrideConfig,
+                mResizeMode, mSupportsPictureInPicture, isHomeTask(), isOnTopLauncher(),
+                onTop, showForAllUsers, lastTaskDescription);
     }
 
     void removeWindowContainer() {
@@ -457,6 +458,12 @@
         mWindowContainerController.setTaskDockedResizing(resizing);
     }
 
+    // TODO: Consolidate this with the resize() method below.
+    @Override
+    public void requestResize(Rect bounds, int resizeMode) {
+        mService.resizeTask(taskId, bounds, resizeMode);
+    }
+
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
         if (!isResizeable()) {
             Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
@@ -527,25 +534,6 @@
                 false /* forced */);
     }
 
-    // TODO: Remove once we have a stack controller.
-    void positionWindowContainerAt(int position) {
-        mWindowContainerController.positionAt(position, mBounds, getOverrideConfiguration());
-    }
-
-    // TODO: Replace with moveChildToTop?
-    void moveWindowContainerToTop(boolean includingParents) {
-        if (mWindowContainerController != null) {
-            mWindowContainerController.moveToTop(includingParents);
-        }
-    }
-
-    // TODO: Replace with moveChildToBottom?
-    void moveWindowContainerToBottom() {
-        if (mWindowContainerController != null) {
-            mWindowContainerController.moveToBottom();
-        }
-    }
-
     void getWindowContainerBounds(Rect bounds) {
         mWindowContainerController.getBounds(bounds);
     }
@@ -563,7 +551,7 @@
 
             // Must reparent first in window manager to avoid a situation where AM can delete the
             // we are coming from in WM before we reparent because it became empty.
-            mWindowContainerController.reparent(stackId, position);
+            mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
 
             final ActivityStack prevStack = mStack;
             prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 914cc8d..3a74ded 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -57,6 +57,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -69,6 +70,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
@@ -634,6 +636,13 @@
      */
     DisplayContent(Display display, WindowManagerService service,
             WindowLayersController layersController, WallpaperController wallpaperController) {
+
+        if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+            throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+                    + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+                    + " new=" + display);
+        }
+
         mDisplay = display;
         mDisplayId = display.getDisplayId();
         mLayersController = layersController;
@@ -957,75 +966,43 @@
         out.set(mContentRect);
     }
 
-    /**
-     * Adds the stack to this display.
-     * @see WindowManagerService#addStackToDisplay(int, int, boolean)
-     */
-    Rect addStackToDisplay(int stackId, boolean onTop) {
-        boolean attachedToDisplay = false;
-        TaskStack stack = mService.mStackIdToStack.get(stackId);
-        if (stack == null) {
-            if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
-                    + mDisplayId);
+    TaskStack addStackToDisplay(int stackId, boolean onTop) {
+        if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+                + mDisplayId);
 
-            stack = getStackById(stackId);
-            if (stack != null) {
-                // It's already attached to the display...clear mDeferRemoval and move stack to
-                // appropriate z-order on display as needed.
-                stack.mDeferRemoval = false;
-                // We're not moving the display to front when we're adding stacks, only when
-                // requested to change the position of stack explicitly.
-                mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
-                        false /* includingParents */);
-                attachedToDisplay = true;
-            } else {
-                stack = new TaskStack(mService, stackId);
-            }
-
-            mService.mStackIdToStack.put(stackId, stack);
-            if (stackId == DOCKED_STACK_ID) {
-                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
-            }
+        TaskStack stack = getStackById(stackId);
+        if (stack != null) {
+            // It's already attached to the display...clear mDeferRemoval and move stack to
+            // appropriate z-order on display as needed.
+            stack.mDeferRemoval = false;
+            // We're not moving the display to front when we're adding stacks, only when
+            // requested to change the position of stack explicitly.
+            mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
+                    false /* includingParents */);
         } else {
-            final DisplayContent currentDC = stack.getDisplayContent();
-            if (currentDC != null) {
-                throw new IllegalStateException("Trying to add stackId=" + stackId
-                        + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
-                        + currentDC.getDisplayId());
-            }
-        }
-
-        if (!attachedToDisplay) {
+            stack = new TaskStack(mService, stackId);
             mTaskStackContainers.addStackToDisplay(stack, onTop);
         }
 
-        if (stack.getRawFullscreen()) {
-            return null;
+        if (stackId == DOCKED_STACK_ID) {
+            mDividerControllerLocked.notifyDockedStackExistsChanged(true);
         }
-        final Rect bounds = new Rect();
-        stack.getRawBounds(bounds);
-        return bounds;
+        return stack;
     }
 
-    /** Removes the stack from the display and prepares for changing the parent. */
-    private void removeStackFromDisplay(TaskStack stack) {
-        mTaskStackContainers.removeStackFromDisplay(stack);
-    }
-
-    /** Moves the stack to this display and returns the updated bounds. */
-    Rect moveStackToDisplay(TaskStack stack) {
-        final DisplayContent currentDisplayContent = stack.getDisplayContent();
-        if (currentDisplayContent == null) {
+    void moveStackToDisplay(TaskStack stack) {
+        final DisplayContent prevDc = stack.getDisplayContent();
+        if (prevDc == null) {
             throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
                     + " which is not currently attached to any display");
         }
-        if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+        if (prevDc.getDisplayId() == mDisplayId) {
             throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
                     + " to its current displayId=" + mDisplayId);
         }
 
-        currentDisplayContent.removeStackFromDisplay(stack);
-        return addStackToDisplay(stack.mStackId, true /* onTop */);
+        prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+        mTaskStackContainers.addStackToDisplay(stack, true /* onTop */);
     }
 
     @Override
@@ -1172,7 +1149,7 @@
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mDimLayerController.close();
-            if (mDisplayId == DEFAULT_DISPLAY) {
+            if (mDisplayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
                 mService.unregisterPointerEventListener(mTapDetector);
                 mService.unregisterPointerEventListener(mService.mMousePositionTracker);
             }
@@ -1250,7 +1227,7 @@
         final WindowState imeWin = mService.mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
                 && !mDividerControllerLocked.isImeHideRequested();
-        final boolean dockVisible = mService.isStackVisibleLocked(DOCKED_STACK_ID);
+        final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
         final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
         final int imeDockSide = (dockVisible && imeTargetStack != null) ?
                 imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -1447,7 +1424,7 @@
      * @return The docked stack, but only if it is visible, and {@code null} otherwise.
      */
     TaskStack getDockedStackLocked() {
-        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack stack = getStackById(DOCKED_STACK_ID);
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
@@ -1456,7 +1433,7 @@
      * visible.
      */
     TaskStack getDockedStackIgnoringVisibility() {
-        return mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        return getStackById(DOCKED_STACK_ID);
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -1532,6 +1509,9 @@
         }
     }
 
+    // TODO: This should probably be called any time a visual change is made to the hierarchy like
+    // moving containers or resizing them. Need to investigate the best way to have it automatically
+    // happen so we don't run into issues with programmers forgetting to do it.
     void layoutAndAssignWindowLayersIfNeeded() {
         mService.mWindowsChanged = true;
         setLayoutNeeded();
@@ -2441,6 +2421,27 @@
         }
     }
 
+    void setExitingTokensHasVisible(boolean hasVisible) {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            mExitingTokens.get(i).hasVisible = hasVisible;
+        }
+
+        // Initialize state of exiting applications.
+        mTaskStackContainers.setExitingTokensHasVisible(hasVisible);
+    }
+
+    void removeExistingTokensIfPossible() {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            final WindowToken token = mExitingTokens.get(i);
+            if (!token.hasVisible) {
+                mExitingTokens.remove(i);
+            }
+        }
+
+        // Time to remove any exiting applications?
+        mTaskStackContainers.removeExistingAppTokensIfPossible();
+    }
+
     static final class TaskForResizePointSearchResult {
         boolean searchDone;
         Task taskForResize;
@@ -2650,10 +2651,38 @@
             return false;
         }
 
+        void setExitingTokensHasVisible(boolean hasVisible) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    appTokens.get(j).hasVisible = hasVisible;
+                }
+            }
+        }
+
+        void removeExistingAppTokensIfPossible() {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    final AppWindowToken token = appTokens.get(j);
+                    if (!token.hasVisible && !mService.mClosingApps.contains(token)
+                            && (!token.mIsExiting || token.isEmpty())) {
+                        // Make sure there is no animation running on this token, so any windows
+                        // associated with it will be removed as soon as their animations are
+                        // complete.
+                        token.mAppAnimator.clearAnimation();
+                        token.mAppAnimator.animating = false;
+                        if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                                "performLayout: App token exiting now removed" + token);
+                        token.removeIfPossible();
+                    }
+                }
+            }
+        }
+
         @Override
         int getOrientation() {
-            if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
-                    || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+            if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
                 // Apps and their containers are not allowed to specify an orientation while the
                 // docked or freeform stack is visible...except for the home stack/task if the
                 // docked stack is minimized and it actually set something.
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ed1e2d9..aaf724e 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -457,8 +457,7 @@
     void registerDockedStackListener(IDockedStackListener listener) {
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
-        notifyDockedStackExistsChanged(
-                mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
+        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
 
@@ -466,7 +465,7 @@
 
     void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
         mService.openSurfaceTransaction();
-        final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
+        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
         final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
@@ -544,8 +543,8 @@
         if (homeTask == null || !isWithinDisplay(homeTask)) {
             return;
         }
-        final TaskStack fullscreenStack
-                = mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID);
+        final TaskStack fullscreenStack =
+                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
         final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible())
                 || (homeStack.hasMultipleTaskWithHomeTaskNotTop());
@@ -739,7 +738,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0cc6c70..22abf30 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -382,6 +382,17 @@
         }
     }
 
+    TaskStack getStackById(int stackId) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final TaskStack stack = dc.getStackById(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
             if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -535,18 +546,7 @@
         final int numDisplays = mChildren.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
-            for (i = displayContent.mExitingTokens.size() - 1; i >= 0; i--) {
-                displayContent.mExitingTokens.get(i).hasVisible = false;
-            }
-        }
-
-        for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
-            // Initialize state of exiting applications.
-            final AppTokenList exitingAppTokens =
-                    mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
-            for (int tokenNdx = exitingAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
-                exitingAppTokens.get(tokenNdx).hasVisible = false;
-            }
+            displayContent.setExitingTokensHasVisible(false);
         }
 
         mHoldScreen = null;
@@ -681,33 +681,7 @@
         // Time to remove any exiting tokens?
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
-            ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens;
-            for (i = exitingTokens.size() - 1; i >= 0; i--) {
-                WindowToken token = exitingTokens.get(i);
-                if (!token.hasVisible) {
-                    exitingTokens.remove(i);
-                }
-            }
-        }
-
-        // Time to remove any exiting applications?
-        for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
-            // Initialize state of exiting applications.
-            final AppTokenList exitingAppTokens =
-                    mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
-            for (i = exitingAppTokens.size() - 1; i >= 0; i--) {
-                final AppWindowToken token = exitingAppTokens.get(i);
-                if (!token.hasVisible && !mService.mClosingApps.contains(token) &&
-                        (!token.mIsExiting || token.isEmpty())) {
-                    // Make sure there is no animation running on this token, so any windows
-                    // associated with it will be removed as soon as their animations are complete
-                    token.mAppAnimator.clearAnimation();
-                    token.mAppAnimator.animating = false;
-                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
-                            "performLayout: App token exiting now removed" + token);
-                    token.removeIfPossible();
-                }
-            }
+            displayContent.removeExistingTokensIfPossible();
         }
 
         if (wallpaperDestroyed) {
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
new file mode 100644
index 0000000..9a6f3eb5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.RemoteAction;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.server.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+/**
+ * Controller for the stack container. This is created by activity manager to link activity stacks
+ * to the stack container they use in window manager.
+ *
+ * Test class: {@link StackWindowControllerTests}
+ */
+public class StackWindowController
+        extends WindowContainerController<TaskStack, StackWindowListener> {
+
+    final int mStackId;
+
+    private final H mHandler;
+
+    public StackWindowController(int stackId, StackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds) {
+        this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+    }
+
+    @VisibleForTesting
+    public StackWindowController(int stackId, StackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(listener, service);
+        mStackId = stackId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
+
+        synchronized (mWindowMap) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
+            stack.setController(this);
+            getRawBounds(outBounds);
+        }
+    }
+
+    @Override
+    public void removeContainer() {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.removeIfPossible();
+                super.removeContainer();
+            }
+        }
+    }
+
+    public void reparent(int displayId, Rect outStackBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+                        + " to displayId=" + displayId);
+            }
+
+            final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+            if (targetDc == null) {
+                throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            targetDc.moveStackToDisplay(mContainer);
+            getRawBounds(outStackBounds);
+        }
+    }
+
+    public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
+            Configuration overrideConfig) {
+        synchronized (mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+                    + " at " + position);
+            if (child.mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find task=" + this);
+                return;
+            }
+            if (mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find stack for task=" + mContainer);
+                return;
+            }
+            child.mContainer.positionAt(position, bounds, overrideConfig);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(false);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtBottom(TaskWindowContainerController child) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(true);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    /**
+     * Re-sizes a stack and its containing tasks.
+     *
+     * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
+     * @param configs Configurations for tasks in the resized stack, keyed by task id.
+     * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
+     * @return True if the stack is now fullscreen.
+     */
+    public boolean resize(Rect bounds, SparseArray<Configuration> configs,
+            SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+            }
+            // We might trigger a configuration change. Save the current task bounds for freezing.
+            mContainer.prepareFreezingTaskBounds();
+            if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+                    && mContainer.isVisible()) {
+                mContainer.getDisplayContent().setLayoutNeeded();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+            return mContainer.getRawFullscreen();
+        }
+    }
+
+    // TODO: This and similar methods should be separated into PinnedStackWindowContainerController
+    public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Pinned stack container not found :(");
+            }
+            final Rect originalBounds = new Rect();
+            mContainer.getBounds(originalBounds);
+            mContainer.setAnimatingBounds(bounds);
+            UiThread.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    mService.mBoundsAnimationController.animateBounds(
+                            mContainer, originalBounds, bounds, animationDuration);
+                }
+            });
+        }
+    }
+
+    /** Sets the current picture-in-picture aspect ratio. */
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            final int displayId = mContainer.getDisplayContent().getDisplayId();
+            final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+            animateResizePinnedStack(toBounds, -1 /* duration */);
+        }
+    }
+
+    public void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getStackDockedModeBoundsLocked(outBounds, ignoreVisibility);
+                return;
+            }
+            outBounds.setEmpty();
+        }
+    }
+
+    public void prepareFreezingTaskBounds() {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+                        + " not found.");
+            }
+            mContainer.prepareFreezingTaskBounds();
+        }
+    }
+
+    /** Sets the current picture-in-picture actions. */
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
+        }
+    }
+
+    private void getRawBounds(Rect outBounds) {
+        if (mContainer.getRawFullscreen()) {
+            outBounds.setEmpty();
+        } else {
+            mContainer.getRawBounds(outBounds);
+        }
+    }
+
+    public void getBounds(Rect outBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getBounds(outBounds);
+                return;
+            }
+            outBounds.setEmpty();
+        }
+    }
+
+    public Rect getBoundsForNewConfiguration() {
+        synchronized(mWindowMap) {
+            final Rect outBounds = new Rect();
+            mContainer.getBoundsForNewConfiguration(outBounds);
+            return outBounds;
+        }
+    }
+
+    void requestResize(Rect bounds) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
+    }
+
+    @Override
+    public String toString() {
+        return "{StackWindowController stackId=" + mStackId + "}";
+    }
+
+    private static final class H extends Handler {
+
+        static final int REQUEST_RESIZE = 0;
+
+        private final WeakReference<StackWindowController> mController;
+
+        H(WeakReference<StackWindowController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final StackWindowController controller = mController.get();
+            final StackWindowListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj);
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/StackWindowListener.java b/services/core/java/com/android/server/wm/StackWindowListener.java
new file mode 100644
index 0000000..c763c17
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link StackWindowController} to listen to changes with
+ * the stack container.
+ */
+public interface StackWindowListener extends WindowContainerListener {
+
+    /** Called when the stack container would like its controller to resize. */
+    void requestResize(Rect bounds);
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c5ed6ec..d96e1ef 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -27,7 +27,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
 
 import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskDescription;
@@ -567,11 +566,10 @@
 
         displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
-            // Post message to inform activity manager of the bounds change simulating a one-way
-            // call. We do this to prevent a deadlock between window manager lock and activity
-            // manager lock been held.
-            mService.mH.obtainMessage(RESIZE_TASK, mTaskId,
-                    RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mBounds).sendToTarget();
+            final TaskWindowContainerController controller = getController();
+            if (controller != null) {
+                controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 53292bb..d3eae8c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -35,17 +35,12 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 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.WindowManagerService.H.RESIZE_STACK;
 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.app.ActivityManager.StackId;
-import android.app.IActivityManager;
 import android.content.res.Configuration;
-import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Debug;
 import android.os.RemoteException;
 import android.util.EventLog;
 import android.util.Slog;
@@ -53,11 +48,9 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 
-import android.view.WindowManagerPolicy;
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.internal.policy.DockedDividerUtils;
-import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -206,14 +199,6 @@
         }
     }
 
-    boolean isFullscreenBounds(Rect bounds) {
-        if (mDisplayContent == null || bounds == null) {
-            return true;
-        }
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
-        return mTmpRect.equals(bounds);
-    }
-
     /**
      * Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from
      * the normal task bounds.
@@ -658,7 +643,7 @@
 
         final Rect oldBounds = new Rect(mBounds);
         Rect bounds = null;
-        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
         if (mStackId == DOCKED_STACK_ID
                 || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
                         && !dockedStack.fillsParent())) {
@@ -697,7 +682,7 @@
             return;
         }
 
-        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
@@ -805,8 +790,12 @@
 
         final Rect bounds = new Rect();
         getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/);
-        mService.mH.obtainMessage(RESIZE_STACK, DOCKED_STACK_ID,
-                1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
+        getController().requestResize(bounds);
+    }
+
+    @Override
+    StackWindowController getController() {
+        return (StackWindowController) super.getController();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index fbc3389..11667c0 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -27,6 +27,8 @@
 import android.util.Slog;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.ref.WeakReference;
+
 import static com.android.server.EventLogTags.WM_TASK_CREATED;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -43,43 +45,38 @@
 public class TaskWindowContainerController
         extends WindowContainerController<Task, TaskWindowContainerListener> {
 
-    private static final int REPORT_SNAPSHOT_CHANGED = 0;
-
     private final int mTaskId;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case REPORT_SNAPSHOT_CHANGED:
-                    if (mListener != null) {
-                        mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
-                    }
-                    break;
-            }
-        }
-    };
+    private final H mHandler;
 
     public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
-            int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
-            boolean supportsPictureInPicture, boolean homeTask, boolean isOnTopLauncher,
-            boolean toTop, boolean showForAllUsers, TaskDescription taskDescription) {
-        super(listener, WindowManagerService.getInstance());
+            StackWindowController stackController, int userId, Rect bounds,
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription) {
+        this(taskId, listener, stackController, userId, bounds, overrideConfig, resizeMode,
+                supportsPictureInPicture, homeTask, isOnTopLauncher, toTop, showForAllUsers,
+                taskDescription, WindowManagerService.getInstance());
+    }
+
+    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+            StackWindowController stackController, int userId, Rect bounds,
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription, WindowManagerService service) {
+        super(listener, service);
         mTaskId = taskId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
 
         synchronized(mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
-                    + " stackId=" + stackId + " bounds=" + bounds);
+                    + " stack=" + stackController + " bounds=" + bounds);
 
-            // TODO: Pass controller for the stack to get the container object when stack is
-            // switched to use controller.
-            final TaskStack stack = mService.mStackIdToStack.get(stackId);
+            final TaskStack stack = stackController.mContainer;
             if (stack == null) {
-                throw new IllegalArgumentException("TaskWindowContainerController: invalid stackId="
-                        + stackId);
+                throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
+                        + stackController);
             }
-            EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
+            EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
             final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
                     supportsPictureInPicture, homeTask, isOnTopLauncher, taskDescription);
             final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
@@ -124,21 +121,22 @@
         }
     }
 
-    public void reparent(int stackId, int position) {
+    public void reparent(StackWindowController stackController, int position) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
-                    + " to stackId=" + stackId + " at " + position);
+                    + " to stack=" + stackController + " at " + position);
             if (mContainer == null) {
                 if (DEBUG_STACK) Slog.i(TAG_WM,
                         "reparent: could not find taskId=" + mTaskId);
                 return;
             }
-            final TaskStack stack = mService.mStackIdToStack.get(stackId);
+            final TaskStack stack = stackController.mContainer;
             if (stack == null) {
-                throw new IllegalArgumentException("reparent: could not find stackId=" + stackId);
+                throw new IllegalArgumentException("reparent: could not find stack="
+                        + stackController);
             }
             mContainer.reparent(stack, position);
-            mService.mWindowPlacerLocked.performSurfacePlacement();
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
         }
     }
 
@@ -158,68 +156,11 @@
             }
 
             if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
-                mContainer.getDisplayContent().setLayoutNeeded();
-                mService.mWindowPlacerLocked.performSurfacePlacement();
+                mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
             }
         }
     }
 
-    // TODO: Move to positionChildAt() in stack controller once we have a stack controller.
-    public void positionAt(int position, Rect bounds, Configuration overrideConfig) {
-        synchronized (mWindowMap) {
-            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning taskId=" + mTaskId
-                    + " at " + position);
-            if (mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionAt: could not find taskId=" + mTaskId);
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            if (stack == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionAt: could not find stack for task=" + mContainer);
-                return;
-            }
-            mContainer.positionAt(position, bounds, overrideConfig);
-            final DisplayContent displayContent = stack.getDisplayContent();
-            displayContent.setLayoutNeeded();
-            mService.mWindowPlacerLocked.performSurfacePlacement();
-        }
-    }
-
-    // TODO: Replace with moveChildToTop in stack controller?
-    public void moveToTop(boolean includingParents) {
-        synchronized(mWindowMap) {
-            if (mContainer == null) {
-                Slog.e(TAG_WM, "moveToTop: taskId=" + mTaskId + " not found");
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            stack.positionChildAt(POSITION_TOP, mContainer, includingParents);
-
-            if (mService.mAppTransition.isTransitionSet()) {
-                mContainer.setSendingToBottom(false);
-            }
-            stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-        }
-    }
-
-    // TODO: Replace with moveChildToBottom in stack controller?
-    public void moveToBottom() {
-        synchronized(mWindowMap) {
-            if (mContainer == null) {
-                Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + mTaskId + " not found");
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            stack.positionChildAt(POSITION_BOTTOM, mContainer, false /* includingParents */);
-            if (mService.mAppTransition.isTransitionSet()) {
-                mContainer.setSendingToBottom(true);
-            }
-            stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-        }
-    }
-
     public void getBounds(Rect bounds) {
         synchronized (mWindowMap) {
             if (mContainer != null) {
@@ -276,11 +217,46 @@
     }
 
     void reportSnapshotChanged(TaskSnapshot snapshot) {
-        mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+        mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+    }
+
+    void requestResize(Rect bounds, int resizeMode) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
     }
 
     @Override
     public String toString() {
         return "{TaskWindowContainerController taskId=" + mTaskId + "}";
     }
+
+    private static final class H extends Handler {
+
+        static final int REPORT_SNAPSHOT_CHANGED = 0;
+        static final int REQUEST_RESIZE = 1;
+
+        private final WeakReference<TaskWindowContainerController> mController;
+
+        H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final TaskWindowContainerController controller = mController.get();
+            final TaskWindowContainerListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REPORT_SNAPSHOT_CHANGED:
+                    listener.onSnapshotChanged((TaskSnapshot) msg.obj);
+                    break;
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj, msg.arg1);
+                    break;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
index 61b202d..af67de3 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
@@ -17,14 +17,17 @@
 package com.android.server.wm;
 
 import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Rect;
 
 /**
- * Interface used by the creator of the controller to listen to changes with the container.
+ * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
+ * the task container.
  */
 public interface TaskWindowContainerListener extends WindowContainerListener {
 
-    /**
-     * Called when the snapshot of this task has changed.
-     */
+    /** Called when the snapshot of this task has changed. */
     void onSnapshotChanged(TaskSnapshot snapshot);
+
+    /** Called when the task container would like its controller to resize. */
+    void requestResize(Rect bounds, int resizeMode);
 }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index feceb8e..c32e689 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -447,7 +447,7 @@
 
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
-        if (mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+        if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible.
             mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f4bd2..1987f90 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -706,12 +706,7 @@
 
     final WindowAnimator mAnimator;
 
-    private final BoundsAnimationController mBoundsAnimationController;
-
-    /** All of the TaskStacks in the window manager, unordered. For an ordered list call
-     * DisplayContent.getStacks(). */
-    // TODO: Don't believe this is needed with the WindowContainer model.
-    SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
+    final BoundsAnimationController mBoundsAnimationController;
 
     private final PointerEventDispatcher mPointerEventDispatcher;
 
@@ -2573,16 +2568,6 @@
         }
     }
 
-    @Override
-    public Rect getBoundsForNewConfiguration(int stackId) {
-        synchronized(mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            final Rect outBounds = new Rect();
-            stack.getBoundsForNewConfiguration(outBounds);
-            return outBounds;
-        }
-    }
-
     void setFocusTaskRegionLocked() {
         final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
         if (focusedTask != null) {
@@ -2827,11 +2812,6 @@
         }
     }
 
-    boolean isStackVisibleLocked(int stackId) {
-        final TaskStack stack = mStackIdToStack.get(stackId);
-        return (stack != null && stack.isVisible());
-    }
-
     public void setDockedStackCreateState(int mode, Rect bounds) {
         synchronized (mWindowMap) {
             setDockedStackCreateStateLocked(mode, bounds);
@@ -2880,17 +2860,19 @@
             }
 
             final Rect stackBounds;
-            final DisplayContent displayContent;
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                return null;
+            }
+
+            final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID);
             if (stack != null) {
                 // If the stack exists, then use its final bounds to calculate the new aspect ratio
                 // bounds.
-                displayContent = stack.getDisplayContent();
                 stackBounds = new Rect();
                 stack.getAnimatingBounds(stackBounds);
             } else {
                 // Otherwise, just calculate the aspect ratio bounds from the default bounds
-                displayContent = mRoot.getDisplayContent(displayId);
                 stackBounds = displayContent.getPinnedStackController().getDefaultBounds();
             }
             return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds,
@@ -2898,119 +2880,10 @@
         }
     }
 
-    /**
-     * Sets the current picture-in-picture aspect ratio.
-     */
-    public void setPictureInPictureAspectRatio(float aspectRatio) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (!mSupportsPictureInPicture || stack == null) {
-                return;
-            }
-
-            final int displayId = stack.getDisplayContent().getDisplayId();
-            final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio);
-            animateResizePinnedStack(toBounds, -1 /* duration */);
-        }
-    }
-
-    /**
-     * Sets the current picture-in-picture actions.
-     */
-    public void setPictureInPictureActions(List<RemoteAction> actions) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (!mSupportsPictureInPicture || stack == null) {
-                return;
-            }
-
-            stack.getDisplayContent().getPinnedStackController().setActions(actions);
-        }
-    }
-
-    /**
-     * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
-     * specified stackId.
-     * @param stackId The unique identifier of the new stack.
-     * @param displayId The unique identifier of the DisplayContent.
-     * @param onTop If true the stack will be place at the top of the display,
-     *              else at the bottom.
-     * @return The bounds that the stack has after adding. null means fullscreen.
-     */
-    public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mWindowMap) {
-                final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                if (dc == null) {
-                    throw new IllegalArgumentException("Trying to add stackId=" + stackId
-                            + " to unknown displayId=" + displayId);
-                }
-
-                return dc.addStackToDisplay(stackId, onTop);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    /**
-     * Move a TaskStack from current DisplayContent to specified one.
-     * @param stackId The unique identifier of the new stack.
-     * @param displayId The unique identifier of the new display.
-     */
-    public Rect moveStackToDisplay(int stackId, int displayId) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mWindowMap) {
-                TaskStack stack = mStackIdToStack.get(stackId);
-                if (stack == null) {
-                    throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
-                            + " to displayId=" + displayId);
-                }
-
-                final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
-                if (targetDisplayContent == null) {
-                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
-                            + " to unknown displayId=" + displayId);
-                }
-
-                return targetDisplayContent.moveStackToDisplay(stack);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    /**
-     * Remove a TaskStack completely.
-     * @param stackId The unique identifier of the stack.
-     */
-    public void removeStack(int stackId) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack != null) {
-                stack.removeIfPossible();
-                mStackIdToStack.remove(stackId);
-            }
-        }
-    }
-
-    public void getStackDockedModeBounds(int stackId, Rect bounds, boolean ignoreVisibility) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack != null) {
-                stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibility);
-                return;
-            }
-            bounds.setEmpty();
-        }
-    }
-
     @Override
     public void getStackBounds(int stackId, Rect bounds) {
         synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
+            final TaskStack stack = mRoot.getStackById(stackId);
             if (stack != null) {
                 stack.getBounds(bounds);
                 return;
@@ -3035,43 +2908,6 @@
     }
 
     /**
-     * Re-sizes a stack and its containing tasks.
-     * @param stackId Id of stack to resize.
-     * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
-     * @param configs Configurations for tasks in the resized stack, keyed by task id.
-     * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
-     * @return True if the stack is now fullscreen.
-     * */
-    public boolean resizeStack(int stackId, Rect bounds,
-            SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
-            SparseArray<Rect> taskTempInsetBounds) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack == null) {
-                throw new IllegalArgumentException("resizeStack: stackId " + stackId
-                        + " not found.");
-            }
-            if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
-                    && stack.isVisible()) {
-                stack.getDisplayContent().setLayoutNeeded();
-                mWindowPlacerLocked.performSurfacePlacement();
-            }
-            return stack.getRawFullscreen();
-        }
-    }
-
-    public void prepareFreezingTaskBounds(int stackId) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack == null) {
-                throw new IllegalArgumentException("prepareFreezingTaskBounds: stackId " + stackId
-                        + " not found.");
-            }
-            stack.prepareFreezingTaskBounds();
-        }
-    }
-
-    /**
      * Starts deferring layout passes. Useful when doing multiple changes but to optimize
      * performance, only one layout pass should be done. This can be called multiple times, and
      * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
@@ -3505,8 +3341,9 @@
 
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
-            displayContent.mDividerControllerLocked
-                    .notifyDockedStackExistsChanged(hasDockedTasksForUser(newUserId));
+            final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+            displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
+                    stack != null && stack.hasTaskForUser(newUserId));
 
             // If the display is already prepared, update the density.
             // Otherwise, we'll update it when it's prepared.
@@ -3519,15 +3356,6 @@
         }
     }
 
-    /** Returns whether there is a docked task for the current user. */
-    boolean hasDockedTasksForUser(int userId) {
-        final TaskStack stack = mStackIdToStack.get(DOCKED_STACK_ID);
-        if (stack == null) {
-            return false;
-        }
-        return stack.hasTaskForUser(userId);
-    }
-
     /* Called by WindowState */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUserId) return true;
@@ -5417,8 +5245,6 @@
 
         public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
 
-        public static final int RESIZE_STACK = 42;
-        public static final int RESIZE_TASK = 43;
         public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
 
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
@@ -5844,23 +5670,6 @@
                     }
                 }
                 break;
-                case RESIZE_TASK: {
-                    try {
-                        mActivityManager.resizeTask(msg.arg1, (Rect) msg.obj, msg.arg2);
-                    } catch (RemoteException e) {
-                        // This will not happen since we are in the same process.
-                    }
-                }
-                break;
-                case RESIZE_STACK: {
-                    try {
-                        mActivityManager.resizeStack(
-                                msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false, false, -1);
-                    } catch (RemoteException e) {
-                        // This will not happen since we are in the same process.
-                    }
-                }
-                break;
                 case WINDOW_REPLACEMENT_TIMEOUT: {
                     synchronized (mWindowMap) {
                         for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
@@ -7646,26 +7455,6 @@
         }
     }
 
-    public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (stack == null) {
-                Slog.w(TAG, "animateResizePinnedStack: stackId " + PINNED_STACK_ID + " not found.");
-                return;
-            }
-            final Rect originalBounds = new Rect();
-            stack.getBounds(originalBounds);
-            stack.setAnimatingBounds(bounds);
-            UiThread.getHandler().post(new Runnable() {
-                @Override
-                public void run() {
-                    mBoundsAnimationController.animateBounds(
-                            stack, originalBounds, bounds, animationDuration);
-                }
-            });
-        }
-    }
-
     public void setForceResizableTasks(boolean forceResizableTasks) {
         synchronized (mWindowMap) {
             mForceResizableTasks = forceResizableTasks;
@@ -8123,7 +7912,8 @@
         @Override
         public boolean isStackVisible(int stackId) {
             synchronized (mWindowMap) {
-                return WindowManagerService.this.isStackVisibleLocked(stackId);
+                final DisplayContent dc = getDefaultDisplayContentLocked();
+                return dc.isStackVisible(stackId);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 10aebe6..13358da8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2304,6 +2304,7 @@
                     final WindowState win = mService.windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
+                        final DisplayContent dc = getDisplayContent();
                         if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) {
                             mService.mTaskSnapshotController.onAppDied(win.mAppToken);
                         }
@@ -2313,7 +2314,7 @@
                             // just in case they have the divider at an unstable position. Better
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
-                            final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+                            final TaskStack stack = dc.getDockedStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 1d9875f..154fa91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -119,6 +119,7 @@
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
         assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sWm.mLastOrientation);
         assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
     }
 
     @Test
@@ -148,5 +149,6 @@
         sWm.updateRotation(false, false);
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
         assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 85931e8..e54e319 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -236,7 +236,7 @@
         assertEquals(dc, token.getDisplayContent());
 
         // Move stack to first display.
-        sWm.moveStackToDisplay(stack.mStackId, sDisplayContent.getDisplayId());
+        sDisplayContent.moveStackToDisplay(stack);
         assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
         assertEquals(sDisplayContent, stack.getParent().getParent());
         assertEquals(sDisplayContent, stack.getDisplayContent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
new file mode 100644
index 0000000..7a789d4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
+import android.view.Display;
+import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link StackWindowController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.StackWindowControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class StackWindowControllerTests extends WindowTestsBase {
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final Task task = taskController.mContainer;
+        assertNotNull(stack);
+        assertNotNull(task);
+        stackController.removeContainer();
+        // Assert that the container was removed.
+        assertNull(stackController.mContainer);
+        assertNull(taskController.mContainer);
+        assertNull(stack.getDisplayContent());
+        assertNull(task.getDisplayContent());
+        assertNull(task.mStack);
+    }
+
+    @Test
+    public void testRemoveContainer_deferRemoval() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final TestTask task = (TestTask) taskController.mContainer;
+        // Stack removal is deferred if one of its child is animating.
+        task.setLocalIsAnimating(true);
+
+        stackController.removeContainer();
+        // For the case of deferred removal the stack controller will no longer be connected to the
+        // container, but the task controller will still be connected to the its container until
+        // the stack window container is removed.
+        assertNull(stackController.mContainer);
+        assertNull(stack.getController());
+        assertNotNull(taskController.mContainer);
+        assertNotNull(task.getController());
+
+        stack.removeImmediately();
+        assertNull(taskController.mContainer);
+        assertNull(task.getController());
+    }
+
+    @Test
+    public void testReparent() throws Exception {
+        // Create first stack on primary display.
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stack1Controller);
+        final TestTask task1 = (TestTask) taskController.mContainer;
+        task1.mOnDisplayChangedCalled = false;
+
+        // Create second display and put second stack on it.
+        final Display display = new Display(DisplayManagerGlobal.getInstance(),
+                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
+                new WallpaperController(sWm));
+        sWm.mRoot.addChild(dc, 1);
+
+        final StackWindowController stack2Controller =
+                createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
+
+        // Reparent
+        stack1Controller.reparent(dc.getDisplayId(), new Rect());
+        assertEquals(dc, stack1.getDisplayContent());
+        final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
+        final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
+        assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
+        assertTrue(task1.mOnDisplayChangedCalled);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
index bb9bc9e..462bd68 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -17,17 +17,16 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.After;
 
-import android.hardware.display.DisplayManagerGlobal;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,95 +45,69 @@
 @RunWith(AndroidJUnit4.class)
 public class TaskStackContainersTests extends WindowTestsBase {
 
+    private TaskStack mPinnedStack;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mPinnedStack = new StackWindowController(PINNED_STACK_ID, null,
+                sDisplayContent.getDisplayId(), true /* onTop */, new Rect(), sWm).mContainer;
+
+        // Stack should contain visible app window to be considered visible.
+        final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
+        assertFalse(mPinnedStack.isVisible());
+        final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
+        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+        assertTrue(mPinnedStack.isVisible());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPinnedStack.removeImmediately();
+    }
+
     @Test
     public void testStackPositionChildAt() throws Exception {
         // Test that always-on-top stack can't be moved to position other than top.
         final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
         final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
-        final TaskStack pinnedStack = addPinnedStack();
 
         final WindowContainer taskStackContainer = stack1.getParent();
 
         final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
         final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
         assertGreaterThan(pinnedStackPos, stack2Pos);
         assertGreaterThan(stack2Pos, stack1Pos);
 
-        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
 
-        taskStackContainer.positionChildAt(1, pinnedStack, false);
+        taskStackContainer.positionChildAt(1, mPinnedStack, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
     }
+
     @Test
     public void testStackPositionBelowPinnedStack() throws Exception {
         // Test that no stack can be above pinned stack.
-        final TaskStack pinnedStack = addPinnedStack();
         final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
 
         final WindowContainer taskStackContainer = stack1.getParent();
 
         final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
         assertGreaterThan(pinnedStackPos, stackPos);
 
         taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
 
         taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
-    }
-
-    @Test
-    public void testReparentBetweenDisplays() throws Exception {
-        // Create first stack on primary display.
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
-        final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
-        final TestTask task1 = (TestTask) taskController.mContainer;
-        task1.mOnDisplayChangedCalled = false;
-
-        // Create second display and put second stack on it.
-        final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
-                DEFAULT_DISPLAY_ADJUSTMENTS);
-        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(dc, 1);
-        final TaskStack stack2 = createTaskStackOnDisplay(dc);
-
-        // Reparent and check state.DisplayContent.java:2572
-        sWm.moveStackToDisplay(stack1.mStackId, dc.getDisplayId());
-        assertEquals(dc, stack1.getDisplayContent());
-        final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
-        final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
-        assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
-        assertTrue(task1.mOnDisplayChangedCalled);
-    }
-
-    private TaskStack addPinnedStack() {
-        TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
-        if (pinnedStack == null) {
-            sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
-            pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
-        }
-
-        if (!pinnedStack.isVisible()) {
-            // Stack should contain visible app window to be considered visible.
-            final Task pinnedTask = createTaskInStack(pinnedStack, 0 /* userId */);
-            assertFalse(pinnedStack.isVisible());
-            final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
-            pinnedTask.addChild(pinnedApp, 0 /* addPos */);
-            assertTrue(pinnedStack.isVisible());
-        }
-
-        return pinnedStack;
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
index 7cd3f64..3ee1da43 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -18,14 +18,14 @@
 
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
-import org.junit.Test;
-
 import android.hardware.display.DisplayManagerGlobal;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.Display;
 import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -40,7 +40,7 @@
  */
 @SmallTest
 @Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
+@RunWith(AndroidJUnit4.class)
 public class TaskWindowContainerControllerTests extends WindowTestsBase {
 
     @Test
@@ -57,7 +57,7 @@
     }
 
     @Test
-    public void testRemoveContainer_DeferRemoval() throws Exception {
+    public void testRemoveContainer_deferRemoval() throws Exception {
         final TestTaskWindowContainerController taskController =
                 new TestTaskWindowContainerController();
         final TestAppWindowContainerController appController =
@@ -83,58 +83,66 @@
 
     @Test
     public void testReparent() throws Exception {
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(sDisplayContent);
         final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
-        final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
+                new TestTaskWindowContainerController(stackController1);
+        final StackWindowController stackController2 =
+                createStackControllerOnDisplay(sDisplayContent);
         final TestTaskWindowContainerController taskController2 =
-                new TestTaskWindowContainerController(stack2.mStackId);
+                new TestTaskWindowContainerController(stackController2);
 
         boolean gotException = false;
         try {
-            taskController.reparent(stack1.mStackId, 0);
+            taskController.reparent(stackController1, 0);
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
         assertTrue("Should not be able to reparent to the same parent", gotException);
 
+        final StackWindowController stackController3 =
+                createStackControllerOnDisplay(sDisplayContent);
+        stackController3.setContainer(null);
         gotException = false;
         try {
-            taskController.reparent(sNextStackId + 1, 0);
+            taskController.reparent(stackController3, 0);
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
-        assertTrue("Should not be able to reparent to a stackId that doesn't exist", gotException);
+        assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+                gotException);
 
-        taskController.reparent(stack2.mStackId, 0);
-        assertEquals(stack2, taskController.mContainer.getParent());
+        taskController.reparent(stackController2, 0);
+        assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
         assertEquals(0, ((TestTask) taskController.mContainer).positionInParent());
         assertEquals(1, ((TestTask) taskController2.mContainer).positionInParent());
     }
 
     @Test
-    public void testReparentBetweenDisplays() throws Exception {
+    public void testReparent_BetweenDisplays() throws Exception {
         // Create first stack on primary display.
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
         final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
+                new TestTaskWindowContainerController(stack1Controller);
         final TestTask task1 = (TestTask) taskController.mContainer;
         task1.mOnDisplayChangedCalled = false;
 
         // Create second display and put second stack on it.
         final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
-                DEFAULT_DISPLAY_ADJUSTMENTS);
+                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
         final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
                 new WallpaperController(sWm));
         sWm.mRoot.addChild(dc, 1);
-        final TaskStack stack2 = createTaskStackOnDisplay(dc);
+        final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
         final TestTaskWindowContainerController taskController2 =
-                new TestTaskWindowContainerController(stack2.mStackId);
+                new TestTaskWindowContainerController(stack2Controller);
         final TestTask task2 = (TestTask) taskController2.mContainer;
 
         // Reparent and check state
-        taskController.reparent(stack2.mStackId, 0);
+        taskController.reparent(stack2Controller, 0);
         assertEquals(stack2, task1.getParent());
         assertEquals(0, task1.positionInParent());
         assertEquals(1, task2.positionInParent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 847f34d..72157b6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -28,6 +28,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import android.app.ActivityManager.TaskSnapshot;
 import android.content.Context;
 import android.os.IBinder;
 import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,10 @@
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
         sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
         sLayersController = new WindowLayersController(sWm);
+        sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
+        if (sDisplayContent != null) {
+            sDisplayContent.removeImmediately();
+        }
         sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
                 new WallpaperController(sWm));
         sWm.mRoot.addChild(sDisplayContent, 0);
@@ -173,9 +178,13 @@
 
     /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
     static TaskStack createTaskStackOnDisplay(DisplayContent dc) {
-        final int stackId = sNextStackId++;
-        dc.addStackToDisplay(stackId, true);
-        return sWm.mStackIdToStack.get(stackId);
+        return createStackControllerOnDisplay(dc).mContainer;
+    }
+
+    static StackWindowController createStackControllerOnDisplay(DisplayContent dc) {
+        final int stackId = ++sNextStackId;
+        return new StackWindowController(stackId, null, dc.getDisplayId(),
+                true /* onTop */, new Rect(), sWm);
     }
 
     /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
@@ -235,6 +244,8 @@
 
         boolean mShouldDeferRemoval = false;
         boolean mOnDisplayChangedCalled = false;
+        private boolean mUseLocalIsAnimating = false;
+        private boolean mIsAnimating = false;
 
         TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
                 Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
@@ -258,6 +269,16 @@
             super.onDisplayChanged(dc);
             mOnDisplayChangedCalled = true;
         }
+
+        @Override
+        boolean isAnimating() {
+            return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating();
+        }
+
+        void setLocalIsAnimating(boolean isAnimating) {
+            mUseLocalIsAnimating = true;
+            mIsAnimating = isAnimating;
+        }
     }
 
     /**
@@ -267,15 +288,25 @@
     class TestTaskWindowContainerController extends TaskWindowContainerController {
 
         TestTaskWindowContainerController() {
-            this(createTaskStackOnDisplay(sDisplayContent).mStackId);
+            this(createStackControllerOnDisplay(sDisplayContent));
         }
 
-        TestTaskWindowContainerController(int stackId) {
-            super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
+        TestTaskWindowContainerController(StackWindowController stackController) {
+            super(sNextTaskId++, new TaskWindowContainerListener() {
+                        @Override
+                        public void onSnapshotChanged(TaskSnapshot snapshot) {
+
+                        }
+
+                        @Override
+                        public void requestResize(Rect bounds, int resizeMode) {
+
+                        }
+                    }, stackController, 0 /* userId */, null /* bounds */,
                     EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE,
                     false /* supportsPictureInPicture */, false /* homeTask*/,
                     false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */,
-                    new TaskDescription());
+                    new TaskDescription(), sWm);
         }
 
         @Override