Add always on top feature support

Add basic functionalities for always on top feature.

- Add a new flag to WindowConfiguration to represent a task wanting to
be on top.
- Update the logic on changing the z-order of windows to make sure
always on top windows are placed above other windows.

Bug: 69370884
Test: go/wm-smoke
Test: atest DisplayContentTests
Test: Used ArcCompanionLibDemo app to verify that when always-on-top is
      set, the app is above the other Android apps and Chrome windows.

Change-Id: Ie8edeb8ceeed0b9ec154b6031ed6cbe7ecc65b12
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 698b6f7..e65ae5c 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -173,12 +173,22 @@
 
     private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
         int position = mStacks.size();
-        if (position > 0) {
-            final ActivityStack topStack = mStacks.get(position - 1);
-            if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) {
-                // If the top stack is always on top, we move this stack just below it.
-                position--;
+        if (stack.inPinnedWindowingMode()) {
+            // Stack in pinned windowing mode is z-ordered on-top of all other stacks so okay to
+            // just return the candidate position.
+            return Math.min(position, candidatePosition);
+        }
+        while (position > 0) {
+            final ActivityStack targetStack = mStacks.get(position - 1);
+            if (!targetStack.isAlwaysOnTop()) {
+                // We reached a stack that isn't always-on-top.
+                break;
             }
+            if (stack.isAlwaysOnTop() && !targetStack.inPinnedWindowingMode()) {
+                // Always on-top non-pinned windowing mode stacks can go anywhere below pinned stack.
+                break;
+            }
+            position--;
         }
         return Math.min(position, candidatePosition);
     }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 7df057c..f2ce63c 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -302,6 +302,18 @@
         onOverrideConfigurationChanged(mTmpConfig);
     }
 
+    /** Sets the always on top flag for this configuration container.
+     *  When you call this function, make sure that the following functions are called as well to
+     *  keep proper z-order.
+     *  - {@Link DisplayContent#positionStackAt(POSITION_TOP, TaskStack)};
+     *  - {@Link ActivityDisplay#positionChildAtTop(ActivityStack)};
+     * */
+    public void setAlwaysOnTop(boolean alwaysOnTop) {
+        mTmpConfig.setTo(getOverrideConfiguration());
+        mTmpConfig.windowConfiguration.setAlwaysOnTop(alwaysOnTop);
+        onOverrideConfigurationChanged(mTmpConfig);
+    }
+
     /**
      * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
      * with another activity.
@@ -513,7 +525,7 @@
         return toString();
     }
 
-    boolean isAlwaysOnTop() {
+    public boolean isAlwaysOnTop() {
         return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b0e6208..578a1489 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1569,6 +1569,11 @@
     }
 
     @VisibleForTesting
+    WindowList<TaskStack> getStacks() {
+        return mTaskStackContainers.mChildren;
+    }
+
+    @VisibleForTesting
     TaskStack getTopStack() {
         return mTaskStackContainers.getTopStack();
     }
@@ -3429,21 +3434,44 @@
             boolean toTop = requestedPosition == POSITION_TOP;
             toTop |= adding ? requestedPosition >= topChildPosition + 1
                     : requestedPosition >= topChildPosition;
-            int targetPosition = requestedPosition;
 
-            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED && hasPinnedStack()) {
-                // The pinned stack is always the top most stack (always-on-top) when it is present.
-                TaskStack topStack = mChildren.get(topChildPosition);
-                if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
-                    throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
+            if (stack.inPinnedWindowingMode()) {
+                // Stack in pinned windowing mode is z-ordered on-top of all other stacks so okay to
+                // just return the candidate position.
+                return requestedPosition;
+            }
+
+            // We might call mChildren.get() with targetPosition below, but targetPosition might be
+            // POSITION_TOP (INTEGER_MAX). We need to adjust the value to the actual index in the
+            // array.
+            int targetPosition = toTop ? topChildPosition : requestedPosition;
+            // Note that the index we should return varies depending on the value of adding.
+            // When we're adding a new stack the index is the current target position.
+            // When we're positioning an existing stack the index is the position below the target
+            // stack, because WindowContainer#positionAt() first removes element and then adds
+            // it to specified place.
+            if (toTop && adding) {
+                targetPosition++;
+            }
+
+            // Note we might have multiple always on top windows.
+            while (targetPosition >= 0) {
+                int adjustedTargetStackId = adding ? targetPosition - 1 : targetPosition;
+                if (adjustedTargetStackId < 0 || adjustedTargetStackId > topChildPosition) {
+                    break;
                 }
-
-                // 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;
+                TaskStack targetStack = mChildren.get(adjustedTargetStackId);
+                if (!targetStack.isAlwaysOnTop()) {
+                    // We reached a stack that isn't always-on-top.
+                    break;
+                }
+                if (stack.isAlwaysOnTop() && !targetStack.inPinnedWindowingMode()) {
+                    // Always on-top non-pinned windowing mode stacks can go anywhere below pinned
+                    // stack.
+                    break;
+                }
+                // We go one level down, looking for the place on which the new stack can be put.
+                targetPosition--;
             }
 
             return targetPosition;