From f646ee116ecd04dca79b252573bb4d7a60f4b1e8 Mon Sep 17 00:00:00 2001 From: Jiaming Liu Date: Tue, 9 Jan 2024 00:15:38 +0000 Subject: Allow TaskFragment to move to bottom when clear top Add TaskFragment op OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH If set to true, when a launching activity clears top, the TaskFragment is moved bottom of the Task instead of being finished. Bug: 317896352 Test: atest TaskTests Change-Id: I4c7e11f82fd26adda20233f6dfd954beb896c72b --- .../java/android/window/TaskFragmentOperation.java | 47 ++++++++++++++++++++-- services/core/java/com/android/server/wm/Task.java | 47 ++++++++++++++++++++++ .../java/com/android/server/wm/TaskFragment.java | 14 +++++++ .../server/wm/WindowOrganizerController.java | 17 ++++++++ .../src/com/android/server/wm/TaskTests.java | 29 +++++++++++++ 5 files changed, 150 insertions(+), 4 deletions(-) diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index acc6a749e9b7..7b8cdff8e20b 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -125,6 +125,16 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_DIM_ON_TASK = 16; + /** + * Sets this TaskFragment to move to bottom of the Task if any of the activities below it is + * launched in a mode requiring clear top. + * + * This is only allowed for system organizers. See + * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( + * ITaskFragmentOrganizer, boolean)} + */ + public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -144,6 +154,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE, OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE, OP_TYPE_SET_DIM_ON_TASK, + OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} @@ -173,12 +184,14 @@ public final class TaskFragmentOperation implements Parcelable { private final boolean mDimOnTask; + private final boolean mMoveToBottomIfClearWhenLaunch; + private TaskFragmentOperation(@OperationType int opType, @Nullable TaskFragmentCreationParams taskFragmentCreationParams, @Nullable IBinder activityToken, @Nullable Intent activityIntent, @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken, @Nullable TaskFragmentAnimationParams animationParams, - boolean isolatedNav, boolean dimOnTask) { + boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) { mOpType = opType; mTaskFragmentCreationParams = taskFragmentCreationParams; mActivityToken = activityToken; @@ -188,6 +201,7 @@ public final class TaskFragmentOperation implements Parcelable { mAnimationParams = animationParams; mIsolatedNav = isolatedNav; mDimOnTask = dimOnTask; + mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; } private TaskFragmentOperation(Parcel in) { @@ -200,6 +214,7 @@ public final class TaskFragmentOperation implements Parcelable { mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR); mIsolatedNav = in.readBoolean(); mDimOnTask = in.readBoolean(); + mMoveToBottomIfClearWhenLaunch = in.readBoolean(); } @Override @@ -213,6 +228,7 @@ public final class TaskFragmentOperation implements Parcelable { dest.writeTypedObject(mAnimationParams, flags); dest.writeBoolean(mIsolatedNav); dest.writeBoolean(mDimOnTask); + dest.writeBoolean(mMoveToBottomIfClearWhenLaunch); } @NonNull @@ -300,6 +316,14 @@ public final class TaskFragmentOperation implements Parcelable { return mDimOnTask; } + /** + * Returns whether the TaskFragment should move to bottom of task when any activity below it + * is launched in clear top mode. + */ + public boolean isMoveToBottomIfClearWhenLaunch() { + return mMoveToBottomIfClearWhenLaunch; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder(); @@ -324,6 +348,7 @@ public final class TaskFragmentOperation implements Parcelable { } sb.append(", isolatedNav=").append(mIsolatedNav); sb.append(", dimOnTask=").append(mDimOnTask); + sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch); sb.append('}'); return sb.toString(); @@ -332,7 +357,8 @@ public final class TaskFragmentOperation implements Parcelable { @Override public int hashCode() { return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent, - mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask); + mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask, + mMoveToBottomIfClearWhenLaunch); } @Override @@ -349,7 +375,8 @@ public final class TaskFragmentOperation implements Parcelable { && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken) && Objects.equals(mAnimationParams, other.mAnimationParams) && mIsolatedNav == other.mIsolatedNav - && mDimOnTask == other.mDimOnTask; + && mDimOnTask == other.mDimOnTask + && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch; } @Override @@ -385,6 +412,8 @@ public final class TaskFragmentOperation implements Parcelable { private boolean mDimOnTask; + private boolean mMoveToBottomIfClearWhenLaunch; + /** * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}. */ @@ -465,6 +494,16 @@ public final class TaskFragmentOperation implements Parcelable { return this; } + /** + * Sets whether the TaskFragment should move to bottom of task when any activity below it + * is launched in clear top mode. + */ + @NonNull + public Builder setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) { + mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; + return this; + } + /** * Constructs the {@link TaskFragmentOperation}. */ @@ -472,7 +511,7 @@ public final class TaskFragmentOperation implements Parcelable { public TaskFragmentOperation build() { return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams, - mIsolatedNav, mDimOnTask); + mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch); } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a7a6bf2ed2a1..314d720692f7 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -208,6 +208,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; @@ -1702,6 +1703,8 @@ class Task extends TaskFragment { final ActivityRecord r = findActivityInHistory(newR.mActivityComponent, newR.mUserId); if (r == null) return null; + moveTaskFragmentsToBottomIfNeeded(r, finishCount); + final PooledPredicate f = PooledLambda.obtainPredicate( (ActivityRecord ar, ActivityRecord boundaryActivity) -> finishActivityAbove(ar, boundaryActivity, finishCount), @@ -1722,6 +1725,50 @@ class Task extends TaskFragment { return r; } + /** + * Moves {@link TaskFragment}s to the bottom if the flag + * {@link TaskFragment#isMoveToBottomIfClearWhenLaunch} is {@code true}. + */ + @VisibleForTesting + void moveTaskFragmentsToBottomIfNeeded(@NonNull ActivityRecord r, @NonNull int[] finishCount) { + final int activityIndex = mChildren.indexOf(r); + if (activityIndex < 0) { + return; + } + + List taskFragmentsToMove = null; + + // Find the TaskFragments that need to be moved + for (int i = mChildren.size() - 1; i > activityIndex; i--) { + final TaskFragment taskFragment = mChildren.get(i).asTaskFragment(); + if (taskFragment != null && taskFragment.isMoveToBottomIfClearWhenLaunch()) { + if (taskFragmentsToMove == null) { + taskFragmentsToMove = new ArrayList<>(); + } + taskFragmentsToMove.add(taskFragment); + } + } + if (taskFragmentsToMove == null) { + return; + } + + // Move the TaskFragments to the bottom of the Task. Their relative orders are preserved. + final int size = taskFragmentsToMove.size(); + for (int i = 0; i < size; i++) { + final TaskFragment taskFragment = taskFragmentsToMove.get(i); + + // The visibility of the TaskFragment may change. Collect it in the transition so that + // transition animation can be properly played. + mTransitionController.collect(taskFragment); + + positionChildAt(POSITION_BOTTOM, taskFragment, false /* includeParents */); + } + + // Treat it as if the TaskFragments are finished so that a transition animation can be + // played to send the TaskFragments back and bring the activity to front. + finishCount[0] += size; + } + private static boolean finishActivityAbove(ActivityRecord r, ActivityRecord boundaryActivity, @NonNull int[] finishCount) { // Stop operation once we reach the boundary activity. diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index f56759f9481c..7d418eae6548 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -363,6 +363,12 @@ class TaskFragment extends WindowContainer { */ private boolean mIsolatedNav; + /** + * Whether the TaskFragment should move to bottom of task when any activity below it is + * launched in clear top mode. + */ + private boolean mMoveToBottomIfClearWhenLaunch; + /** 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; @@ -3045,6 +3051,14 @@ class TaskFragment extends WindowContainer { mEmbeddedDimArea = embeddedDimArea; } + void setMoveToBottomIfClearWhenLaunch(boolean moveToBottomIfClearWhenLaunch) { + mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; + } + + boolean isMoveToBottomIfClearWhenLaunch() { + return mMoveToBottomIfClearWhenLaunch; + } + @VisibleForTesting boolean isDimmingOnParentTask() { return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 59d0210251d1..cb291f0868d8 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -36,6 +36,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH; 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; @@ -1502,6 +1503,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub : EMBEDDED_DIM_AREA_TASK_FRAGMENT); break; } + case OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH: { + taskFragment.setMoveToBottomIfClearWhenLaunch( + operation.isMoveToBottomIfClearWhenLaunch()); + break; + } } return effects; } @@ -1554,6 +1560,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH) + && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { + final Throwable exception = new SecurityException( + "Only a system organizer can perform " + + "OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH." + ); + 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/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index b36080023ef2..961fdfb14bf3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1822,6 +1822,35 @@ public class TaskTests extends WindowTestsBase { verify(fragment2).assignLayer(t, 2); } + @Test + public void testMoveTaskFragmentsToBottomIfNeeded() { + 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 TaskFragment fragment3 = createTaskFragmentWithEmbeddedActivity(task, organizer); + doReturn(true).when(fragment1).isMoveToBottomIfClearWhenLaunch(); + doReturn(false).when(fragment2).isMoveToBottomIfClearWhenLaunch(); + doReturn(true).when(fragment3).isMoveToBottomIfClearWhenLaunch(); + + assertEquals(unembeddedActivity, task.mChildren.get(0)); + assertEquals(fragment1, task.mChildren.get(1)); + assertEquals(fragment2, task.mChildren.get(2)); + assertEquals(fragment3, task.mChildren.get(3)); + + final int[] finishCount = {0}; + task.moveTaskFragmentsToBottomIfNeeded(unembeddedActivity, finishCount); + + // fragment1 and fragment3 should be moved to the bottom of the task + assertEquals(fragment1, task.mChildren.get(0)); + assertEquals(fragment3, task.mChildren.get(1)); + assertEquals(unembeddedActivity, task.mChildren.get(2)); + assertEquals(fragment2, task.mChildren.get(3)); + assertEquals(2, finishCount[0]); + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } -- cgit v1.2.3-59-g8ed1b