From f85197be4a47061b39e7e10224fa0b16b6dc3eda Mon Sep 17 00:00:00 2001 From: Kazuki Takise Date: Mon, 18 Jun 2018 18:18:36 +0900 Subject: [PATCH] Position stack at top when always on top flag is set When always on top is set, the position of the stack must be changed in ActivityDisplay and in DisplayContent. This CL introduces this logic in ActivityStack.setAlwaysOnTop(). Bug: 69370884 Test: go/wm-smoke Test: atest DisplayContentTests Test: atest WindowContainerTests Change-Id: Ie7efe175175a4db209a6b0f3476d9dfc27432df5 --- .../java/android/app/WindowConfiguration.java | 12 ++- .../com/android/server/am/ActivityStack.java | 14 ++++ .../com/android/server/wm/DisplayContent.java | 79 ++++++++++--------- .../java/com/android/server/wm/TaskStack.java | 21 ++++- .../android/server/wm/WindowContainer.java | 13 +++ .../android/server/am/ActivityStackTests.java | 37 +++++++++ .../server/wm/DisplayContentTests.java | 8 ++ .../server/wm/WindowContainerTests.java | 27 +++++++ 8 files changed, 169 insertions(+), 42 deletions(-) diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 7a729f97bb9a5..aea767ecb3f0d 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -488,7 +488,7 @@ public class WindowConfiguration implements Parcelable, Comparable extends ConfigurationContai } } + 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 578a1489a0a69..b388fa1840782 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3430,50 +3430,53 @@ class DisplayContent extends WindowContainer= 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 76c9c262cd762..547521c2e7bba 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 @@ public class TaskStack extends WindowContainer implements @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 19c5a3d6452a9..b699159e53b8f 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 @@ class WindowContainer extends ConfigurationContainer< + " 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 01425ed51b559..a697ca56b96c4 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 @@ package com.android.server.am; 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; @@ -472,6 +473,42 @@ public class ActivityStackTests extends ActivityTestsBase { assertTrue(mDefaultDisplay.getStackAbove(homeStack) == fullscreenStack2); } + @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( 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 bb9b1c41fcf34..ec068dbcd755e 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 @@ public class DisplayContentTests extends WindowTestsBase { // 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 6c7830e5cf79f..f8b282848489f 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java @@ -255,6 +255,33 @@ public class WindowContainerTests extends WindowTestsBase { assertNull(controller.mContainer); } + @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();