diff options
| author | 2023-11-29 07:07:39 +0000 | |
|---|---|---|
| committer | 2023-11-29 07:07:39 +0000 | |
| commit | 7fd1bda9069d8293911e29fef0e98ed2068245e9 (patch) | |
| tree | 99d246773965fa78e0f35b1a7d77a4af907e74e9 | |
| parent | ec820a85358c873ed2e059116fb480d58cbad573 (diff) | |
| parent | 2b2b5b703351009a1c70e5344998b55e58903b79 (diff) | |
Merge "Add decor surface API for TaskFragmentOrganizer" into main
11 files changed, 439 insertions, 12 deletions
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 4e0f9a51c0a0..0ec9ffe6390b 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -108,6 +108,18 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + /** + * 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. + */ + public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14; + + /** + * Removes the decor surface in the parent Task of the TaskFragment. + */ + public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 15; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -124,6 +136,8 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ISOLATED_NAVIGATION, OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, OP_TYPE_REORDER_TO_TOP_OF_TASK, + OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE, + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java index e6eeca4b4801..a77c23475c60 100644 --- a/core/java/android/window/TaskFragmentParentInfo.java +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -22,6 +22,9 @@ import android.app.WindowConfiguration; import android.content.res.Configuration; import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControl; + +import java.util.Objects; /** * The information about the parent Task of a particular TaskFragment @@ -37,12 +40,15 @@ public class TaskFragmentParentInfo implements Parcelable { private final boolean mHasDirectActivity; + @Nullable private final SurfaceControl mDecorSurface; + public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, - boolean visible, boolean hasDirectActivity) { + boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) { mConfiguration.setTo(configuration); mDisplayId = displayId; mVisible = visible; mHasDirectActivity = hasDirectActivity; + mDecorSurface = decorSurface; } public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { @@ -50,6 +56,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDisplayId = info.mDisplayId; mVisible = info.mVisible; mHasDirectActivity = info.mHasDirectActivity; + mDecorSurface = info.mDecorSurface; } /** The {@link Configuration} of the parent Task */ @@ -92,7 +99,13 @@ public class TaskFragmentParentInfo implements Parcelable { return false; } return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId - && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity; + && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity + && mDecorSurface == that.mDecorSurface; + } + + @Nullable + public SurfaceControl getDecorSurface() { + return mDecorSurface; } @WindowConfiguration.WindowingMode @@ -107,6 +120,7 @@ public class TaskFragmentParentInfo implements Parcelable { + ", displayId=" + mDisplayId + ", visible=" + mVisible + ", hasDirectActivity=" + mHasDirectActivity + + ", decorSurface=" + mDecorSurface + "}"; } @@ -128,7 +142,8 @@ public class TaskFragmentParentInfo implements Parcelable { return mConfiguration.equals(that.mConfiguration) && mDisplayId == that.mDisplayId && mVisible == that.mVisible - && mHasDirectActivity == that.mHasDirectActivity; + && mHasDirectActivity == that.mHasDirectActivity + && mDecorSurface == that.mDecorSurface; } @Override @@ -137,6 +152,7 @@ public class TaskFragmentParentInfo implements Parcelable { result = 31 * result + mDisplayId; result = 31 * result + (mVisible ? 1 : 0); result = 31 * result + (mHasDirectActivity ? 1 : 0); + result = 31 * result + Objects.hashCode(mDecorSurface); return result; } @@ -146,6 +162,7 @@ public class TaskFragmentParentInfo implements Parcelable { dest.writeInt(mDisplayId); dest.writeBoolean(mVisible); dest.writeBoolean(mHasDirectActivity); + dest.writeTypedObject(mDecorSurface, flags); } private TaskFragmentParentInfo(Parcel in) { @@ -153,6 +170,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDisplayId = in.readInt(); mVisible = in.readBoolean(); mHasDirectActivity = in.readBoolean(); + mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR); } public static final Creator<TaskFragmentParentInfo> CREATOR = diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 50cfd941adb3..4c2433fab2f8 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -443,7 +443,8 @@ public class OverlayPresentationTest { assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); mSplitController.updateOverlayContainer(mTransaction, overlayContainer); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 02031a67e7e3..8c274a26177d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -1139,7 +1139,8 @@ public class SplitControllerTest { public void testOnTransactionReady_taskFragmentParentInfoChanged() { final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */); transaction.addChange(new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) .setTaskId(TASK_ID) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index e56c8ab686e7..7b77235f66f7 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -79,14 +79,16 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertEquals(WINDOWING_MODE_FREEFORM, taskContainer.getWindowingModeForSplitTaskFragment(splitBounds)); @@ -106,13 +108,15 @@ public class TaskContainerTest { configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertFalse(taskContainer.isInPictureInPicture()); configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED); taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration, - DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */)); + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */)); assertTrue(taskContainer.isInPictureInPicture()); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index de802b9b0e4d..5f082124dbcb 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -422,6 +422,11 @@ class Task extends TaskFragment { // TODO: remove this once the recents animation is moved to the Shell SurfaceControl mLastRecentsAnimationOverlay; + // A surface that is used by TaskFragmentOrganizer to place content on top of own activities and + // trusted TaskFragments. + @Nullable + DecorSurfaceContainer mDecorSurfaceContainer; + static final int LAYER_RANK_INVISIBLE = -1; // Ranking (from top) of this task among all visible tasks. (-1 means it's not visible) // This number will be assigned when we evaluate OOM scores for all visible tasks. @@ -1540,6 +1545,11 @@ class Task extends TaskFragment { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } + if (mDecorSurfaceContainer != null && r == mDecorSurfaceContainer.mOwnerTaskFragment) { + // Remove the decor surface if the owner TaskFragment is removed; + removeDecorSurface(); + } + if (hasChild()) { updateEffectiveIntent(); @@ -2638,6 +2648,9 @@ class Task extends TaskFragment { } // If applicable let the TaskOrganizer know the Task is vanishing. setTaskOrganizer(null); + if (mDecorSurfaceContainer != null) { + mDecorSurfaceContainer.release(); + } super.removeImmediately(); mRemoving = false; @@ -3644,7 +3657,8 @@ class Task extends TaskFragment { */ TaskFragmentParentInfo getTaskFragmentParentInfo() { return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), - shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity()); + shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity(), + getDecorSurface()); } @Override @@ -3666,6 +3680,62 @@ class Task extends TaskFragment { } } + @Override + void assignChildLayers(@NonNull SurfaceControl.Transaction t) { + int layer = 0; + boolean decorSurfacePlaced = false; + + // We use two passes as a way to promote children which + // need Z-boosting to the end of the list. + for (int j = 0; j < mChildren.size(); ++j) { + final WindowContainer wc = mChildren.get(j); + wc.assignChildLayers(t); + if (!wc.needsZBoost()) { + // Place the decor surface under any untrusted content. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced + && shouldPlaceDecorSurfaceBelowContainer(wc)) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + wc.assignLayer(t, layer++); + + // Place the decor surface just above the owner TaskFragment. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced + && wc == mDecorSurfaceContainer.mOwnerTaskFragment) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + } + } + + // If not placed yet, the decor surface should be on top of all non-boosted children. + if (mDecorSurfaceContainer != null && !decorSurfacePlaced) { + mDecorSurfaceContainer.assignLayer(t, layer++); + decorSurfacePlaced = true; + } + + for (int j = 0; j < mChildren.size(); ++j) { + final WindowContainer wc = mChildren.get(j); + if (wc.needsZBoost()) { + wc.assignLayer(t, layer++); + } + } + if (mOverlayHost != null) { + mOverlayHost.setLayer(t, layer++); + } + } + + boolean shouldPlaceDecorSurfaceBelowContainer(@NonNull WindowContainer wc) { + boolean isOwnActivity = + wc.asActivityRecord() != null + && wc.asActivityRecord().isUid(effectiveUid); + boolean isTrustedTaskFragment = + wc.asTaskFragment() != null + && wc.asTaskFragment().isEmbedded() + && wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode(); + return !isOwnActivity && !isTrustedTaskFragment; + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } @@ -6673,4 +6743,77 @@ class Task extends TaskFragment { mOverlayHost.dispatchInsetsChanged(s, mTmpRect); } } + + /** + * Associates the decor surface with the given TF, or create one if there + * isn't one in the Task yet. The surface will be removed with the TF, + * and become invisible if the TF is invisible. */ + void moveOrCreateDecorSurfaceFor(TaskFragment taskFragment) { + if (mDecorSurfaceContainer != null) { + mDecorSurfaceContainer.mOwnerTaskFragment = taskFragment; + } else { + mDecorSurfaceContainer = new DecorSurfaceContainer(taskFragment); + assignChildLayers(); + sendTaskFragmentParentInfoChangedIfNeeded(); + } + } + + void removeDecorSurface() { + if (mDecorSurfaceContainer == null) { + return; + } + mDecorSurfaceContainer.release(); + mDecorSurfaceContainer = null; + sendTaskFragmentParentInfoChangedIfNeeded(); + } + + @Nullable SurfaceControl getDecorSurface() { + return mDecorSurfaceContainer != null ? mDecorSurfaceContainer.mDecorSurface : null; + } + + /** + * 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. + */ + @VisibleForTesting + class DecorSurfaceContainer { + @VisibleForTesting + @NonNull final SurfaceControl mContainerSurface; + + @VisibleForTesting + @NonNull final SurfaceControl mDecorSurface; + + // The TaskFragment that requested the decor surface. If it is destroyed, the decor surface + // is also released. + @VisibleForTesting + @NonNull TaskFragment mOwnerTaskFragment; + + private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) { + mOwnerTaskFragment = initialOwner; + mContainerSurface = makeSurface().setContainerLayer() + .setParent(mSurfaceControl) + .setName(mSurfaceControl + " - decor surface container") + .setEffectLayer() + .setHidden(false) + .setCallsite("Task.DecorSurfaceContainer") + .build(); + + mDecorSurface = makeSurface() + .setParent(mContainerSurface) + .setName(mSurfaceControl + " - decor surface") + .setHidden(false) + .setCallsite("Task.DecorSurfaceContainer") + .build(); + } + + private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) { + t.setLayer(mContainerSurface, layer); + t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible()); + } + + private void release() { + mDecorSurface.release(); + mContainerSurface.release(); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 8bc461f05387..39b4480a7da0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -316,7 +316,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; - private int mTaskFragmentOrganizerUid = INVALID_UID; + @VisibleForTesting + int mTaskFragmentOrganizerUid = INVALID_UID; private @Nullable String mTaskFragmentOrganizerProcessName; /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index e7a1cf106a44..707f9fc9ea5f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -762,6 +762,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr .setTask(task) .build()); } + // Make sure the parent info changed event will be dispatched if there are no other changes. + mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } boolean isSystemOrganizer(@NonNull IBinder organizerToken) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 208df6c768bf..2af656942a2a 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -23,7 +23,9 @@ import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; @@ -1468,6 +1470,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: { + final Task task = taskFragment.getTask(); + task.moveOrCreateDecorSurfaceFor(taskFragment); + break; + } + case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: { + final Task task = taskFragment.getTask(); + task.removeDecorSurface(); + break; + } } return effects; } @@ -1507,6 +1519,19 @@ 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; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 8a90f127f4eb..06f29c262b42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -25,7 +25,9 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK; @@ -1759,6 +1761,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_createTaskFragmentDecorSurface() { + // TODO(b/293654166) remove system organizer requirement once security review is cleared. + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + + final TaskFragment tf = createTaskFragment(task); + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(task).moveOrCreateDecorSurfaceFor(tf); + } + + @Test + public void testApplyTransaction_removeTaskFragmentDecorSurface() { + // TODO(b/293654166) remove system organizer requirement once security review is cleared. + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + final TaskFragment tf = createTaskFragment(task); + + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); + mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation); + + assertApplyTransactionAllowed(mTransaction); + + verify(task).removeDecorSurface(); + } + + @Test public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); @@ -1966,7 +2002,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { /** Setups the mock Task as the parent of the given TaskFragment. */ private static void setupMockParent(TaskFragment taskFragment, Task mockParent) { doReturn(mockParent).when(taskFragment).getTask(); - doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true, true)) + doReturn(new TaskFragmentParentInfo( + new Configuration(), DEFAULT_DISPLAY, true, true, null /* decorSurface */)) .when(mockParent).getTaskFragmentParentInfo(); // Task needs to be visible 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 5e531b4cbc4f..da7612b17dc9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -63,6 +63,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.app.ActivityManager; @@ -82,6 +83,7 @@ import android.util.DisplayMetrics; import android.util.Xml; import android.view.Display; import android.view.DisplayInfo; +import android.view.SurfaceControl; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -1619,6 +1621,185 @@ public class TaskTests extends WindowTestsBase { assertFalse(task.isDragResizing()); } + @Test + public void testMoveOrCreateDecorSurface() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + + // Decor surface should not be present initially. + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + assertNull(task.getTaskFragmentParentInfo().getDecorSurface()); + + // Decor surface should be created. + clearInvocations(task); + task.moveOrCreateDecorSurfaceFor(fragment); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + verify(task).sendTaskFragmentParentInfoChangedIfNeeded(); + assertNotNull(task.getTaskFragmentParentInfo().getDecorSurface()); + assertEquals(fragment, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Decor surface should be removed. + clearInvocations(task); + task.removeDecorSurface(); + + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + verify(task).sendTaskFragmentParentInfoChangedIfNeeded(); + assertNull(task.getTaskFragmentParentInfo().getDecorSurface()); + } + + @Test + public void testMoveOrCreateDecorSurface_whenOwnerTaskFragmentRemoved() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord activity = task.getTopMostActivity(); + final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + + task.moveOrCreateDecorSurfaceFor(fragment1); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + assertEquals(fragment1, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Transfer ownership + task.moveOrCreateDecorSurfaceFor(fragment2); + + assertNotNull(task.mDecorSurfaceContainer); + assertNotNull(task.getDecorSurface()); + assertEquals(fragment2, task.mDecorSurfaceContainer.mOwnerTaskFragment); + + // Safe surface should be removed when the owner TaskFragment is removed. + task.removeChild(fragment2); + + verify(task).removeDecorSurface(); + assertNull(task.mDecorSurfaceContainer); + assertNull(task.getDecorSurface()); + } + + @Test + public void testAssignChildLayers_decorSurfacePlacement() { + 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 = mock(SurfaceControl.Transaction.class); + + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + spyOn(unembeddedActivity); + spyOn(fragment1); + spyOn(fragment2); + + // Initially, the decor surface should not be placed. + task.assignChildLayers(t); + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(fragment2).assignLayer(t, 2); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed just above the owner TaskFragment. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.moveOrCreateDecorSurfaceFor(fragment1); + task.assignChildLayers(t); + + 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, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be invisible if the owner TaskFragment is invisible. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(false).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + 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, false); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed on below activity from a different UID. + doReturn(false).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 0); + verify(unembeddedActivity).assignLayer(t, 1); + verify(fragment1).assignLayer(t, 2); + verify(fragment2).assignLayer(t, 3); + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed below untrusted embedded TaskFragment. + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.assignChildLayers(t); + + 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, never()).setLayer(eq(task.getDecorSurface()), anyInt()); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should not be placed after removal. + task.removeDecorSurface(); + task.assignChildLayers(t); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(fragment2).assignLayer(t, 2); + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } |