Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 148d00ac authored by Kazuki Takise's avatar Kazuki Takise
Browse files

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
parent 0287faf3
Loading
Loading
Loading
Loading
+47 −4
Original line number Diff line number Diff line
@@ -59,6 +59,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
    /** 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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
    /** 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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        dest.writeParcelable(mAppBounds, flags);
        dest.writeInt(mWindowingMode);
        dest.writeInt(mActivityType);
        dest.writeInt(mFlags);
    }

    private void readFromParcel(Parcel source) {
@@ -175,6 +184,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        mAppBounds = source.readParcelable(Rect.class.getClassLoader());
        mWindowingMode = source.readInt();
        mActivityType = source.readInt();
        mFlags = source.readInt();
    }

    @Override
@@ -222,6 +232,23 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        setAppBounds(other.mAppBounds);
        setWindowingMode(other.mWindowingMode);
        setActivityType(other.mActivityType);
        setFlags(other.mFlags);
    }

    /** Set this object to completely undefined.
@@ -295,6 +323,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        setBounds(null);
        setWindowingMode(WINDOWING_MODE_UNDEFINED);
        setActivityType(ACTIVITY_TYPE_UNDEFINED);
        setFlags(0);
    }

    /**
@@ -312,6 +341,10 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
            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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
            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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        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 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu

        result = 31 * result + mWindowingMode;
        result = 31 * result + mActivityType;
        result = 31 * result + mFlags;
        return result;
    }

@@ -434,7 +475,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
        return "{ mBounds=" + mBounds
                + " mAppBounds=" + mAppBounds
                + " mWindowingMode=" + windowingModeToString(mWindowingMode)
                + " mActivityType=" + activityTypeToString(mActivityType) + "}";
                + " mActivityType=" + activityTypeToString(mActivityType)
                + " mFlags=0x" + Integer.toHexString(mFlags)
                + "}";
    }

    /**
@@ -520,7 +563,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
     * @hide
     */
    public boolean isAlwaysOnTop() {
        return mWindowingMode == WINDOWING_MODE_PINNED;
        return mWindowingMode == WINDOWING_MODE_PINNED || (mFlags & PFLAG_ALWAYS_ON_TOP) != 0;
    }

    /**
+15 −5
Original line number Diff line number Diff line
@@ -173,12 +173,22 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>

    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);
    }
+13 −1
Original line number Diff line number Diff line
@@ -302,6 +302,18 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
        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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
        return toString();
    }

    boolean isAlwaysOnTop() {
    public boolean isAlwaysOnTop() {
        return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
    }

+41 −13
Original line number Diff line number Diff line
@@ -1568,6 +1568,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        return mTaskStackContainers.getStack(windowingMode, activityType);
    }

    @VisibleForTesting
    WindowList<TaskStack> getStacks() {
        return mTaskStackContainers.mChildren;
    }

    @VisibleForTesting
    TaskStack getTopStack() {
        return mTaskStackContainers.getTopStack();
@@ -3429,21 +3434,44 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
            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;
            }

                // 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
            // 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.
                targetPosition = adding ? topChildPosition : topChildPosition - 1;
            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;
                }
                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;
+24 −9
Original line number Diff line number Diff line
@@ -380,21 +380,36 @@ public class DisplayContentTests extends WindowTestsBase {
    }

    /**
     * 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));
    }

    /**