summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jiaming Liu <jiamingliu@google.com> 2023-11-29 07:07:39 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-11-29 07:07:39 +0000
commit7fd1bda9069d8293911e29fef0e98ed2068245e9 (patch)
tree99d246773965fa78e0f35b1a7d77a4af907e74e9
parentec820a85358c873ed2e059116fb480d58cbad573 (diff)
parent2b2b5b703351009a1c70e5344998b55e58903b79 (diff)
Merge "Add decor surface API for TaskFragmentOrganizer" into main
-rw-r--r--core/java/android/window/TaskFragmentOperation.java14
-rw-r--r--core/java/android/window/TaskFragmentParentInfo.java24
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java3
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java3
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java12
-rw-r--r--services/core/java/com/android/server/wm/Task.java145
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java181
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();
}