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/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();