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

Commit 8b424dab authored by Jiaming Liu's avatar Jiaming Liu
Browse files

[Divider] Add Operation to boost the decor surface

This is needed to draw the veils on the decor surface when dragging the
divider. A cover surface is added to ensure that the content of the
below windows are invisible.

Bug: 293654166
Test: atest TaskTests
Change-Id: I894528a6eb7880deb94cb04893ec8eea42c062b5
parent 9a51c141
Loading
Loading
Loading
Loading
+80 −5
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.SurfaceControl;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@ public final class TaskFragmentOperation implements Parcelable {
    /**
     * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
     * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
     * event callback.
     * event callback. The decor surface can be used to draw the divider between TaskFragments or
     * other decorations.
     */
    public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;

@@ -135,6 +137,15 @@ public final class TaskFragmentOperation implements Parcelable {
     */
    public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;

    /**
     * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
     * below any TaskFragments in untrusted mode or any activities with uid different from the
     * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
     * surface is placed above all the non-boosted windows in the Task, the content of these
     * non-boosted windows will be hidden and inputs are disabled.
     */
    public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;

    @IntDef(prefix = { "OP_TYPE_" }, value = {
            OP_TYPE_UNKNOWN,
            OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@ public final class TaskFragmentOperation implements Parcelable {
            OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
            OP_TYPE_SET_DIM_ON_TASK,
            OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
            OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface OperationType {}
@@ -186,12 +198,18 @@ public final class TaskFragmentOperation implements Parcelable {

    private final boolean mMoveToBottomIfClearWhenLaunch;

    private final boolean mBooleanValue;

    @Nullable
    private final SurfaceControl.Transaction mSurfaceTransaction;

    private TaskFragmentOperation(@OperationType int opType,
            @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
            @Nullable IBinder activityToken, @Nullable Intent activityIntent,
            @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
            @Nullable TaskFragmentAnimationParams animationParams,
            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
            boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
            boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
        mOpType = opType;
        mTaskFragmentCreationParams = taskFragmentCreationParams;
        mActivityToken = activityToken;
@@ -202,6 +220,8 @@ public final class TaskFragmentOperation implements Parcelable {
        mIsolatedNav = isolatedNav;
        mDimOnTask = dimOnTask;
        mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
        mBooleanValue = booleanValue;
        mSurfaceTransaction = surfaceTransaction;
    }

    private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@ public final class TaskFragmentOperation implements Parcelable {
        mIsolatedNav = in.readBoolean();
        mDimOnTask = in.readBoolean();
        mMoveToBottomIfClearWhenLaunch = in.readBoolean();
        mBooleanValue = in.readBoolean();
        mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
    }

    @Override
@@ -229,6 +251,8 @@ public final class TaskFragmentOperation implements Parcelable {
        dest.writeBoolean(mIsolatedNav);
        dest.writeBoolean(mDimOnTask);
        dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
        dest.writeBoolean(mBooleanValue);
        dest.writeTypedObject(mSurfaceTransaction, flags);
    }

    @NonNull
@@ -324,6 +348,22 @@ public final class TaskFragmentOperation implements Parcelable {
        return mMoveToBottomIfClearWhenLaunch;
    }

    /** Returns the boolean value for this operation. */
    public boolean getBooleanValue() {
        return mBooleanValue;
    }

    /**
     * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
     * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
     * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
     * change the decor surface layers.
     */
    @Nullable
    public SurfaceControl.Transaction getSurfaceTransaction() {
        return mSurfaceTransaction;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@ public final class TaskFragmentOperation implements Parcelable {
        sb.append(", isolatedNav=").append(mIsolatedNav);
        sb.append(", dimOnTask=").append(mDimOnTask);
        sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
        sb.append(", booleanValue=").append(mBooleanValue);
        if (mSurfaceTransaction != null) {
            sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
        }

        sb.append('}');
        return sb.toString();
@@ -358,7 +402,7 @@ public final class TaskFragmentOperation implements Parcelable {
    public int hashCode() {
        return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
                mMoveToBottomIfClearWhenLaunch);
                mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
    }

    @Override
@@ -376,7 +420,9 @@ public final class TaskFragmentOperation implements Parcelable {
                && Objects.equals(mAnimationParams, other.mAnimationParams)
                && mIsolatedNav == other.mIsolatedNav
                && mDimOnTask == other.mDimOnTask
                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
                && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
                && mBooleanValue == other.mBooleanValue
                && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
    }

    @Override
@@ -414,6 +460,11 @@ public final class TaskFragmentOperation implements Parcelable {

        private boolean mMoveToBottomIfClearWhenLaunch;

        private boolean mBooleanValue;

        @Nullable
        private SurfaceControl.Transaction mSurfaceTransaction;

        /**
         * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
         */
@@ -504,6 +555,29 @@ public final class TaskFragmentOperation implements Parcelable {
            return this;
        }

        /**
         * Sets the boolean value for this operation.
         * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
         */
        @NonNull
        public Builder setBooleanValue(boolean booleanValue) {
            mBooleanValue = booleanValue;
            return this;
        }

        /**
         * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
         * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
         * specify a {@link SurfaceControl.Transaction} that should be applied together with the
         * transaction to change the decor surface layers.
         */
        @NonNull
        public Builder setSurfaceTransaction(
                @Nullable SurfaceControl.Transaction surfaceTransaction) {
            mSurfaceTransaction = surfaceTransaction;
            return this;
        }

        /**
         * Constructs the {@link TaskFragmentOperation}.
         */
@@ -511,7 +585,8 @@ public final class TaskFragmentOperation implements Parcelable {
        public TaskFragmentOperation build() {
            return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
                    mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
                    mSurfaceTransaction);
        }
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -7786,8 +7786,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    @Override
    void prepareSurfaces() {
        final boolean show = isVisible() || isAnimating(PARENTS,
                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
        final boolean show = (isVisible()
                // Ensure that the activity content is hidden when the decor surface is boosted to
                // prevent UI redressing attack.
                && !getTask().isDecorSurfaceBoosted())
                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
                        | ANIMATION_TYPE_PREDICT_BACK);

        if (mSurfaceControl != null) {
+73 −10
Original line number Diff line number Diff line
@@ -3741,7 +3741,9 @@ class Task extends TaskFragment {
            wc.assignChildLayers(t);
            if (!wc.needsZBoost()) {
                // Place the decor surface under any untrusted content.
                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
                if (mDecorSurfaceContainer != null
                        && !mDecorSurfaceContainer.mIsBoosted
                        && !decorSurfacePlaced
                        && shouldPlaceDecorSurfaceBelowContainer(wc)) {
                    mDecorSurfaceContainer.assignLayer(t, layer++);
                    decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@ class Task extends TaskFragment {
                }

                // Place the decor surface just above the owner TaskFragment.
                if (mDecorSurfaceContainer != null && !decorSurfacePlaced
                if (mDecorSurfaceContainer != null
                        && !mDecorSurfaceContainer.mIsBoosted
                        && !decorSurfacePlaced
                        && wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
                    mDecorSurfaceContainer.assignLayer(t, layer++);
                    decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@ class Task extends TaskFragment {
            }
        }

        // If not placed yet, the decor surface should be on top of all non-boosted children.
        if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
        // Boost the decor surface above other non-boosted windows if requested. The cover surface
        // will ensure that the content of the windows below are invisible.
        if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
            mDecorSurfaceContainer.assignLayer(t, layer++);
            decorSurfacePlaced = true;
        }

        for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@ class Task extends TaskFragment {
        return !isOwnActivity && !isTrustedTaskFragment;
    }

    void setDecorSurfaceBoosted(
            @NonNull TaskFragment ownerTaskFragment,
            boolean isBoosted,
            @Nullable SurfaceControl.Transaction clientTransaction) {
        if (mDecorSurfaceContainer == null
                || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
            return;
        }
        mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
        // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
        // surface visibility is updated with prepareSurfaces()
        assignChildLayers();
    }

    boolean isDecorSurfaceBoosted() {
        return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
    }

    boolean isTaskId(int taskId) {
        return mTaskId == taskId;
    }
@@ -6796,14 +6818,35 @@ class Task extends TaskFragment {
    }

    /**
     * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
     * below children windows except for own Activities and TaskFragment in fully trusted mode.
     * A class managing the decor surface.
     *
     * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
     * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
     * decor surface is created and shared with the client app with
     * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
     * be removed with
     * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
     *
     * When boosted with
     * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
     * surface is placed above all non-boosted windows in the Task, but all the content below it
     * will be hidden to prevent UI redressing attacks. This can be used by the draggable
     * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
     * dragging to indicate new bounds.
     */
    @VisibleForTesting
    class DecorSurfaceContainer {

        // The container surface is the parent of the decor surface. The container surface
        // should NEVER be shared with the client. It is used to ensure that the decor surface has
        // a z-order in the Task that is managed by WM core and cannot be updated by the client
        // process.
        @VisibleForTesting
        @NonNull final SurfaceControl mContainerSurface;

        // The decor surface is shared with the client process owning the
        // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
        // or other decorations.
        @VisibleForTesting
        @NonNull final SurfaceControl mDecorSurface;

@@ -6812,12 +6855,18 @@ class Task extends TaskFragment {
        @VisibleForTesting
        @NonNull TaskFragment mOwnerTaskFragment;

        private boolean mIsBoosted;

        // The surface transactions that will be applied when the layer is reassigned.
        @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
                new ArrayList<>();

        private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
            mOwnerTaskFragment = initialOwner;
            mContainerSurface = makeSurface().setContainerLayer()
                    .setParent(mSurfaceControl)
                    .setName(mSurfaceControl + " - decor surface container")
                    .setEffectLayer()
                    .setContainerLayer()
                    .setHidden(false)
                    .setCallsite("Task.DecorSurfaceContainer")
                    .build();
@@ -6830,14 +6879,28 @@ class Task extends TaskFragment {
                    .build();
        }

        private void setBoosted(
                boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
            mIsBoosted = isBoosted;
            // The client transaction will be applied together with the next assignLayer.
            if (clientTransaction != null) {
                mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
            }
        }

        private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
            t.setLayer(mContainerSurface, layer);
            t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
            for (int i = 0; i < mPendingClientTransactions.size(); i++) {
                t.merge(mPendingClientTransactions.get(i));
            }
            mPendingClientTransactions.clear();
        }

        private void release() {
            mDecorSurface.release();
            mContainerSurface.release();
            getSyncTransaction()
                    .remove(mDecorSurface)
                    .remove(mContainerSurface);
        }
    }
}
+21 −17
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
import com.android.window.flags.Flags;

import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                break;
            }
            case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
                final Task task = taskFragment.getTask();
                task.moveOrCreateDecorSurfaceFor(taskFragment);
                taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
                break;
            }
            case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
                final Task task = taskFragment.getTask();
                task.removeDecorSurface();
                taskFragment.getTask().removeDecorSurface();
                break;
            }
            case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
                        operation.isMoveToBottomIfClearWhenLaunch());
                break;
            }
            case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
                if (Flags.activityEmbeddingInteractiveDividerFlag()) {
                    final SurfaceControl.Transaction clientTransaction =
                            operation.getSurfaceTransaction();
                    if (clientTransaction != null) {
                        // Sanitize the client transaction. sanitize() silently removes invalid
                        // operations and does not throw or provide signal about whether there are
                        // any invalid operations.
                        clientTransaction.sanitize(caller.mPid, caller.mUid);
                    }
                    taskFragment.getTask().setDecorSurfaceBoosted(
                            taskFragment,
                            operation.getBooleanValue() /* isBoosted */,
                            clientTransaction);
                }
                break;
            }
        }
        return effects;
    }
@@ -1616,19 +1633,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
            return false;
        }

        // TODO (b/293654166) remove the decor surface checks once we clear security reviews
        if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
                || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
            final Throwable exception = new SecurityException(
                    "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
                            + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
            );
            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
                    opType, exception);
            return false;
        }

        if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
            final Throwable exception = new SecurityException(
+60 −0
Original line number Diff line number Diff line
@@ -1822,6 +1822,66 @@ public class TaskTests extends WindowTestsBase {
        verify(fragment2).assignLayer(t, 2);
    }

    @Test
    public void testAssignChildLayers_boostedDecorSurfacePlacement() {
        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
        final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
        final ActivityRecord unembeddedActivity = task.getTopMostActivity();

        final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
        final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
        final SurfaceControl.Transaction t = task.getSyncTransaction();
        final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);

        doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
        spyOn(unembeddedActivity);
        spyOn(fragment1);
        spyOn(fragment2);

        doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
        doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
        doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
        doReturn(true).when(fragment1).isVisible();

        task.moveOrCreateDecorSurfaceFor(fragment1);

        clearInvocations(t);
        clearInvocations(unembeddedActivity);
        clearInvocations(fragment1);
        clearInvocations(fragment2);

        // The decor surface should be placed above all the windows when boosted and the cover
        // surface should show.
        task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);

        verify(unembeddedActivity).assignLayer(t, 0);
        verify(fragment1).assignLayer(t, 1);
        verify(fragment2).assignLayer(t, 2);
        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);

        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
        verify(t).merge(clientTransaction);

        clearInvocations(t);
        clearInvocations(unembeddedActivity);
        clearInvocations(fragment1);
        clearInvocations(fragment2);

        // The decor surface should be placed just above the owner TaskFragment and the cover
        // surface should hide.
        task.moveOrCreateDecorSurfaceFor(fragment1);
        task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);

        verify(unembeddedActivity).assignLayer(t, 0);
        verify(fragment1).assignLayer(t, 1);
        verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
        verify(fragment2).assignLayer(t, 3);

        verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
        verify(t).merge(clientTransaction);

    }

    @Test
    public void testMoveTaskFragmentsToBottomIfNeeded() {
        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);