Incremental repairs to side by side stacks.

- Add taskId parameter to createStack() so stacks are pre-populated
with a task.
- Keep track of stack access order in DisplayContent so getTasks
returns in MRU order.
- Set touchableRegion in InputMonitor so modal touching does not
extend beyond stack boundary.
- Fix stack merging so that deleting a stack results in a new
stack the size of the two children.

Change-Id: I62a6ba0a34f34dd7ec866b440bf04595379e19e8
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6e7080f..16c7824 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6261,13 +6261,16 @@
     }
 
     @Override
-    public int createStack(int relativeStackId, int position, float weight) {
+    public int createStack(int taskId, int relativeStackId, int position, float weight) {
         synchronized (this) {
             if (mStackSupervisor.getStack(relativeStackId) == null) {
                 return -1;
             }
             int stackId = mStackSupervisor.createStack();
             mWindowManager.createStack(stackId, relativeStackId, position, weight);
+            if (taskId > 0) {
+                moveTaskToStack(taskId, stackId, true);
+            }
             return stackId;
         }
     }
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 7b8ad43..8768688 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -236,13 +236,10 @@
         final ActivityStack stack = task.stack;
         if (stack.removeTask(task) && !stack.isHomeStack()) {
             mStacks.remove(stack);
-            final int oldStackId = stack.mStackId;
-            final int newMainStackId = mService.mWindowManager.removeStack(oldStackId);
-            if (newMainStackId == HOME_STACK_ID) {
-                return;
-            }
-            if (mMainStack.mStackId == oldStackId) {
-                mMainStack = getStack(newMainStackId);
+            final int stackId = stack.mStackId;
+            final int nextStackId = mService.mWindowManager.removeStack(stackId);
+            if (mMainStack.mStackId == stackId) {
+                mMainStack = nextStackId == HOME_STACK_ID ? null : getStack(nextStackId);
             }
         }
     }
@@ -1044,8 +1041,8 @@
         if (!r.isHomeActivity) {
             if (mStacks.size() == 1) {
                 // Time to create the first app stack.
-                int stackId =
-                        mService.createStack(HOME_STACK_ID, StackBox.TASK_STACK_GOES_OVER, 1.0f);
+                int stackId = mService.createStack(-1, HOME_STACK_ID,
+                        StackBox.TASK_STACK_GOES_OVER, 1.0f);
                 mMainStack = getStack(stackId);
             }
             return mMainStack;
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index edf3d3b..915c696c 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -90,11 +90,12 @@
     /** True when the home StackBox is at the top of mStackBoxes, false otherwise */
     private TaskStack mHomeStack = null;
 
-    /**
-     * Sorted most recent at top, oldest at [0].
-     */
+    /** Save allocating when retrieving tasks */
     ArrayList<Task> mTmpTasks = new ArrayList<Task>();
 
+    /** Sorted most recent at top, oldest at [0]. */
+    ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>();
+
     /**
      * @param display May not be null.
      */
@@ -125,17 +126,23 @@
         return mStackBoxes.get(0).mStack != mHomeStack;
     }
 
+    void moveStack(TaskStack stack, boolean toTop) {
+        mStackHistory.remove(stack);
+        mStackHistory.add(toTop ? mStackHistory.size() : 0, stack);
+    }
+
     /**
-     * Retrieve the tasks on this display in stack order from the topmost TaskStack down.
-     * Note that the order of TaskStacks in the same StackBox is defined within StackBox.
+     * Retrieve the tasks on this display in stack order from the bottommost TaskStack up.
      * @return All the Tasks, in order, on this display.
      */
     ArrayList<Task> getTasks() {
         mTmpTasks.clear();
-        int numBoxes = mStackBoxes.size();
-        for (int boxNdx = 0; boxNdx < numBoxes; ++boxNdx) {
-            mTmpTasks.addAll(mStackBoxes.get(boxNdx).getTasks());
+        final int numStacks = mStackHistory.size();
+        for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+            mTmpTasks.addAll(mStackHistory.get(stackNdx).getTasks());
         }
+        if (WindowManagerService.DEBUG_LAYERS) Slog.i(TAG, "getTasks: mStackHistory=" +
+                mStackHistory);
         return mTmpTasks;
     }
 
@@ -221,6 +228,13 @@
         return false;
     }
 
+    void addStackBox(StackBox box, boolean toTop) {
+        if (mStackBoxes.size() >= 2) {
+            throw new RuntimeException("addStackBox: Too many toplevel StackBoxes!");
+        }
+        mStackBoxes.add(toTop ? mStackBoxes.size() : 0, box);
+    }
+
     void removeStackBox(StackBox box) {
         if (DEBUG_STACK) Slog.d(TAG, "removeStackBox: box=" + box);
         final TaskStack stack = box.mStack;
diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java
index d966001..cacc723 100644
--- a/services/java/com/android/server/wm/InputMonitor.java
+++ b/services/java/com/android/server/wm/InputMonitor.java
@@ -31,7 +31,6 @@
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 
 final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
@@ -68,6 +67,7 @@
      * 
      * Called by the InputManager.
      */
+    @Override
     public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
         if (inputWindowHandle == null) {
             return;
@@ -87,6 +87,7 @@
      * 
      * Called by the InputManager.
      */
+    @Override
     public long notifyANR(InputApplicationHandle inputApplicationHandle,
             InputWindowHandle inputWindowHandle) {
         AppWindowToken appWindowToken = null;
@@ -163,10 +164,20 @@
     }
 
     private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
-            final WindowState child, final int flags, final int type,
+            final WindowState child, int flags, final int type,
             final boolean isVisible, final boolean hasFocus, final boolean hasWallpaper) {
         // Add a window to our list of input windows.
         inputWindowHandle.name = child.toString();
+        final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0;
+        if (modal && child.mAppToken != null) {
+            // Limit the outer touch to the activity stack region.
+            flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+            inputWindowHandle.touchableRegion.set(child.getStackBounds());
+        } else {
+            // Not modal or full screen modal
+            child.getTouchableRegion(inputWindowHandle.touchableRegion);
+        }
         inputWindowHandle.layoutParamsFlags = flags;
         inputWindowHandle.layoutParamsType = type;
         inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
@@ -195,7 +206,6 @@
             inputWindowHandle.scaleFactor = 1;
         }
 
-        child.getTouchableRegion(inputWindowHandle.touchableRegion);
 
         addInputWindowHandleLw(inputWindowHandle);
     }
@@ -259,10 +269,10 @@
                 // Skip this window because it cannot possibly receive input.
                 continue;
             }
-            
+
             final int flags = child.mAttrs.flags;
             final int type = child.mAttrs.type;
-            
+
             final boolean hasFocus = (child == mInputFocus);
             final boolean isVisible = child.isVisibleLw();
             final boolean hasWallpaper = (child == mService.mWallpaperTarget)
diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java
index bcc5137..b195f0b 100644
--- a/services/java/com/android/server/wm/StackBox.java
+++ b/services/java/com/android/server/wm/StackBox.java
@@ -42,7 +42,7 @@
 
     /** Non-null indicates this is mFirst or mSecond of a parent StackBox. Null indicates this
      * is this entire size of mDisplayContent. */
-    final StackBox mParent;
+    StackBox mParent;
 
     /** First child, this is null exactly when mStack is non-null. */
     StackBox mFirst;
@@ -105,13 +105,14 @@
         return mFirst.contains(stackId) || mSecond.contains(stackId);
     }
 
-    /** Determine if the specified stack is the first child or second child. Presumes that this
-     * is called on mParent of the specified stack.
-     * @param stack the stack to determine.
-     * @return true if stack is the first child.
+    /** Determine if this StackBox is the first child or second child.
+     * @return true if this is the first child.
      */
-    boolean isFirstChild(TaskStack stack) {
-        return mFirst.mStack == stack;
+    boolean isFirstChild() {
+        if (mParent == null) {
+            return false;
+        }
+        return mParent.mFirst == this;
     }
 
     /** Returns the bounds of the specified TaskStack if it is contained in this StackBox.
@@ -217,15 +218,6 @@
         return mTmpTasks;
     }
 
-    /** Combine a child StackBox into its parent.
-     * @param child The surviving child to be merge up into this StackBox. */
-    void absorb(StackBox child) {
-        mFirst = child.mFirst;
-        mSecond = child.mSecond;
-        mStack = child.mStack;
-        layoutNeeded = true;
-    }
-
     /** Return the stackId of the first mFirst StackBox with a non-null mStack */
     int getStackId() {
         if (mStack != null) {
@@ -236,18 +228,33 @@
 
     /** Remove this box and propagate its sibling's content up to their parent.
      * @return The first stackId of the resulting StackBox. */
-    int removeStack() {
+    int remove() {
+        if (mStack != null) {
+            mDisplayContent.mStackHistory.remove(mStack);
+        }
+        mDisplayContent.layoutNeeded = true;
+
         if (mParent == null) {
+            // This is the top-plane stack.
             mDisplayContent.removeStackBox(this);
             return HOME_STACK_ID;
         }
-        if (mParent.mFirst == this) {
-            mParent.absorb(mParent.mSecond);
+
+        StackBox sibling = isFirstChild() ? mParent.mSecond : mParent.mFirst;
+        StackBox grandparent = mParent.mParent;
+        if (grandparent == null) {
+            // mParent is a top-plane stack. Now sibling will be.
+            mDisplayContent.removeStackBox(mParent);
+            mDisplayContent.addStackBox(sibling, true);
         } else {
-            mParent.absorb(mParent.mFirst);
+            sibling.mParent = grandparent;
+            if (mParent.isFirstChild()) {
+                grandparent.mFirst = sibling;
+            } else {
+                grandparent.mSecond = sibling;
+            }
         }
-        mParent.makeDirty();
-        return mParent.getStackId();
+        return sibling.getStackId();
     }
 
     boolean resize(int stackId, float weight) {
@@ -255,7 +262,7 @@
             return mFirst.resize(stackId, weight) || mSecond.resize(stackId, weight);
         }
         if (mStack.mStackId == stackId) {
-            mParent.mWeight = mParent.isFirstChild(mStack) ? weight : 1.0f - weight;
+            mParent.mWeight = isFirstChild() ? weight : 1.0f - weight;
             return true;
         }
         return false;
diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java
index 2520f31..88eb96e 100644
--- a/services/java/com/android/server/wm/Task.java
+++ b/services/java/com/android/server/wm/Task.java
@@ -47,6 +47,6 @@
 
     @Override
     public String toString() {
-        return "taskId=" + taskId + " appTokens=" + mAppTokens;
+        return "{taskId=" + taskId + " appTokens=" + mAppTokens + "}";
     }
 }
diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java
index 1c49e7d..590849f 100644
--- a/services/java/com/android/server/wm/TaskStack.java
+++ b/services/java/com/android/server/wm/TaskStack.java
@@ -92,7 +92,7 @@
     }
 
     int remove() {
-        return mStackBox.removeStack();
+        return mStackBox.remove();
     }
 
     int numTokens() {
@@ -109,4 +109,9 @@
             pw.print(prefix); pw.println(mTasks.get(taskNdx));
         }
     }
+
+    @Override
+    public String toString() {
+        return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
+    }
 }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index a00fb7b..da28cd8 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3383,6 +3383,7 @@
                 }
                 task = new Task(atoken, stack);
                 stack.addTask(task, true);
+                stack.getDisplayContent().moveStack(stack, true);
                 mTaskIdToTask.put(taskId, task);
             } else {
                 task.addAppToken(addPos, atoken);
@@ -3703,6 +3704,8 @@
                     Slog.w(TAG, "Attempted to set focus to non-existing app token: " + token);
                     return;
                 }
+                Task task = mTaskIdToTask.get(newFocus.groupId);
+                task.getDisplayContent().moveStack(task.mStack, true);
                 changed = mFocusedApp != newFocus;
                 mFocusedApp = newFocus;
                 if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp
@@ -4675,6 +4678,7 @@
                     displayContent.moveHomeStackBox(true);
                 }
                 stack.moveTaskToTop(task);
+                displayContent.moveStack(stack, true);
                 moveTaskWindowsLocked(task);
             }
         } finally {
@@ -4694,6 +4698,7 @@
                 }
                 task.mStack.moveTaskToBottom(task);
                 moveTaskWindowsLocked(task);
+                task.getDisplayContent().moveStack(task.mStack, false);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -4721,15 +4726,22 @@
                         "createStack: weight must be between " + STACK_WEIGHT_MIN + " and " +
                         STACK_WEIGHT_MAX + ", weight=" + weight);
             }
-            final TaskStack relativeStack = mStackIdToStack.get(relativeStackId);
-            DisplayContent displayContent = relativeStack != null ?
-                    relativeStack.getDisplayContent() : getDefaultDisplayContentLocked();
-            TaskStack stack = displayContent.createStack(stackId, relativeStackId, position,
-                    weight);
-            if (stack != null) {
-                mStackIdToStack.put(stackId, stack);
-                performLayoutAndPlaceSurfacesLocked();
+            final DisplayContent displayContent;
+            if (stackId != HOME_STACK_ID) {
+                // TODO: What to do for the first stack on a non-default display?
+                final TaskStack relativeStack = mStackIdToStack.get(relativeStackId);
+                if (relativeStack == null) {
+                    throw new IllegalArgumentException("createStack: Invalid relativeStackId=" +
+                            relativeStackId);
+                }
+                displayContent = relativeStack.getDisplayContent();
+            } else {
+                displayContent = getDefaultDisplayContentLocked();
             }
+            TaskStack stack =
+                    displayContent.createStack(stackId, relativeStackId, position, weight);
+            mStackIdToStack.put(stackId, stack);
+            displayContent.moveStack(stack, true);
         }
     }
 
@@ -4748,10 +4760,15 @@
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
         synchronized (mWindowMap) {
             Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                return;
+            }
             task.mStack.removeTask(task);
-            TaskStack newStack = mStackIdToStack.get(stackId);
-            newStack.addTask(task, toTop);
-            newStack.getDisplayContent().layoutNeeded = true;
+
+            TaskStack stack = mStackIdToStack.get(stackId);
+            stack.addTask(task, toTop);
+            stack.getDisplayContent().layoutNeeded = true;
+
             performLayoutAndPlaceSurfacesLocked();
         }
     }
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index c0881da..d4c727b 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -441,8 +441,7 @@
 
         final int type = mAttrs.type;
         if (mAppToken != null) {
-            StackBox stack = mService.mTaskIdToTask.get(mAppToken.groupId).mStack.mStackBox;
-            mContainingFrame.set(stack.mBounds);
+            mContainingFrame.set(getStackBounds());
         } else {
             mContainingFrame.set(pf);
         }
@@ -703,6 +702,14 @@
         return null;
     }
 
+    Rect getStackBounds() {
+        TaskStack stack = getStack();
+        if (stack != null) {
+            return stack.mStackBox.mBounds;
+        }
+        return mFrame;
+    }
+
     public long getInputDispatchingTimeoutNanos() {
         return mAppToken != null
                 ? mAppToken.inputDispatchingTimeoutNanos