Loading core/java/android/window/TaskFragmentOperation.java +80 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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, Loading @@ -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 {} Loading Loading @@ -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; Loading @@ -202,6 +220,8 @@ public final class TaskFragmentOperation implements Parcelable { mIsolatedNav = isolatedNav; mDimOnTask = dimOnTask; mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; mBooleanValue = booleanValue; mSurfaceTransaction = surfaceTransaction; } private TaskFragmentOperation(Parcel in) { Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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(); Loading @@ -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 Loading @@ -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 Loading Loading @@ -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}. */ Loading Loading @@ -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}. */ Loading @@ -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); } } } services/core/java/com/android/server/wm/ActivityRecord.java +5 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading services/core/java/com/android/server/wm/Task.java +73 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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(); Loading @@ -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); } } } services/core/java/com/android/server/wm/WindowOrganizerController.java +21 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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: { Loading @@ -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; } Loading Loading @@ -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( Loading services/tests/wmtests/src/com/android/server/wm/TaskTests.java +60 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
core/java/android/window/TaskFragmentOperation.java +80 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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, Loading @@ -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 {} Loading Loading @@ -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; Loading @@ -202,6 +220,8 @@ public final class TaskFragmentOperation implements Parcelable { mIsolatedNav = isolatedNav; mDimOnTask = dimOnTask; mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; mBooleanValue = booleanValue; mSurfaceTransaction = surfaceTransaction; } private TaskFragmentOperation(Parcel in) { Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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(); Loading @@ -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 Loading @@ -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 Loading Loading @@ -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}. */ Loading Loading @@ -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}. */ Loading @@ -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); } } }
services/core/java/com/android/server/wm/ActivityRecord.java +5 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading
services/core/java/com/android/server/wm/Task.java +73 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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(); Loading @@ -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); } } }
services/core/java/com/android/server/wm/WindowOrganizerController.java +21 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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: { Loading @@ -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; } Loading Loading @@ -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( Loading
services/tests/wmtests/src/com/android/server/wm/TaskTests.java +60 −0 Original line number Diff line number Diff line Loading @@ -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); Loading