summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jiaming Liu <jiamingliu@google.com> 2024-01-09 00:15:38 +0000
committer Jiaming Liu <jiamingliu@google.com> 2024-01-16 09:06:54 +0000
commitf646ee116ecd04dca79b252573bb4d7a60f4b1e8 (patch)
tree1e988bb78437a680dd6d15fd876d5105e5519429
parent55e27576181f3a252c8e54a5a777ad52f2b64f99 (diff)
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
-rw-r--r--core/java/android/window/TaskFragmentOperation.java47
-rw-r--r--services/core/java/com/android/server/wm/Task.java47
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java29
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}.
*/
@@ -466,13 +495,23 @@ public final class TaskFragmentOperation implements Parcelable {
}
/**
+ * 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}.
*/
@NonNull
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<TaskFragment> 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<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<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();
}