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/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 21d6762..09c7981 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -59,6 +59,11 @@
/** The current windowing mode of the configuration. */
private @WindowingMode int mWindowingMode;
+ private int mFlags;
+
+ /** Indicates that this window should always be on top of the other windows. */
+ private static final int PFLAG_ALWAYS_ON_TOP = 1 << 0;
+
/** Windowing mode is currently not defined. */
public static final int WINDOWING_MODE_UNDEFINED = 0;
/** Occupies the full area of the screen or the parent container. */
@@ -136,13 +141,16 @@
/** Bit that indicates that the {@link #mActivityType} changed.
* @hide */
public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3;
-
+ /** Bit that indicates that the {@link #mFlags} changed.
+ * @hide */
+ public static final int WINDOW_CONFIG_FLAGS = 1 << 4;
/** @hide */
@IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
WINDOW_CONFIG_BOUNDS,
WINDOW_CONFIG_APP_BOUNDS,
WINDOW_CONFIG_WINDOWING_MODE,
- WINDOW_CONFIG_ACTIVITY_TYPE
+ WINDOW_CONFIG_ACTIVITY_TYPE,
+ WINDOW_CONFIG_FLAGS
})
public @interface WindowConfig {}
@@ -168,6 +176,7 @@
dest.writeParcelable(mAppBounds, flags);
dest.writeInt(mWindowingMode);
dest.writeInt(mActivityType);
+ dest.writeInt(mFlags);
}
private void readFromParcel(Parcel source) {
@@ -175,6 +184,7 @@
mAppBounds = source.readParcelable(Rect.class.getClassLoader());
mWindowingMode = source.readInt();
mActivityType = source.readInt();
+ mFlags = source.readInt();
}
@Override
@@ -222,6 +232,23 @@
setAppBounds(rect.left, rect.top, rect.right, rect.bottom);
}
+ private void setFlags(int flags) {
+ mFlags = flags;
+ }
+
+ /**
+ * Sets whether this window should be always on top.
+ * @param alwaysOnTop {@code true} to set window always on top, otherwise {@code false}
+ * @hide
+ */
+ public void setAlwaysOnTop(boolean alwaysOnTop) {
+ if (alwaysOnTop) {
+ mFlags |= PFLAG_ALWAYS_ON_TOP;
+ } else {
+ mFlags &= ~PFLAG_ALWAYS_ON_TOP;
+ }
+ }
+
/**
* @see #setAppBounds(Rect)
* @see #getAppBounds()
@@ -281,6 +308,7 @@
setAppBounds(other.mAppBounds);
setWindowingMode(other.mWindowingMode);
setActivityType(other.mActivityType);
+ setFlags(other.mFlags);
}
/** Set this object to completely undefined.
@@ -295,6 +323,7 @@
setBounds(null);
setWindowingMode(WINDOWING_MODE_UNDEFINED);
setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ setFlags(0);
}
/**
@@ -312,6 +341,10 @@
changed |= WINDOW_CONFIG_BOUNDS;
setBounds(delta.mBounds);
}
+ if (delta.mFlags != mFlags) {
+ changed |= WINDOW_CONFIG_FLAGS;
+ setFlags(delta.mFlags);
+ }
if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) {
changed |= WINDOW_CONFIG_APP_BOUNDS;
setAppBounds(delta.mAppBounds);
@@ -347,6 +380,10 @@
changes |= WINDOW_CONFIG_BOUNDS;
}
+ if (mFlags != other.mFlags) {
+ changes |= WINDOW_CONFIG_FLAGS;
+ }
+
// Make sure that one of the values is not null and that they are not equal.
if ((compareUndefined || other.mAppBounds != null)
&& mAppBounds != other.mAppBounds
@@ -399,6 +436,9 @@
n = mActivityType - that.mActivityType;
if (n != 0) return n;
+ n = mFlags - that.mFlags;
+ if (n != 0) return n;
+
// if (n != 0) return n;
return n;
}
@@ -425,6 +465,7 @@
result = 31 * result + mWindowingMode;
result = 31 * result + mActivityType;
+ result = 31 * result + mFlags;
return result;
}
@@ -434,7 +475,9 @@
return "{ mBounds=" + mBounds
+ " mAppBounds=" + mAppBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
- + " mActivityType=" + activityTypeToString(mActivityType) + "}";
+ + " mActivityType=" + activityTypeToString(mActivityType)
+ + " mFlags=0x" + Integer.toHexString(mFlags)
+ + "}";
}
/**
@@ -520,7 +563,7 @@
* @hide
*/
public boolean isAlwaysOnTop() {
- return mWindowingMode == WINDOWING_MODE_PINNED;
+ return mWindowingMode == WINDOWING_MODE_PINNED || (mFlags & PFLAG_ALWAYS_ON_TOP) != 0;
}
/**
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;
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 ac196f9..c3b2f87 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -380,21 +380,36 @@
}
/**
- * This test enforces that the pinned stack is always kept as the top stack.
+ * This test enforces that alwaysOnTop stack is placed at proper position.
*/
@Test
- public void testPinnedStackLocation() {
+ public void testAlwaysOnTopStackLocation() {
+ final TaskStack alwaysOnTopStack = createTaskStackOnDisplay(mDisplayContent);
+ alwaysOnTopStack.setAlwaysOnTop(true);
+ mDisplayContent.positionStackAt(POSITION_TOP, alwaysOnTopStack);
+ assertTrue(alwaysOnTopStack.isAlwaysOnTop());
+ assertEquals(alwaysOnTopStack, mDisplayContent.getTopStack());
+
final TaskStack pinnedStack = createStackControllerOnStackOnDisplay(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
- // Ensure that the pinned stack is the top stack
assertEquals(pinnedStack, mDisplayContent.getPinnedStack());
assertEquals(pinnedStack, mDisplayContent.getTopStack());
- // By default, this should try to create a new stack on top
- final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent);
- // Ensure that the other stack is on the display.
- assertEquals(mDisplayContent, otherStack.getDisplayContent());
- // Ensure that the pinned stack is still on top
- assertEquals(pinnedStack, mDisplayContent.getTopStack());
+
+ final TaskStack anotherAlwaysOnTopStack = createTaskStackOnDisplay(mDisplayContent);
+ anotherAlwaysOnTopStack.setAlwaysOnTop(true);
+ mDisplayContent.positionStackAt(POSITION_TOP, anotherAlwaysOnTopStack);
+ assertTrue(anotherAlwaysOnTopStack.isAlwaysOnTop());
+ int topPosition = mDisplayContent.getStacks().size() - 1;
+ // Ensure the new alwaysOnTop stack is put below the pinned stack, but on top of the
+ // existing alwaysOnTop stack.
+ assertEquals(anotherAlwaysOnTopStack, mDisplayContent.getStacks().get(topPosition - 1));
+
+ final TaskStack nonAlwaysOnTopStack = createTaskStackOnDisplay(mDisplayContent);
+ assertEquals(mDisplayContent, nonAlwaysOnTopStack.getDisplayContent());
+ topPosition = mDisplayContent.getStacks().size() - 1;
+ // 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));
}
/**