Merge "Position stack at top when always on top flag is set"
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 7a729f9..aea767e 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -488,7 +488,7 @@
+ " mAppBounds=" + mAppBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType)
- + " mAlwaysOnTop=" + activityTypeToString(mAlwaysOnTop)
+ + " mAlwaysOnTop=" + alwaysOnTopToString(mAlwaysOnTop)
+ "}";
}
@@ -652,4 +652,14 @@
}
return String.valueOf(applicationType);
}
+
+ /** @hide */
+ public static String alwaysOnTopToString(@AlwaysOnTop int alwaysOnTop) {
+ switch (alwaysOnTop) {
+ case ALWAYS_ON_TOP_UNDEFINED: return "undefined";
+ case ALWAYS_ON_TOP_ON: return "on";
+ case ALWAYS_ON_TOP_OFF: return "off";
+ }
+ return String.valueOf(alwaysOnTop);
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9b01919..05e1893 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -5279,6 +5279,20 @@
}
}
+ public void setAlwaysOnTop(boolean alwaysOnTop) {
+ if (isAlwaysOnTop() == alwaysOnTop) {
+ return;
+ }
+ super.setAlwaysOnTop(alwaysOnTop);
+ final ActivityDisplay display = getDisplay();
+ // positionChildAtTop() must be called even when always on top gets turned off because we
+ // need to make sure that the stack is moved from among always on top windows to below other
+ // always on top windows. Since the position the stack should be inserted into is calculated
+ // properly in {@link ActivityDisplay#getTopInsertPosition()} in both cases, we can just
+ // request that the stack is put at top here.
+ display.positionChildAtTop(this);
+ }
+
void moveToFrontAndResumeStateIfNeeded(ActivityRecord r, boolean moveToFront, boolean setResume,
boolean setPause, String reason) {
if (!moveToFront) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0cbf8a7..98c554d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3427,50 +3427,53 @@
* @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;
-
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;
+ return POSITION_TOP;
}
- // 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) {
+ final int topChildPosition = mChildren.size() - 1;
+ int belowAlwaysOnTopPosition = POSITION_BOTTOM;
+ for (int i = topChildPosition; i >= 0; --i) {
+ if (getStacks().get(i) != stack && !getStacks().get(i).isAlwaysOnTop()) {
+ belowAlwaysOnTopPosition = i;
+ break;
+ }
+ }
+
+ // The max possible position we can insert the stack at.
+ int maxPosition = POSITION_TOP;
+ // The min possible position we can insert the stack at.
+ int minPosition = POSITION_BOTTOM;
+
+ if (stack.isAlwaysOnTop()) {
+ if (hasPinnedStack()) {
+ // Always-on-top stacks go below the pinned stack.
+ maxPosition = getStacks().indexOf(mPinnedStack) - 1;
+ }
+ // Always-on-top stacks need to be above all other stacks.
+ minPosition = belowAlwaysOnTopPosition !=
+ POSITION_BOTTOM ? belowAlwaysOnTopPosition : topChildPosition;
+ } else {
+ // Other stacks need to be below the always-on-top stacks.
+ maxPosition = belowAlwaysOnTopPosition !=
+ POSITION_BOTTOM ? belowAlwaysOnTopPosition : topChildPosition;
+ }
+
+ int targetPosition = requestedPosition;
+ targetPosition = Math.min(targetPosition, maxPosition);
+ targetPosition = Math.max(targetPosition, minPosition);
+
+ int prevPosition = getStacks().indexOf(stack);
+ // The positions we calculated above (maxPosition, minPosition) do not take into
+ // consideration the following edge cases.
+ // 1) We need to adjust the position depending on the value "adding".
+ // 2) When we are moving a stack to another position, we also need to adjust the
+ // position depending on whether the stack is moving to a higher or lower position.
+ if ((targetPosition != requestedPosition) &&
+ (adding || targetPosition < prevPosition)) {
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;
- }
- 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;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 76a060e..e01cebd 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -726,18 +726,33 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
final int prevWindowingMode = getWindowingMode();
+ final boolean prevIsAlwaysOnTop = isAlwaysOnTop();
super.onConfigurationChanged(newParentConfig);
// Only need to update surface size here since the super method will handle updating
// surface position.
updateSurfaceSize(getPendingTransaction());
final int windowingMode = getWindowingMode();
+ final boolean isAlwaysOnTop = isAlwaysOnTop();
- if (mDisplayContent == null || prevWindowingMode == windowingMode) {
+ if (mDisplayContent == null) {
return;
}
- mDisplayContent.onStackWindowingModeChanged(this);
- updateBoundsForWindowModeChange();
+
+ if (prevWindowingMode != windowingMode) {
+ mDisplayContent.onStackWindowingModeChanged(this);
+ updateBoundsForWindowModeChange();
+ }
+
+ if (prevIsAlwaysOnTop != isAlwaysOnTop) {
+ // positionStackAt(POSITION_TOP, this) must be called even when always on top gets
+ // turned off because we need to make sure that the stack is moved from among always on
+ // top windows to below other always on top windows. Since the position the stack should
+ // be inserted into is calculated properly in
+ // {@link DisplayContent#findPositionForStack()} in both cases, we can just request that
+ // the stack is put at top here.
+ mDisplayContent.positionStackAt(POSITION_TOP, this);
+ }
}
private void updateSurfaceBounds() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8fe7063..fee0fcb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -246,6 +246,19 @@
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
+
+ if ((index < 0 && index != POSITION_BOTTOM)
+ || (index > mChildren.size() && index != POSITION_TOP)) {
+ throw new IllegalArgumentException("addChild: invalid position=" + index
+ + ", children number=" + mChildren.size());
+ }
+
+ if (index == POSITION_TOP) {
+ index = mChildren.size();
+ } else if (index == POSITION_BOTTOM) {
+ index = 0;
+ }
+
mChildren.add(index, child);
onChildAdded(child);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 0d0a6ba..4094716 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -473,6 +474,42 @@
}
@Test
+ public void testSetAlwaysOnTop() {
+ final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+ final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ assertTrue(mDefaultDisplay.getStackAbove(homeStack) == pinnedStack);
+
+ final TestActivityStack alwaysOnTopStack = createStackForShouldBeVisibleTest(
+ mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ alwaysOnTopStack.setAlwaysOnTop(true);
+ assertTrue(alwaysOnTopStack.isAlwaysOnTop());
+ // Ensure (non-pinned) always on top stack is put below pinned stack.
+ assertTrue(mDefaultDisplay.getStackAbove(alwaysOnTopStack) == pinnedStack);
+
+ final TestActivityStack nonAlwaysOnTopStack = createStackForShouldBeVisibleTest(
+ mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ // Ensure non always on top stack is put below always on top stacks.
+ assertTrue(mDefaultDisplay.getStackAbove(nonAlwaysOnTopStack) == alwaysOnTopStack);
+
+ final TestActivityStack alwaysOnTopStack2 = createStackForShouldBeVisibleTest(
+ mDefaultDisplay, WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ alwaysOnTopStack2.setAlwaysOnTop(true);
+ assertTrue(alwaysOnTopStack2.isAlwaysOnTop());
+ // Ensure newly created always on top stack is placed above other all always on top stacks.
+ assertTrue(mDefaultDisplay.getStackAbove(alwaysOnTopStack2) == pinnedStack);
+
+ alwaysOnTopStack2.setAlwaysOnTop(false);
+ // Ensure, when always on top is turned off for a stack, the stack is put just below all
+ // other always on top stacks.
+ assertTrue(mDefaultDisplay.getStackAbove(alwaysOnTopStack2) == alwaysOnTopStack);
+ }
+
+ @Test
public void testSplitScreenMoveToFront() throws Exception {
final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(
mDefaultDisplay, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index bb9b1c4..ec068db 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -413,6 +413,14 @@
// Ensure the non-alwaysOnTop stack is put below the three alwaysOnTop stacks, but above the
// existing other non-alwaysOnTop stacks.
assertEquals(nonAlwaysOnTopStack, mDisplayContent.getStacks().get(topPosition - 3));
+
+ anotherAlwaysOnTopStack.setAlwaysOnTop(false);
+ mDisplayContent.positionStackAt(POSITION_TOP, anotherAlwaysOnTopStack);
+ assertFalse(anotherAlwaysOnTopStack.isAlwaysOnTop());
+ topPosition = mDisplayContent.getStacks().size() - 1;
+ // Ensure, when always on top is turned off for a stack, the stack is put just below all
+ // other always on top stacks.
+ assertEquals(anotherAlwaysOnTopStack, mDisplayContent.getStacks().get(topPosition - 2));
}
/**
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 6c7830e..f8b2828 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -256,6 +256,33 @@
}
@Test
+ public void testAddChildByIndex() throws Exception {
+ final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+ final TestWindowContainer root = builder.setLayer(0).build();
+
+ final TestWindowContainer child = root.addChildWindow();
+
+ final TestWindowContainer child2 = builder.setLayer(1).build();
+ final TestWindowContainer child3 = builder.setLayer(2).build();
+ final TestWindowContainer child4 = builder.setLayer(3).build();
+
+ // Test adding at top.
+ root.addChild(child2, POSITION_TOP);
+ assertEquals(child2, root.getChildAt(root.getChildrenCount() - 1));
+
+ // Test adding at bottom.
+ root.addChild(child3, POSITION_BOTTOM);
+ assertEquals(child3, root.getChildAt(0));
+
+ // Test adding in the middle.
+ root.addChild(child4, 1);
+ assertEquals(child3, root.getChildAt(0));
+ assertEquals(child4, root.getChildAt(1));
+ assertEquals(child, root.getChildAt(2));
+ assertEquals(child2, root.getChildAt(3));
+ }
+
+ @Test
public void testPositionChildAt() throws Exception {
final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
final TestWindowContainer root = builder.setLayer(0).build();