diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 43fa0be6c1b74ece429d836af84af06548ca8b10..4e0f9a51c0a038bb84cc80fe327f8d3350f2002d 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -88,6 +88,26 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; + /** + * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the + * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12; + + /** + * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the + * TaskFragment to the top of the Task above all the other Activities and TaskFragments. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -101,7 +121,9 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_ANIMATION_PARAMS, OP_TYPE_SET_RELATIVE_BOUNDS, OP_TYPE_REORDER_TO_FRONT, - OP_TYPE_SET_ISOLATED_NAVIGATION + OP_TYPE_SET_ISOLATED_NAVIGATION, + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, + OP_TYPE_REORDER_TO_TOP_OF_TASK, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 016b0ff558263bb22f8922222cb636254fbc292d..1d5d2794e8510db2a3222fdcb2f36978216183cc 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -489,10 +489,6 @@ class Task extends TaskFragment { private boolean mForceShowForAllUsers; - /** When set, will force the task to report as invisible. */ - static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; - static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; - private int mForceHiddenFlags = 0; private boolean mForceTranslucent = false; // The display category name for this task. @@ -4495,20 +4491,13 @@ class Task extends TaskFragment { * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. * @return Whether the force hidden state changed */ - boolean setForceHidden(int flags, boolean set) { - int newFlags = mForceHiddenFlags; - if (set) { - newFlags |= flags; - } else { - newFlags &= ~flags; - } - if (mForceHiddenFlags == newFlags) { - return false; - } - + @Override + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { final boolean wasHidden = isForceHidden(); final boolean wasVisible = isVisible(); - mForceHiddenFlags = newFlags; + if (!super.setForceHidden(flags, set)) { + return false; + } final boolean nowHidden = isForceHidden(); if (wasHidden != nowHidden) { final String reason = "setForceHidden"; @@ -4539,11 +4528,6 @@ class Task extends TaskFragment { return super.isAlwaysOnTop(); } - @Override - protected boolean isForceHidden() { - return mForceHiddenFlags != 0; - } - boolean isForceHiddenForPinnedTask() { return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 7c5bc6e3bb284122567b967288104526e0e3ab44..906b3b55e015361f9b69e58896dacc24fab68de4 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -362,6 +362,19 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mIsolatedNav; + /** When set, will force the task to report as invisible. */ + static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; + static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2; + + @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = { + FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, + FLAG_FORCE_HIDDEN_FOR_TASK_ORG, + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, + }, flag = true) + @interface FlagForceHidden {} + protected int mForceHiddenFlags = 0; + final Point mLastSurfaceSize = new Point(); private final Rect mTmpBounds = new Rect(); @@ -845,7 +858,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { * Returns whether this TaskFragment is currently forced to be hidden for any reason. */ protected boolean isForceHidden() { - return false; + return mForceHiddenFlags != 0; + } + + /** + * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. + * @return Whether the force hidden state changed + */ + boolean setForceHidden(@FlagForceHidden int flags, boolean set) { + int newFlags = mForceHiddenFlags; + if (set) { + newFlags |= flags; + } else { + newFlags &= ~flags; + } + if (mForceHiddenFlags == newFlags) { + return false; + } + mForceHiddenFlags = newFlags; + return true; } protected boolean isForceTranslucent() { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 04164c20a3725ad638b3122d10af92ab969b19b1..ff766beee33724a479c5b0199863f9873e12634c 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,7 +51,6 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; -import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; @@ -745,9 +744,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } - boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { + boolean isSystemOrganizer(@NonNull IBinder organizerToken) { final TaskFragmentOrganizerState state = - mTaskFragmentOrganizerState.get(token.asBinder()); + mTaskFragmentOrganizerState.get(organizerToken); return state != null && state.mIsSystemOrganizer; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dd9a88f72bdec70c9900a1ee5414b8184b86bd49..5ed6caffe1fba2ae70c938d823d557f72c9e5858 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -24,7 +24,9 @@ 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_DELETE_TASK_FRAGMENT; +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; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -34,6 +36,8 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATI import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; +import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN; import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION; @@ -61,6 +65,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -821,6 +826,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return TRANSACT_EFFECTS_NONE; } + int effects = TRANSACT_EFFECTS_NONE; // When the TaskFragment is resized, we may want to create a change transition for it, for // which we want to defer the surface update until we determine whether or not to start // change transition. @@ -843,7 +849,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub c.getConfiguration().windowConfiguration.setBounds(absBounds); taskFragment.setRelativeEmbeddedBounds(relBounds); } - final int effects = applyChanges(taskFragment, c); + if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { + if (taskFragment.setForceHidden( + FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + } + effects |= applyChanges(taskFragment, c); + if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) { taskFragment.initializeChangeTransition(mTmpBounds0); } @@ -1393,6 +1406,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub taskFragment.setIsolatedNav(isolatedNav); break; } + case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(0, taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } + case OP_TYPE_REORDER_TO_TOP_OF_TASK: { + final Task task = taskFragment.getTask(); + if (task != null) { + task.mChildren.remove(taskFragment); + task.mChildren.add(taskFragment); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + } + break; + } } return effects; } @@ -1420,6 +1451,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK + || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or " + + "OP_TYPE_REORDER_TO_TOP_OF_TASK." + ); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, + opType, exception); + return false; + } + final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken(); return secondaryFragmentToken == null || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, @@ -1920,6 +1963,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub * For config change on {@link TaskFragment}, we only support the following operations: * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)}, * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}. + * + * For a system organizer, we additionally support + * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and + * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See + * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)} */ private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func, @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change, @@ -1938,31 +1986,49 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new SecurityException(msg); } - final int changeMask = change.getChangeMask(); - final int configSetMask = change.getConfigSetMask(); - final int windowSetMask = change.getWindowSetMask(); - if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0 - && change.getWindowingMode() >= 0) { - // The change contains only setWindowingMode, which is allowed. - return; + final int originalChangeMask = change.getChangeMask(); + final int originalConfigSetMask = change.getConfigSetMask(); + final int originalWindowSetMask = change.getWindowSetMask(); + + int changeMaskToBeChecked = originalChangeMask; + int configSetMaskToBeChecked = originalConfigSetMask; + int windowSetMaskToBeChecked = originalWindowSetMask; + + if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + // System organizer is allowed to update the hidden and focusable state. + // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. + changeMaskToBeChecked &= ~CHANGE_HIDDEN; + changeMaskToBeChecked &= ~CHANGE_FOCUSABLE; } - if (changeMask != CHANGE_RELATIVE_BOUNDS - || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION - || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) { - // None of the change should be requested from a TaskFragment organizer except - // setRelativeBounds and setWindowingMode. - // For setRelativeBounds, we don't need to check whether it is outside of the Task + + // setRelativeBounds is allowed. + if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0 + && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 + && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { + // For setRelativeBounds, we don't need to check whether it is outside the Task // bounds, because it is possible that the Task is also resizing, for which we don't // want to throw an exception. The bounds will be adjusted in // TaskFragment#translateRelativeBoundsToAbsoluteBounds. - String msg = "Permission Denial: " + func + " from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " trying to apply changes of changeMask=" + changeMask - + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask - + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; - Slog.w(TAG, msg); - throw new SecurityException(msg); + changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS; + configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; + windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS; } + + if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0 + && windowSetMaskToBeChecked == 0) { + // All the changes have been checked. + // Note that setWindowingMode is always allowed, so we don't need to check the mask. + return; + } + + final String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " trying to apply changes of changeMask=" + originalChangeMask + + " configSetMask=" + originalConfigSetMask + + " windowSetMask=" + originalWindowSetMask + + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; + Slog.w(TAG, msg); + throw new SecurityException(msg); } private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -2019,7 +2085,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); taskFragment.setTaskFragmentOrganizer(organizerToken, ownerActivity.getUid(), ownerActivity.info.processName, - mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); + mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())); final int position; if (creationParams.getPairedPrimaryFragmentToken() != null) { // When there is a paired primary TaskFragment, we want to place the new TaskFragment 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 2bf13857e537087e7e7280b07c671b3402762fd0..6235b3b67145070843e596953b08d6953824214a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,7 +26,9 @@ 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_DELETE_TASK_FRAGMENT; +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; import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; @@ -1655,6 +1657,127 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(frontMostTaskFragment, tf0); } + @Test + public void testApplyTransaction_reorderToBottomOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to bottom + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf0, task.getChildAt(2).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord()); + assertEquals(tf1, task.getChildAt(0).asTaskFragment()); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Reorder TaskFragment to top + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_REORDER_TO_TOP_OF_TASK).build(); + mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation); + assertApplyTransactionAllowed(mTransaction); + + // Ensure correct order of the children after the operation + assertEquals(tf0, task.getChildAt(3).asTaskFragment()); + assertEquals(topActivity, task.getChildAt(2).asActivityRecord()); + assertEquals(tf1, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + + @Test + public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); + } + + @Test + public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() { + testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + OP_TYPE_REORDER_TO_TOP_OF_TASK); + } + + private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( + @TaskFragmentOperation.OperationType int opType) { + final Task task = createTask(mDisplayContent); + // Create a non-embedded Activity at the bottom. + final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + final TaskFragment tf0 = createTaskFragment(task); + final TaskFragment tf1 = createTaskFragment(task); + // Create a non-embedded Activity at the top. + final ActivityRecord topActivity = new ActivityBuilder(mAtm) + .setTask(task) + .build(); + + // Ensure correct order of the children before the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + + // Apply reorder transaction, which is expected to fail for non-system organizer. + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + opType).build(); + mTransaction + .addTaskFragmentOperation(tf0.getFragmentToken(), operation) + .setErrorCallbackToken(mErrorToken); + assertApplyTransactionAllowed(mTransaction); + // The pending event will be dispatched on the handler (from requestTraversal). + waitHandlerIdle(mWm.mAnimationHandler); + + assertTaskFragmentErrorTransaction(opType, SecurityException.class); + + // Ensure no change to the order of the children after the operation + assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); + assertEquals(tf1, task.getChildAt(2).asTaskFragment()); + assertEquals(tf0, task.getChildAt(1).asTaskFragment()); + assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + } + /** * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the @@ -1782,6 +1905,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(activityToken, change.getActivityToken()); } + /** Setups an embedded TaskFragment. */ + private TaskFragment createTaskFragment(Task task) { + final IBinder token = new Binder(); + TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(token) + .setOrganizer(mOrganizer) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment); + return taskFragment; + } + /** Setups an embedded TaskFragment in a PIP Task. */ private void setupTaskFragmentInPip() { mTaskFragment = new TaskFragmentBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7168670f96522a184e88822ff115a7d2902b74cb..0b77fd82874576cc6a19dcf1be969607398ecffa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -40,6 +40,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.testing.Assert.assertThrows; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -58,6 +59,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; @@ -77,11 +79,13 @@ import android.util.Rational; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowInsets; +import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; import android.window.TaskAppearedInfo; +import android.window.TaskFragmentOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -578,6 +582,87 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(rootTask.shouldBeVisible(null)); } + @Test + public void testTaskFragmentHiddenAndFocusableChanges() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, true /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + // Should be visible and focusable initially. + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment hidden and not focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be not visible and not focusable after the transaction. + assertFalse(taskFragment.shouldBeVisible(null)); + assertFalse(taskFragment.isFocusable()); + assertFalse(taskFragment.isTopActivityFocusable()); + + // Apply transaction to the TaskFragment not hidden and focusable. + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */); + + // Should be visible and focusable after the transaction. + assertTrue(taskFragment.shouldBeVisible(null)); + assertTrue(taskFragment.isFocusable()); + assertTrue(taskFragment.isTopActivityFocusable()); + } + + @Test + public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { + removeGlobalMinSizeRestriction(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + + final WindowContainerTransaction t = new WindowContainerTransaction(); + final TaskFragmentOrganizer organizer = + createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); + + final IBinder token = new Binder(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(rootTask) + .setFragmentToken(token) + .setOrganizer(organizer) + .createActivityCount(1) + .build(); + + assertTrue(rootTask.shouldBeVisible(null)); + assertTrue(taskFragment.shouldBeVisible(null)); + + t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); + t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + + // Non-system organizers are not allow to update the hidden and focusable states. + assertThrows(SecurityException.class, () -> + mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( + t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, + false /* shouldApplyIndependently */) + ); + } + @Test public void testContainerTranslucentChanges() { removeGlobalMinSizeRestriction(); @@ -1600,4 +1685,20 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(taskIds.contains(expectedTasks[i].mTaskId)); } } + + @NonNull + private TaskFragmentOrganizer createTaskFragmentOrganizer( + @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer organizerInterface = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController + .registerOrganizerInternal( + ITaskFragmentOrganizer.Stub.asInterface( + organizer.getOrganizerToken().asBinder()), + isSystemOrganizer); + t.setTaskFragmentOrganizer(organizerInterface); + + return organizer; + } }