Add positionChildAt method to WindowContainer

Added method to change the position of a child among siblings.
It accepts int value, which can either specify a target position
or POSITION_TOP/POSITION_BOTTOM.
When child is moved to top or bottom, there is an option to also
perform same action on parents. This will effectively move the
entire branch of the hierarchy tree to top/bottom.

Test: bit FrameworksServicesTests:com.android.server.wm.WindowContainerTests
Test: #testPositionChildAt
Test: #testPositionChildAtIncludeParents
Test: #testPositionChildAtInvalid
Test: bit FrameworksServicesTests:com.android.server.wm.TaskStackContainersTests
Test: bit FrameworksServicesTests:com.android.server.wm.TaskStackTests
Change-Id: I6ade787487055f1c9a305afea64270c243196614
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19dcf41..1583023 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10044,6 +10044,11 @@
         }
     }
 
+    /**
+     * Try to place task to provided position. The final position might be different depending on
+     * current user and stacks state. The task will be moved to target stack if it's currently in
+     * different stack.
+     */
     @Override
     public void positionTaskInStack(int taskId, int stackId, int position) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 5bdae57..ba95abd 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2547,6 +2547,10 @@
         return null;
     }
 
+    /**
+     * Used from {@link ActivityStack#positionTask(TaskRecord, int)}.
+     * @see ActivityManagerService#positionTaskInStack(int, int, int).
+     */
     private void insertTaskAtPosition(TaskRecord task, int position) {
         if (position >= mTaskHistory.size()) {
             insertTaskAtTop(task, null);
@@ -4935,6 +4939,7 @@
         postAddTask(task, prevStack);
     }
 
+    /** @see ActivityManagerService#positionTaskInStack(int, int, int). */
     void positionTask(final TaskRecord task, int position) {
         final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
         final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b4b3465..235325b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2200,7 +2200,7 @@
             } else {
                 for (int i = size - 1; i >= 0; i--) {
                     positionTaskInStackLocked(tasks.get(i).taskId,
-                            FULLSCREEN_WORKSPACE_STACK_ID, 0);
+                            FULLSCREEN_WORKSPACE_STACK_ID, 0 /* position */);
                 }
             }
         } finally {
@@ -2872,6 +2872,7 @@
         return true;
     }
 
+    /** @see ActivityManagerService#positionTaskInStack(int, int, int). */
     void positionTaskInStackLocked(int taskId, int stackId, int position) {
         final TaskRecord task = anyTaskForIdLocked(taskId);
         if (task == null) {
@@ -2882,6 +2883,8 @@
 
         task.updateOverrideConfigurationForStack(stack);
 
+        // TODO: Return final position from WM for AM to use instead of duplicating computations in
+        // ActivityStack#insertTaskAtPosition.
         mWindowManager.positionTaskInStack(
                 taskId, stackId, position, task.mBounds, task.getOverrideConfiguration());
         stack.positionTask(task, position);
@@ -4322,7 +4325,7 @@
                 if (activityDisplay == null) {
                     return;
                 }
-                addToDisplayLocked(activityDisplay, true);
+                addToDisplayLocked(activityDisplay, true /* onTop */);
             }
         }
 
@@ -4538,7 +4541,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                addToDisplayLocked(virtualActivityDisplay, true);
+                addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
             }
 
             if (mSurface != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 203137d..135a3dc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -979,7 +979,10 @@
                 // It's already attached to the display...clear mDeferRemoval and move stack to
                 // appropriate z-order on display as needed.
                 stack.mDeferRemoval = false;
-                moveStack(stack, onTop);
+                // 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);
@@ -1031,10 +1034,6 @@
         return addStackToDisplay(stack.mStackId, true /* onTop */);
     }
 
-    void moveStack(TaskStack stack, boolean toTop) {
-        mTaskStackContainers.moveStack(stack, toTop);
-    }
-
     @Override
     protected void addChild(DisplayChildWindowContainer child,
             Comparator<DisplayChildWindowContainer> comparator) {
@@ -1057,6 +1056,13 @@
         throw new UnsupportedOperationException("See DisplayChildWindowContainer");
     }
 
+    @Override
+    void positionChildAt(int position, DisplayChildWindowContainer child, boolean includingParents) {
+        // Children of the display are statically ordered, so the real intention here is to perform
+        // the operation on the display and not the static direct children.
+        getParent().positionChildAt(position, this, includingParents);
+    }
+
     int taskIdFromPoint(int x, int y) {
         for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.get(stackNdx);
@@ -2499,20 +2505,6 @@
             stack.onRemovedFromDisplay();
         }
 
-        void moveStack(TaskStack stack, boolean toTop) {
-            if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
-                // This stack is always-on-top silly...
-                Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + stack + " to bottom");
-                return;
-            }
-
-            if (!mChildren.contains(stack)) {
-                Slog.wtf(TAG_WM, "moving stack that was not added: " + stack, new Throwable());
-            }
-            removeChild(stack);
-            addChild(stack, toTop);
-        }
-
         private void addChild(TaskStack stack, boolean toTop) {
             int addIndex = toTop ? mChildren.size() : 0;
 
@@ -2532,6 +2524,22 @@
         }
 
         @Override
+        void positionChildAt(int position, TaskStack child, boolean includingParents) {
+            if (StackId.isAlwaysOnTop(child.mStackId) && position != POSITION_TOP) {
+                // This stack is always-on-top, override the default behavior.
+                Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
+
+                // Moving to its current position, as we must call super but we don't want to
+                // perform any meaningful action.
+                final int currentPosition = mChildren.indexOf(child);
+                super.positionChildAt(currentPosition, child, false /* includingParents */);
+                return;
+            }
+
+            super.positionChildAt(position, child, includingParents);
+        }
+
+        @Override
         boolean forAllWindows(ToBooleanFunction<WindowState> callback,
                 boolean traverseTopToBottom) {
             if (traverseTopToBottom) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index aa6e3c7..6005a99 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
@@ -154,7 +155,7 @@
         mService.mTaskIdToTask.delete(mTaskId);
     }
 
-    // Change to use re-parenting in WC when TaskStack is switched to use WC.
+    // TODO: Change to use re-parenting in WC.
     void moveTaskToStack(TaskStack stack, boolean toTop) {
         if (stack == mStack) {
             return;
@@ -166,15 +167,20 @@
         stack.addTask(this, toTop);
     }
 
+    /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
     void positionTaskInStack(TaskStack stack, int position, Rect bounds,
             Configuration overrideConfig) {
         if (mStack != null && stack != mStack) {
+            // Task is already attached to a different stack. First we need to remove it from there
+            // and add to top of the target stack. We will move it proper position afterwards.
             if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
                     + " from stack=" + mStack);
             EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
             mStack.removeChild(this);
+            stack.addTask(this, true /* toTop */);
         }
-        stack.positionTask(this, position, showForAllUsers());
+
+        stack.positionChildAt(position, this, true /* includingParents */);
         resizeLocked(bounds, overrideConfig, false /* force */);
 
         for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
@@ -183,6 +189,20 @@
     }
 
     @Override
+    void onParentSet() {
+        // Update task bounds if needed.
+        updateDisplayInfo(getDisplayContent());
+
+        if (StackId.windowsAreScaleable(mStack.mStackId)) {
+            // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
+            // while a resize is pending.
+            forceWindowsScaleable(true /* force */);
+        } else {
+            forceWindowsScaleable(false /* force */);
+        }
+    }
+
+    @Override
     void removeChild(AppWindowToken token) {
         if (!mChildren.contains(token)) {
             Slog.e(TAG, "removeChild: token=" + this + " not found.");
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 74c0919..d7c7cfa 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -512,27 +512,67 @@
     }
 
     /**
-     * Put a Task in this stack. Used for adding and moving.
+     * Put a Task in this stack. Used for adding only.
+     * When task is added to top of the stack, the entire branch of the hierarchy (including stack
+     * and display) will be brought to top.
      * @param task The task to add.
      * @param toTop Whether to add it to the top or bottom.
      * @param showForAllUsers Whether to show the task regardless of the current user.
      */
     void addTask(Task task, boolean toTop, boolean showForAllUsers) {
-        positionTask(task, toTop ? mChildren.size() : 0, showForAllUsers);
+        final TaskStack currentStack = task.mStack;
+        // TODO: We pass stack to task's constructor, but we still need to call this method.
+        // This doesn't make sense, mStack will already be set equal to "this" at this point.
+        if (currentStack != null && currentStack.mStackId != mStackId) {
+            throw new IllegalStateException("Trying to add taskId=" + task.mTaskId
+                    + " to stackId=" + mStackId
+                    + ", but it is already attached to stackId=" + task.mStack.mStackId);
+        }
+
+        final int targetPosition = toTop ? mChildren.size() : 0;
+
+        // Add child task.
+        task.mStack = this;
+        addChild(task, targetPosition);
+
+        // Move child to a proper position, as some restriction for position might apply.
+        positionChildAt(targetPosition, task, true /* includingParents */, showForAllUsers);
+    }
+
+    @Override
+    void positionChildAt(int position, Task child, boolean includingParents) {
+        positionChildAt(position, child, includingParents, child.showForAllUsers());
+    }
+
+    /**
+     * Overridden version of {@link TaskStack#positionChildAt(int, Task, boolean)}. Used in
+     * {@link TaskStack#addTask(Task, boolean, boolean showForAllUsers)}, as it can receive
+     * showForAllUsers param from {@link AppWindowToken} instead of {@link Task#showForAllUsers()}.
+     */
+    private void positionChildAt(int position, Task child, boolean includingParents,
+            boolean showForAllUsers) {
+        final int targetPosition = findPositionForTask(child, position, showForAllUsers,
+                false /* addingNew */);
+        super.positionChildAt(targetPosition, child, includingParents);
+
+        // Log positioning.
+        if (DEBUG_TASK_MOVEMENT)
+            Slog.d(TAG_WM, "positionTask: task=" + this + " position=" + position);
+
+        final int toTop = targetPosition == mChildren.size() - 1 ? 1 : 0;
+        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, child.mTaskId, toTop, targetPosition);
     }
 
     // TODO: We should really have users as a window container in the hierarchy so that we don't
-    // have to do complicated things like we are doing in this method and also also the method to
-    // just be WindowContainer#addChild
-    void positionTask(Task task, int position, boolean showForAllUsers) {
+    // have to do complicated things like we are doing in this method.
+    private int findPositionForTask(Task task, int targetPosition, boolean showForAllUsers,
+            boolean addingNew) {
         final boolean canShowTask =
                 showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
-        if (mChildren.contains(task)) {
-            super.removeChild(task);
-        }
-        int stackSize = mChildren.size();
+
+        final int stackSize = mChildren.size();
         int minPosition = 0;
-        int maxPosition = stackSize;
+        int maxPosition = addingNew ? stackSize : stackSize - 1;
 
         if (canShowTask) {
             minPosition = computeMinPosition(minPosition, stackSize);
@@ -540,28 +580,7 @@
             maxPosition = computeMaxPosition(maxPosition);
         }
         // Reset position based on minimum/maximum possible positions.
-        position = Math.min(Math.max(position, minPosition), maxPosition);
-
-        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM,
-                "positionTask: task=" + task + " position=" + position);
-        addChild(task, position);
-        task.mStack = this;
-        task.updateDisplayInfo(mDisplayContent);
-        boolean toTop = position == mChildren.size() - 1;
-        if (toTop) {
-            // TODO: Have a WidnowContainer method that moves all parents of a container to the
-            // front for cases like this.
-            mDisplayContent.moveStack(this, true);
-        }
-
-        if (StackId.windowsAreScaleable(mStackId)) {
-            // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
-            // while a resize is pending.
-            task.forceWindowsScaleable(true);
-        } else {
-            task.forceWindowsScaleable(false);
-        }
-        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, position);
+        return Math.min(Math.max(targetPosition, minPosition), maxPosition);
     }
 
     /** Calculate the minimum possible position for a task that can be shown to the user.
@@ -591,7 +610,7 @@
      */
     private int computeMaxPosition(int maxPosition) {
         while (maxPosition > 0) {
-            final Task tmpTask = mChildren.get(maxPosition - 1);
+            final Task tmpTask = mChildren.get(maxPosition);
             final boolean canShowTmpTask =
                     tmpTask.showForAllUsers()
                             || mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -603,20 +622,6 @@
         return maxPosition;
     }
 
-    // TODO: Have functionality in WC to move things to the bottom or top. Also, look at the call
-    // points for this methods to see if we need functionality to move something to the front/bottom
-    // with its parents.
-    void moveTaskToTop(Task task) {
-        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToTop: task=" + task + " Callers="
-                + Debug.getCallers(6));
-        addTask(task, true);
-    }
-
-    void moveTaskToBottom(Task task) {
-        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToBottom: task=" + task);
-        addTask(task, false);
-    }
-
     /**
      * Delete a Task from this stack. If it is the last Task in the stack, move this stack to the
      * back.
@@ -627,10 +632,11 @@
         if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task);
 
         super.removeChild(task);
+        task.mStack = null;
 
         if (mDisplayContent != null) {
             if (mChildren.isEmpty()) {
-                mDisplayContent.moveStack(this, false);
+                getParent().positionChildAt(POSITION_BOTTOM, this, false /* includingParents */);
             }
             mDisplayContent.setLayoutNeeded();
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4a1c067..984cf55 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -39,6 +39,9 @@
  */
 class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
 
+    static final int POSITION_TOP = Integer.MAX_VALUE;
+    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
     /**
      * The parent of this window container.
      * For removing or setting new parent {@link #setParent} should be used, because it also
@@ -86,6 +89,16 @@
             // Update merged override configuration of this container and all its children.
             onMergedOverrideConfigurationChanged();
         }
+
+        onParentSet();
+    }
+
+    /**
+     * Callback that is triggered when @link WindowContainer#setParent(WindowContainer)} was called.
+     * Supposed to be overridden and contain actions that should be executed after parent was set.
+     */
+    void onParentSet() {
+        // Do nothing by default.
     }
 
     // Temp. holders for a chain of containers we are currently processing.
@@ -203,6 +216,57 @@
     }
 
     /**
+     * Move a child from it's current place in siblings list to the specified position,
+     * with an option to move all its parents to top.
+     * @param position Target position to move the child to.
+     * @param child Child to move to selected position.
+     * @param includingParents Flag indicating whether we need to move the entire branch of the
+     *                         hierarchy when we're moving a child to {@link #POSITION_TOP} or
+     *                         {@link #POSITION_BOTTOM}. When moving to other intermediate positions
+     *                         this flag will do nothing.
+     */
+    @CallSuper
+    void positionChildAt(int position, E child, boolean includingParents) {
+        if ((position < 0 && position != POSITION_BOTTOM)
+                || (position >= mChildren.size() && position != POSITION_TOP)) {
+            throw new IllegalArgumentException("positionAt: invalid position=" + position
+                    + ", children number=" + mChildren.size());
+        }
+
+        if (position == mChildren.size() - 1) {
+            position = POSITION_TOP;
+        } else if (position == 0) {
+            position = POSITION_BOTTOM;
+        }
+
+        switch (position) {
+            case POSITION_TOP:
+                if (mChildren.getLast() != child) {
+                    mChildren.remove(child);
+                    mChildren.addLast(child);
+                }
+                if (includingParents && getParent() != null) {
+                    getParent().positionChildAt(POSITION_TOP, this /* child */,
+                            true /* includingParents */);
+                }
+                break;
+            case POSITION_BOTTOM:
+                if (mChildren.getFirst() != child) {
+                    mChildren.remove(child);
+                    mChildren.addFirst(child);
+                }
+                if (includingParents && getParent() != null) {
+                    getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
+                            true /* includingParents */);
+                }
+                break;
+            default:
+                mChildren.remove(child);
+                mChildren.add(position, child);
+        }
+    }
+
+    /**
      * Returns full configuration applied to this window container.
      * This method should be used for getting settings applied in each particular level of the
      * hierarchy.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 577e5a0..0f16750 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -209,6 +209,8 @@
 import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
+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;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -3311,26 +3313,17 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized(mWindowMap) {
-                Task task = mTaskIdToTask.get(taskId);
+                final Task task = mTaskIdToTask.get(taskId);
                 if (task == null) {
                     // Normal behavior, addAppToken will be called next and task will be created.
                     return;
                 }
-                final TaskStack stack = task.mStack;
-                final DisplayContent displayContent = task.getDisplayContent();
-                displayContent.moveStack(stack, true);
-                if (displayContent.isDefaultDisplay) {
-                    final TaskStack homeStack = displayContent.getHomeStack();
-                    if (homeStack != stack) {
-                        // When a non-home stack moves to the top, the home stack moves to the
-                        // bottom.
-                        displayContent.moveStack(homeStack, false);
-                    }
-                }
-                stack.moveTaskToTop(task);
+                task.mStack.positionChildAt(POSITION_TOP, task, true /* includingParents */);
+
                 if (mAppTransition.isTransitionSet()) {
                     task.setSendingToBottom(false);
                 }
+                final DisplayContent displayContent = task.getDisplayContent();
                 displayContent.layoutAndAssignWindowLayersIfNeeded();
             }
         } finally {
@@ -3342,14 +3335,14 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized(mWindowMap) {
-                Task task = mTaskIdToTask.get(taskId);
+                final Task task = mTaskIdToTask.get(taskId);
                 if (task == null) {
                     Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + taskId
                             + " not found in mTaskIdToTask");
                     return;
                 }
                 final TaskStack stack = task.mStack;
-                stack.moveTaskToBottom(task);
+                stack.positionChildAt(POSITION_BOTTOM, task, false /* includingParents */);
                 if (mAppTransition.isTransitionSet()) {
                     task.setSendingToBottom(true);
                 }
@@ -3656,6 +3649,7 @@
         }
     }
 
+    /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
     public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
             Configuration overrideConfig) {
         synchronized (mWindowMap) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
new file mode 100644
index 0000000..7463102
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -0,0 +1,69 @@
+/*
+ * 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 static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.TaskStackContainersTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackContainersTests extends WindowTestsBase {
+
+    @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);
+        sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
+        final TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
+
+        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);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+
+        taskStackContainer.positionChildAt(1, pinnedStack, false);
+        assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
new file mode 100644
index 0000000..eca2500
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackTests.java
@@ -0,0 +1,59 @@
+/*
+ * 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 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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskStack} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.TaskStackTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskStackTests extends WindowTestsBase {
+
+    @Test
+    public void testStackPositionChildAt() throws Exception {
+        final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+        final Task task1 = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 1 /* userId */);
+
+        // Current user task should be moved to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task1, false /* includingParents */);
+        assertEquals(stack.mChildren.get(0), task2);
+        assertEquals(stack.mChildren.get(1), task1);
+
+        // Non-current user won't be moved to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task2, false /* includingParents */);
+        assertEquals(stack.mChildren.get(0), task2);
+        assertEquals(stack.mChildren.get(1), task1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 0ccd0ad..7277ba4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -33,6 +33,10 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -87,6 +91,14 @@
         assertEquals(layer1, root.getChildAt(4));
         assertEquals(secondLayer1, root.getChildAt(5));
         assertEquals(layer2, root.getChildAt(6));
+
+        assertTrue(layer1.mOnParentSetCalled);
+        assertTrue(secondLayer1.mOnParentSetCalled);
+        assertTrue(layer2.mOnParentSetCalled);
+        assertTrue(layerNeg1.mOnParentSetCalled);
+        assertTrue(layerNeg2.mOnParentSetCalled);
+        assertTrue(secondLayerNeg1.mOnParentSetCalled);
+        assertTrue(layer0.mOnParentSetCalled);
     }
 
     @Test
@@ -180,6 +192,99 @@
     }
 
     @Test
+    public void testPositionChildAt() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child3 = root.addChildWindow();
+
+        // Test position at top.
+        root.positionChildAt(POSITION_TOP, child1, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(root.getChildrenCount() - 1));
+
+        // Test position at bottom.
+        root.positionChildAt(POSITION_BOTTOM, child1, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(0));
+
+        // Test position in the middle.
+        root.positionChildAt(1, child3, false /* includingParents */);
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child3, root.getChildAt(1));
+        assertEquals(child2, root.getChildAt(2));
+    }
+
+    @Test
+    public void testPositionChildAtIncludeParents() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+        final TestWindowContainer child11 = child1.addChildWindow();
+        final TestWindowContainer child12 = child1.addChildWindow();
+        final TestWindowContainer child13 = child1.addChildWindow();
+        final TestWindowContainer child21 = child2.addChildWindow();
+        final TestWindowContainer child22 = child2.addChildWindow();
+        final TestWindowContainer child23 = child2.addChildWindow();
+
+        // Test moving to top.
+        child1.positionChildAt(POSITION_TOP, child11, true /* includingParents */);
+        assertEquals(child12, child1.getChildAt(0));
+        assertEquals(child13, child1.getChildAt(1));
+        assertEquals(child11, child1.getChildAt(2));
+        assertEquals(child2, root.getChildAt(0));
+        assertEquals(child1, root.getChildAt(1));
+
+        // Test moving to bottom.
+        child1.positionChildAt(POSITION_BOTTOM, child11, true /* includingParents */);
+        assertEquals(child11, child1.getChildAt(0));
+        assertEquals(child12, child1.getChildAt(1));
+        assertEquals(child13, child1.getChildAt(2));
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child2, root.getChildAt(1));
+
+        // Test moving to middle, includeParents shouldn't do anything.
+        child2.positionChildAt(1, child21, true /* includingParents */);
+        assertEquals(child11, child1.getChildAt(0));
+        assertEquals(child12, child1.getChildAt(1));
+        assertEquals(child13, child1.getChildAt(2));
+        assertEquals(child22, child2.getChildAt(0));
+        assertEquals(child21, child2.getChildAt(1));
+        assertEquals(child23, child2.getChildAt(2));
+        assertEquals(child1, root.getChildAt(0));
+        assertEquals(child2, root.getChildAt(1));
+    }
+
+    @Test
+    public void testPositionChildAtInvalid() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        boolean gotException = false;
+        try {
+            // Check response to negative position.
+            root.positionChildAt(-1, child1, false /* includingParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        gotException = false;
+        try {
+            // Check response to position that's bigger than child number.
+            root.positionChildAt(2, child1, false /* includingParents */);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    @Test
     public void testIsAnimating() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
@@ -548,6 +653,8 @@
         private boolean mIsVisible;
         private boolean mFillsParent;
 
+        private boolean mOnParentSetCalled;
+
         /**
          * Compares 2 window layers and returns -1 if the first is lesser than the second in terms
          * of z-order and 1 otherwise.
@@ -598,6 +705,11 @@
         }
 
         @Override
+        void onParentSet() {
+            mOnParentSetCalled = true;
+        }
+
+        @Override
         boolean isAnimating() {
             return mIsAnimating || super.isAnimating();
         }
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 41bf646..3985687 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -75,6 +75,7 @@
         sLayersController = new WindowLayersController(sWm);
         sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
                 new WallpaperController(sWm));
+        sWm.mRoot.addChild(sDisplayContent, 0);
 
         // Set-up some common windows.
         sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
@@ -103,11 +104,8 @@
             return new TestWindowToken(type, dc);
         }
 
-        final int stackId = sNextStackId++;
-        dc.addStackToDisplay(stackId, true);
-        final TaskStack stack = sWm.mStackIdToStack.get(stackId);
-        final Task task = new Task(sNextTaskId++, stack, 0, sWm, null, EMPTY, false, 0, false);
-        stack.addTask(task, true);
+        final TaskStack stack = createTaskStackOnDisplay(dc);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
         final TestAppWindowToken token = new TestAppWindowToken(dc);
         task.addChild(token, 0);
         return token;
@@ -136,6 +134,21 @@
         return w;
     }
 
+    /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
+    TaskStack createTaskStackOnDisplay(DisplayContent dc) {
+        final int stackId = sNextStackId++;
+        dc.addStackToDisplay(stackId, true);
+        return sWm.mStackIdToStack.get(stackId);
+    }
+
+    /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
+    Task createTaskInStack(TaskStack stack, int userId) {
+        final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
+                false);
+        stack.addTask(newTask, true);
+        return newTask;
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowToken} class */
     class TestWindowToken extends WindowToken {