diff options
| author | 2024-03-13 02:00:31 +0000 | |
|---|---|---|
| committer | 2024-03-13 02:00:31 +0000 | |
| commit | 4c2f77ff68979c777e4e21d0b69efb33e5d608c6 (patch) | |
| tree | a243457bfb34f4f05bf1b24598917e6a35b136f6 | |
| parent | 1055d17c036ab9f14030a2d7e50338a3f6478597 (diff) | |
| parent | 8b424daba0ec69a918a131efbc236a53fe2773d7 (diff) | |
Merge "[Divider] Add Operation to boost the decor surface" into main
5 files changed, 239 insertions, 34 deletions
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 7b8cdff8e20b..7e77f150b63b 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -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}. */ @@ -505,13 +556,37 @@ public final class TaskFragmentOperation implements Parcelable { } /** + * 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}. */ @NonNull public TaskFragmentOperation build() { return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams, - mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch); + mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue, + mSurfaceTransaction); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0069cdd1e4e8..6fa6957f2949 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -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) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d87e21c0ac92..55dc30cc37d5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -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); } } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a63e106beb55..7e6f5ac7497e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -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( diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 961fdfb14bf3..f506e9fbbb14 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1823,6 +1823,66 @@ public class TaskTests extends WindowTestsBase { } @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); final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); |