Always place stacks below pinned stack

When positioning or adding stacks we must make sure
that no stack is above pinned stack.

Bug: 34049027
Test: runtest frameworks-services -c com.android.server.wm.TaskStackContainersTests
Change-Id: Ic92a4e07e9cde42deed53912ac1419fde4ab2f08
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ff841b1..d86c4da 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2498,19 +2498,8 @@
         }
 
         private void addChild(TaskStack stack, boolean toTop) {
-            int addIndex = toTop ? mChildren.size() : 0;
-
-            if (toTop
-                    && mService.isStackVisibleLocked(PINNED_STACK_ID)
-                    && stack.mStackId != PINNED_STACK_ID) {
-                // The pinned stack is always the top most stack (always-on-top) when it is visible.
-                // So, stack is moved just below the pinned stack.
-                addIndex--;
-                TaskStack topStack = mChildren.get(addIndex);
-                if (topStack.mStackId != PINNED_STACK_ID) {
-                    throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
-                }
-            }
+            final int addIndex = findPositionForStack(toTop ? mChildren.size() : 0, stack,
+                    true /* adding */);
             addChild(stack, addIndex);
             setLayoutNeeded();
         }
@@ -2528,7 +2517,45 @@
                 return;
             }
 
-            super.positionChildAt(position, child, includingParents);
+            final int targetPosition = findPositionForStack(position, child, false /* adding */);
+            super.positionChildAt(targetPosition, child, includingParents);
+
+            setLayoutNeeded();
+        }
+
+        /**
+         * When stack is added or repositioned, find a proper position for it.
+         * This will make sure that pinned stack always stays on top.
+         * @param requestedPosition Position requested by caller.
+         * @param stack Stack to be added or positioned.
+         * @param adding Flag indicates whether we're adding a new stack or positioning an existing.
+         * @return The proper position for the stack.
+         */
+        private int findPositionForStack(int requestedPosition, TaskStack stack, boolean adding) {
+            final int topChildPosition = mChildren.size() - 1;
+            boolean toTop = requestedPosition == POSITION_TOP;
+            toTop |= adding ? requestedPosition >= topChildPosition + 1
+                    : requestedPosition >= topChildPosition;
+            int targetPosition = requestedPosition;
+
+            if (toTop
+                    && mService.isStackVisibleLocked(PINNED_STACK_ID)
+                    && stack.mStackId != PINNED_STACK_ID) {
+                // The pinned stack is always the top most stack (always-on-top) when it is visible.
+                TaskStack topStack = mChildren.get(topChildPosition);
+                if (topStack.mStackId != PINNED_STACK_ID) {
+                    throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
+                }
+
+                // So, stack is moved just below the pinned stack.
+                // When we're adding a new stack the target is the current pinned stack position.
+                // When we're positioning an existing stack the target is the position below pinned
+                // stack, because WindowContainer#positionAt() first removes element and then adds it
+                // to specified place.
+                targetPosition = adding ? topChildPosition : topChildPosition - 1;
+            }
+
+            return targetPosition;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 984cf55..c9bf4fa 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -434,7 +434,7 @@
         // TODO: Will this be more correct if it checks the visibility of its parents?
         // It depends...For example, Tasks and Stacks are only visible if there children are visible
         // but, WindowState are not visible if there parent are not visible. Maybe have the
-        // container specify which direction to treverse for for visibility?
+        // container specify which direction to traverse for visibility?
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             if (wc.isVisible()) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
index 7463102..24893a1 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -47,14 +47,15 @@
         // 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 TaskStack pinnedStack = addPinnedStack();
 
         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);
+        assertGreaterThan(pinnedStackPos, stack2Pos);
+        assertGreaterThan(stack2Pos, stack1Pos);
 
         taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
@@ -66,4 +67,43 @@
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
         assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
     }
+    @Test
+    public void testStackPositionBelowPinnedStack() throws Exception {
+        // Test that no stack can be above pinned stack.
+        final TaskStack pinnedStack = addPinnedStack();
+        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+
+        final WindowContainer taskStackContainer = stack1.getParent();
+
+        final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+        assertGreaterThan(pinnedStackPos, stackPos);
+
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+
+        taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
+        assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+    }
+
+    private TaskStack addPinnedStack() {
+        TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
+        if (pinnedStack == null) {
+            sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
+            pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
+        }
+
+        if (!pinnedStack.isVisible()) {
+            // Stack should contain visible app window to be considered visible.
+            final Task pinnedTask = createTaskInStack(pinnedStack, 0 /* userId */);
+            assertFalse(pinnedStack.isVisible());
+            final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
+            pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+            assertTrue(pinnedStack.isVisible());
+        }
+
+        return pinnedStack;
+    }
 }