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/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 {