Added support for docked stack

Docked stacks occupy a dedicated region on a display.
When a docked stack is present all other static stacks bounds
are restricted to the region of the screen not occupied by
the docked stack.

Change-Id: I6aec3aa19c41a7e756375002f3a925977b5533b5
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d54930f..f3ea1c4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -29,6 +30,7 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
 import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
@@ -8653,7 +8655,11 @@
                     Slog.e(TAG, "setActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
                     return;
                 }
-                mStackSupervisor.resizeTaskLocked(task, bounds);
+                if (task.stack != null && task.stack.mStackId == DOCKED_STACK_ID) {
+                    mStackSupervisor.resizeStackLocked(task.stack.mStackId, bounds);
+                } else {
+                    mStackSupervisor.resizeTaskLocked(task, bounds);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -9010,7 +9016,7 @@
     public void moveActivityToStack(IBinder token, int stackId) throws RemoteException {
         if (stackId == HOME_STACK_ID) {
             throw new IllegalArgumentException(
-                    "moveTaskToStack: Attempt to move token " + token + " to home stack");
+                    "moveActivityToStack: Attempt to move token " + token + " to home stack");
         }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
@@ -9022,13 +9028,7 @@
                 }
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r
                         + " to stackId=" + stackId);
-                mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP);
-                if (mFocusedActivity != r) {
-                    setFocusedActivityLocked(r, "moveActivityToStack");
-                } else {
-                    mStackSupervisor.setFocusedStack(r, "moveActivityToStack");
-                }
-                mStackSupervisor.resumeTopActivitiesLocked(r.task.stack, null, null);
+                mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, FORCE_FOCUS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9048,7 +9048,7 @@
             try {
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
-                mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop);
+                mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9074,9 +9074,9 @@
         enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                 "positionTaskInStack()");
         if (stackId == HOME_STACK_ID) {
-            Slog.e(TAG, "positionTaskInStack: Attempt to change the position of task "
-                    + taskId + " in/to home stack",
-                    new RuntimeException("here").fillInStackTrace());
+            throw new IllegalArgumentException(
+                    "positionTaskInStack: Attempt to change the position of task "
+                    + taskId + " in/to home stack");
         }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 18a0a36..356565f 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
@@ -229,6 +230,8 @@
 
     // Whether or not this stack covers the entire screen; by default stacks are fullscreen
     boolean mFullscreen = true;
+    // Current bounds of the stack or null if fullscreen.
+    Rect mBounds = null;
 
     long mLaunchStartTime = 0;
     long mFullyDrawnStartTime = 0;
@@ -1227,8 +1230,42 @@
         return null;
     }
 
-    // Checks if any of the stacks above this one has a fullscreen activity behind it.
-    // If so, this stack is hidden, otherwise it is visible.
+    /** Returns true if the stack contains a fullscreen task. */
+    private boolean hasFullscreenTask() {
+        for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
+            final TaskRecord task = mTaskHistory.get(i);
+            if (task.mFullscreen) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Return true if this stack is hidden by the presence of a docked stack. */
+    private boolean isHiddenByDockedStack() {
+        final ActivityStack dockedStack = mStackSupervisor.getStack(DOCKED_STACK_ID);
+        if (dockedStack != null) {
+            final int dockedStackIndex = mStacks.indexOf(dockedStack);
+            final int stackIndex = mStacks.indexOf(this);
+            if (dockedStackIndex > stackIndex) {
+                // Fullscreen stacks or stacks with fullscreen task below the docked stack are not
+                // visible. We do this so we don't have the 2 stacks and their tasks overlap.
+                if (mFullscreen) {
+                    return true;
+                }
+
+                // We need to also check the tasks in the stack because they can be fullscreen
+                // even though their stack isn't due to their root activity not been resizeable
+                // (i.e. doesn't support multi-window mode).
+                if (hasFullscreenTask()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Returns true if the stack is considered visible. */
     private boolean isStackVisibleLocked() {
         if (!isAttached()) {
             return false;
@@ -1238,12 +1275,15 @@
             return true;
         }
 
-        // Any stack that isn't the front stack is not visible, except for the case of the home
-        // stack behind the main application stack since we can have dialog activities on the
-        // main application stack that need the home stack to display behind them.
-        // TODO(multi-window): Also need to add exception for side-by-side stacks.
-        final boolean homeStack = mStackId == HOME_STACK_ID;
-        if (!homeStack) {
+        final int stackIndex = mStacks.indexOf(this);
+
+        if (stackIndex == mStacks.size() - 1) {
+            Slog.wtf(TAG,
+                    "Stack=" + this + " isn't front stack but is at the top of the stack list");
+            return false;
+        }
+
+        if (isHiddenByDockedStack()) {
             return false;
         }
 
@@ -1252,13 +1292,24 @@
          * fullscreen activity, or a translucent activity that requested the
          * wallpaper to be shown behind it.
          */
-        for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) {
+        for (int i = stackIndex + 1; i < mStacks.size(); i++) {
             final ActivityStack stack = mStacks.get(i);
-            if (stack.mStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
+            final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+
+            if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
+                continue;
+            }
+
+            if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID
+                    || stack.mStackId == HOME_STACK_ID) {
+                // The freeform and home stacks can't have any other stack visible behind them
+                // when they are fullscreen since they act as base/cut-off points for visibility.
+                // NOTE: we don't cut-off at the FULLSCREEN_WORKSPACE_STACK_ID because the home
+                // stack sometimes needs to be visible behind it when it is displaying a dialog
+                // activity. We let it fall through to the logic below to determine visibility.
                 return false;
             }
 
-            final ArrayList<TaskRecord> tasks = stack.getAllTasks();
             for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = tasks.get(taskNdx);
                 // task above isn't fullscreen, so, we assume we're still visible.
@@ -2680,9 +2731,10 @@
             ActivityRecord next = topRunningActivityLocked(null);
             final String myReason = reason + " adjustFocus";
             if (next != r) {
-                if (next != null && mStackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    // For freeform stack we always keep the focus within the stack as long as
-                    // there is a running activity in the stack that we can adjust focus to.
+                if (next != null && (mStackId == FREEFORM_WORKSPACE_STACK_ID
+                        || mStackId == DOCKED_STACK_ID)) {
+                    // For freeform and docked stacks we always keep the focus within the stack as
+                    // long as there is a running activity in the stack that we can adjust focus to.
                     mService.setFocusedActivityLocked(next, myReason);
                     return;
                 } else {
@@ -4321,7 +4373,10 @@
             printed |= ActivityStackSupervisor.dumpHistoryList(fd, pw,
                     mTaskHistory.get(taskNdx).mActivities, "    ", "Hist", true, !dumpAll,
                     dumpClient, dumpPackage, needSep, header,
-                    "    Task id #" + task.taskId);
+                    "    Task id #" + task.taskId + "\n" +
+                    "    mFullscreen=" + task.mFullscreen + "\n" +
+                    "    mBounds=" + task.mBounds + "\n" +
+                    "    mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
             if (printed) {
                 header = null;
             }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a449baf..5a338af 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -184,6 +184,9 @@
     // (e.g. stack) is due to it moving to another container.
     static final boolean MOVING = true;
 
+    // Force the focus to change to the stack we are moving a task to..
+    static final boolean FORCE_FOCUS = true;
+
     // Activity actions an app cannot start if it uses a permission which is not granted.
     private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
             new ArrayMap<>();
@@ -328,6 +331,9 @@
     /** Used to keep resumeTopActivityLocked() from being entered recursively */
     boolean inResumeTopActivity;
 
+    // temp. rect used during resize calculation so we don't need to create a new object each time.
+    private final Rect tempRect = new Rect();
+
     /**
      * Description of a request to start a new activity, which has been held
      * due to app switches being disabled.
@@ -2908,16 +2914,13 @@
             return;
         }
 
-        final ActivityRecord r = stack.topRunningActivityLocked(null);
-        if (r != null && !r.task.mResizeable) {
-            Slog.w(TAG, "resizeStack: top task " + r.task + " not resizeable.");
-            return;
-        }
+        ActivityRecord r = stack.topRunningActivityLocked(null);
+        final boolean resizeTasks = r != null && r.task.mResizeable;
 
         final IntArray changedTaskIds = new IntArray(stack.numTasks());
         final List<Configuration> newTaskConfigs = new ArrayList<>(stack.numTasks());
-        stack.mFullscreen =
-                mWindowManager.resizeStack(stackId, bounds, changedTaskIds, newTaskConfigs);
+        stack.mFullscreen = mWindowManager.resizeStack(
+                stackId, bounds, resizeTasks, changedTaskIds, newTaskConfigs);
         for (int i = changedTaskIds.size() - 1; i >= 0; i--) {
             final TaskRecord task = anyTaskForIdLocked(changedTaskIds.get(i), false);
             if (task == null) {
@@ -2927,6 +2930,55 @@
             task.updateOverrideConfiguration(newTaskConfigs.get(i), bounds);
         }
 
+        if (stack.mStackId == DOCKED_STACK_ID) {
+            // Dock stack funness...Yay!
+            if (stack.mFullscreen) {
+                // The dock stack went fullscreen which is kinda like dismissing it.
+                // In this case we make all other static stacks fullscreen and move all
+                // docked stack tasks to the fullscreen stack.
+                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+                    if (i != DOCKED_STACK_ID) {
+                        resizeStackLocked(i, null);
+                    }
+                }
+
+                final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+                final int count = tasks.size();
+                for (int i = 0; i < count; i++) {
+                    moveTaskToStackLocked(tasks.get(i).taskId,
+                            FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS);
+                }
+
+                // stack shouldn't contain anymore activities, so nothing to resume.
+                r = null;
+            } else {
+                // Docked stacks occupy a dedicated region on screen so the size of all other
+                // static stacks need to be adjusted so they don't overlap with the docked stack.
+                final int leftChange = stack.mBounds.left - bounds.left;
+                final int rightChange = stack.mBounds.right - bounds.right;
+                final int topChange = stack.mBounds.top - bounds.top;
+                final int bottomChange = stack.mBounds.bottom - bounds.bottom;
+
+                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
+                    if (i != DOCKED_STACK_ID) {
+                        ActivityStack otherStack = getStack(i);
+                        if (otherStack != null) {
+                            tempRect.set(otherStack.mBounds);
+                            // We adjust the opposing sides of the other stacks to
+                            // the side in the dock stack that changed.
+                            tempRect.left -= rightChange;
+                            tempRect.right -= leftChange;
+                            tempRect.top -= bottomChange;
+                            tempRect.bottom -= topChange;
+                            resizeStackLocked(i, tempRect);
+                        }
+                    }
+                }
+
+            }
+        }
+        stack.mBounds = stack.mFullscreen ? null : new Rect(bounds);
+
         if (r != null) {
             final boolean updated = stack.ensureActivityConfigurationLocked(r, 0);
             // And we need to make sure at this point that all other activities
@@ -2968,7 +3020,8 @@
         final boolean wasFrontStack = isFrontStack(task.stack);
         if (bounds == null && stackId != FULLSCREEN_WORKSPACE_STACK_ID) {
             stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-        } else if (bounds != null && task.stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+        } else if (bounds != null
+                && stackId != FREEFORM_WORKSPACE_STACK_ID && stackId != DOCKED_STACK_ID) {
             stackId = FREEFORM_WORKSPACE_STACK_ID;
         }
         if (stackId != task.stack.mStackId) {
@@ -3069,8 +3122,7 @@
      */
     private ActivityStack moveTaskToStackUncheckedLocked(
             TaskRecord task, int stackId, boolean toTop, String reason) {
-        final ActivityStack stack =
-                getStack(stackId, CREATE_IF_NEEDED, toTop);
+        final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
         mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
         if (task.stack != null) {
             task.stack.removeTask(task, reason, MOVING);
@@ -3079,26 +3131,39 @@
         return stack;
     }
 
-    void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) {
+    void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
             Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
             return;
         }
-        ActivityStack stack =
-                moveTaskToStackUncheckedLocked(task, stackId, toTop, "moveTaskToStack");
+        final String reason = "moveTaskToStack";
+        final ActivityRecord top = task.topRunningActivityLocked(null);
+        final boolean adjustFocus = forceFocus || mService.mFocusedActivity == top;
+        final ActivityStack stack =
+                moveTaskToStackUncheckedLocked(task, stackId, toTop, reason);
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
-            resizeTaskLocked(task, null);
+            resizeTaskLocked(task, stack.mBounds);
         } else if (stackId == FREEFORM_WORKSPACE_STACK_ID
                 && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
             resizeTaskLocked(task, task.mLastNonFullscreenBounds);
+        } else if (stackId == DOCKED_STACK_ID) {
+            resizeTaskLocked(task, stack.mBounds);
+        }
+
+        if (top != null && adjustFocus) {
+            if (mService.mFocusedActivity != top) {
+                mService.setFocusedActivityLocked(top, reason);
+            } else {
+                setFocusedStack(top, reason);
+            }
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
         // the visibility of the stack / windows.
-        stack.ensureActivitiesVisibleLocked(null, 0);
+        ensureActivitiesVisibleLocked(null, 0);
         resumeTopActivitiesLocked();
     }
 
@@ -3659,6 +3724,10 @@
                 stackHeader.append("  Stack #");
                 stackHeader.append(stack.mStackId);
                 stackHeader.append(":");
+                stackHeader.append("\n");
+                stackHeader.append("  mFullscreen=" + stack.mFullscreen);
+                stackHeader.append("\n");
+                stackHeader.append("  mBounds=" + stack.mBounds);
                 printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
                         needSep, stackHeader.toString());
                 printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, "    ", "Run", false,
@@ -4313,7 +4382,9 @@
             mStack.mStacks = activityDisplay.mStacks;
 
             activityDisplay.attachActivities(mStack, onTop);
-            mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
+            mStack.mBounds =
+                    mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
+            mStack.mFullscreen = mStack.mBounds == null;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index dd57f80..8fa0ae6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.DOCKED_STACK_ID;
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
@@ -1172,12 +1173,15 @@
         // is necessarily fullscreen.
         mFullscreen = Configuration.EMPTY.equals(mOverrideConfig);
         if (mFullscreen) {
-            if (mBounds != null) {
+            if (mBounds != null && stack.mStackId != DOCKED_STACK_ID) {
                 mLastNonFullscreenBounds = mBounds;
             }
             mBounds = null;
         } else {
-            mBounds = mLastNonFullscreenBounds = new Rect(bounds);
+            mBounds = new Rect(bounds);
+            if (stack.mStackId != DOCKED_STACK_ID) {
+                mLastNonFullscreenBounds = mBounds;
+            }
         }
         return !mOverrideConfig.equals(oldConfig);
     }
@@ -1207,6 +1211,8 @@
                 || stack.mStackId == HOME_STACK_ID
                 || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
             return null;
+        } else if (stack.mStackId == DOCKED_STACK_ID) {
+            return stack.mBounds;
         }
         return mLastNonFullscreenBounds;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2b26b42..99e9fe6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -173,7 +173,7 @@
     void updateDisplayInfo() {
         mDisplay.getDisplayInfo(mDisplayInfo);
         for (int i = mStacks.size() - 1; i >= 0; --i) {
-            mStacks.get(i).updateDisplayInfo();
+            mStacks.get(i).updateDisplayInfo(null);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1eddb04..554af28 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -41,6 +41,13 @@
      * when no window animation is driving it. */
     private static final int DEFAULT_DIM_DURATION = 200;
 
+    // The amount we divide the height/width of the bounds we are trying to fit the task within
+    // when using the method {@link #fitWithinBounds}.
+    // We always want the task to to be visible in the bounds without affecting its size when
+    // fitting. To make sure this is the case, we don't adjust the task left or top side pass
+    // the input bounds right or bottom side minus the width or height divided by this value.
+    private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
+
     TaskStack mStack;
     final AppTokenList mAppTokens = new AppTokenList();
     final int mTaskId;
@@ -165,6 +172,41 @@
         }
     }
 
+    /** Fits the tasks within the input bounds adjusting the task bounds as needed.
+     *  @param bounds Bounds to fit the task within. Nothing is done if null.
+     *  @return Returns true if the task bounds was adjusted in any way.
+     *  */
+    boolean fitWithinBounds(Rect bounds) {
+        if (bounds == null || bounds.contains(mBounds)) {
+            return false;
+        }
+        mTmpRect2.set(mBounds);
+
+        if (mBounds.left < bounds.left || mBounds.right > bounds.right) {
+            final int maxRight = bounds.right - (bounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int horizontalDiff = bounds.left - mBounds.left;
+            if ((horizontalDiff < 0 && mBounds.left >= maxRight)
+                    || (mBounds.left + horizontalDiff >= maxRight)) {
+                horizontalDiff = maxRight - mBounds.left;
+            }
+            mTmpRect2.left += horizontalDiff;
+            mTmpRect2.right += horizontalDiff;
+        }
+
+        if (mBounds.top < bounds.top || mBounds.bottom > bounds.bottom) {
+            final int maxBottom = bounds.bottom - (bounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int verticalDiff = bounds.top - mBounds.top;
+            if ((verticalDiff < 0 && mBounds.top >= maxBottom)
+                    || (mBounds.top + verticalDiff >= maxBottom)) {
+                verticalDiff = maxBottom - mBounds.top;
+            }
+            mTmpRect2.top += verticalDiff;
+            mTmpRect2.bottom += verticalDiff;
+        }
+
+        return setBounds(mTmpRect2);
+    }
+
     /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
     boolean setBounds(Rect bounds) {
         boolean oldFullscreen = mFullscreen;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 1c65c27..25a71d9 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -16,12 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.*;
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerService.TAG;
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.RemoteException;
 import android.util.EventLog;
 import android.util.IntArray;
 import android.util.Slog;
@@ -34,6 +36,10 @@
 import java.util.List;
 
 public class TaskStack implements DimLayer.DimLayerUser {
+
+    // If the stack should be resized to fullscreen.
+    private static final boolean FULLSCREEN = true;
+
     /** Unique identifier */
     final int mStackId;
 
@@ -91,20 +97,34 @@
     /**
      * Set the bounds of the stack and its containing tasks.
      * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
+     * @param resizeTasks If true, the tasks within the stack will also be resized.
      * @param changedTaskIds Output list of Ids of tasks that changed in bounds.
      * @param newTaskConfigs Output list of new Configuation of the tasks that changed.
      * @return True if the stack bounds was changed.
      * */
-    boolean setBounds(Rect bounds, IntArray changedTaskIds, List<Configuration> newTaskConfigs) {
+    boolean setBounds(Rect bounds, boolean resizeTasks, IntArray changedTaskIds,
+            List<Configuration> newTaskConfigs) {
         if (!setBounds(bounds)) {
             return false;
         }
 
+        if (!resizeTasks) {
+            return true;
+        }
+
         // Update bounds of containing tasks.
         final Rect newBounds = mFullscreen ? null : mBounds;
         for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final Task task = mTasks.get(taskNdx);
-            if (task.setBounds(newBounds)) {
+            if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                // 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.
+                if (task.fitWithinBounds(newBounds)) {
+                    changedTaskIds.add(task.mTaskId);
+                    newTaskConfigs.add(task.mOverrideConfig);
+                }
+            } else if (task.setBounds(newBounds)) {
                 changedTaskIds.add(task.mTaskId);
                 newTaskConfigs.add(task.mOverrideConfig);
             }
@@ -146,9 +166,13 @@
         out.set(mBounds);
     }
 
-    void updateDisplayInfo() {
+    void updateDisplayInfo(Rect bounds) {
         if (mDisplayContent != null) {
-            setBounds(mFullscreen ? null : mBounds);
+            if (bounds != null) {
+                setBounds(bounds);
+            } else {
+                setBounds(mFullscreen ? null : mBounds);
+            }
             for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
                 mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent);
             }
@@ -296,7 +320,82 @@
 
         mDisplayContent = displayContent;
         mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId());
-        updateDisplayInfo();
+
+        Rect bounds = null;
+        final boolean dockedStackExists = mService.mStackIdToStack.get(DOCKED_STACK_ID) != null;
+        if (mStackId == DOCKED_STACK_ID || (dockedStackExists
+                && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
+            // The existence of a docked stack affects the size of any static stack created since
+            // the docked stack occupies a dedicated region on screen.
+            bounds = new Rect();
+            displayContent.getLogicalDisplayRect(mTmpRect);
+            getInitialDockedStackBounds(mTmpRect, bounds, mStackId);
+        }
+
+        updateDisplayInfo(bounds);
+
+        if (mStackId == DOCKED_STACK_ID) {
+            // Attaching a docked stack to the display affects the size of all other static
+            // stacks since the docked stack occupies a dedicated region on screen.
+            // Resize existing static stacks so they are pushed to the side of the docked stack.
+            resizeNonDockedStacks(!FULLSCREEN);
+        }
+    }
+
+    /**
+     * Outputs the initial bounds a stack should be given the presence of a docked stack on the
+     * display.
+     * @param displayRect The bounds of the display the docked stack is on.
+     * @param outBounds Output bounds that should be used for the stack.
+     * @param stackId Id of stack we are calculating the bounds for.
+     */
+    private static void getInitialDockedStackBounds(
+            Rect displayRect, Rect outBounds, int stackId) {
+        // Docked stack start off occupying half the screen space.
+        // TODO(multi-window): Need to support the selecting which half of the screen the
+        // docked stack uses for snapping windows to the edge of the screen.
+        final boolean splitHorizontally = displayRect.width() > displayRect.height();
+        outBounds.set(displayRect);
+        if (stackId == DOCKED_STACK_ID) {
+            if (splitHorizontally) {
+                outBounds.right = displayRect.centerX();
+            } else {
+                outBounds.bottom = displayRect.centerY();
+            }
+        } else {
+            if (splitHorizontally) {
+                outBounds.left = displayRect.centerX();
+            } else {
+                outBounds.top = displayRect.centerY();
+            }
+        }
+    }
+
+    /** Resizes all non-docked stacks in the system to either fullscreen or the appropriate size
+     * based on the presence of a docked stack.
+     * @param fullscreen If true the stacks will be resized to fullscreen, else they will be
+     *                   resized to the appropriate size based on the presence of a docked stack.
+     */
+    private void resizeNonDockedStacks(boolean fullscreen) {
+        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        if (!fullscreen) {
+            getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID);
+        }
+
+        final int count = mService.mStackIdToStack.size();
+        for (int i = 0; i < count; i++) {
+            final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
+            final int otherStackId = otherStack.mStackId;
+            if (otherStackId != DOCKED_STACK_ID
+                    && otherStackId >= FIRST_STATIC_STACK_ID
+                    && otherStackId <= LAST_STATIC_STACK_ID) {
+                try {
+                    mService.mActivityManager.resizeStack(otherStackId, mTmpRect);
+                } catch (RemoteException e) {
+                    // This will not happen since we are in the same process.
+                }
+            }
+        }
     }
 
     void detachDisplay() {
@@ -317,6 +416,12 @@
             mService.requestTraversalLocked();
         }
 
+        if (mStackId == DOCKED_STACK_ID) {
+            // Docked stack was detached from the display, so we no longer need to restrict the
+            // region of the screen other static stacks occupy. Go ahead and make them fullscreen.
+            resizeNonDockedStacks(FULLSCREEN);
+        }
+
         close();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ec61a1d..355b09b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5123,8 +5123,9 @@
      * @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 initial bounds the stack was created with. null means fullscreen.
      */
-    public void attachStack(int stackId, int displayId, boolean onTop) {
+    public Rect attachStack(int stackId, int displayId, boolean onTop) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mWindowMap) {
@@ -5138,16 +5139,24 @@
                     }
                     stack.attachDisplayContent(displayContent);
                     displayContent.attachStack(stack, onTop);
+
                     moveStackWindowsLocked(displayContent);
                     final WindowList windows = displayContent.getWindowList();
                     for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                         windows.get(winNdx).reportResized();
                     }
+                    if (stack.isFullscreen()) {
+                        return null;
+                    }
+                    Rect bounds = new Rect();
+                    stack.getBounds(bounds);
+                    return bounds;
                 }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
+        return null;
     }
 
     void detachStackLocked(DisplayContent displayContent, TaskStack stack) {
@@ -5239,19 +5248,20 @@
      * 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 resizeTasks If true, the tasks within the stack will also be resized.
      * @param changedTaskIds Output list of Ids of tasks that changed in bounds due to resize.
      * @param newTaskConfigs Output list of new Configuation of the tasks that changed.
      * @return True if the stack is now fullscreen.
      * */
-    public boolean resizeStack(
-            int stackId, Rect bounds, IntArray changedTaskIds, List<Configuration> newTaskConfigs) {
+    public boolean resizeStack(int stackId, Rect bounds, boolean resizeTasks,
+            IntArray changedTaskIds, List<Configuration> newTaskConfigs) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);
             if (stack == null) {
                 throw new IllegalArgumentException("resizeStack: stackId " + stackId
                         + " not found.");
             }
-            if (stack.setBounds(bounds, changedTaskIds, newTaskConfigs)) {
+            if (stack.setBounds(bounds, resizeTasks, changedTaskIds, newTaskConfigs)) {
                 stack.resizeWindows();
                 stack.getDisplayContent().layoutNeeded = true;
                 performLayoutAndPlaceSurfacesLocked();