diff options
| author | 2024-10-14 20:15:05 +0000 | |
|---|---|---|
| committer | 2024-10-14 20:15:05 +0000 | |
| commit | 8b1e40a1cc016a1a357ffcce3c155dd22769d56b (patch) | |
| tree | 5b1aede66b9a01420a12953d99df6316206faa9d | |
| parent | a85ddc542444d84c23103d4e0ee34e661e10895b (diff) | |
| parent | faff9bd105a472ea0cf7698727aa4b8a44c4d3d9 (diff) | |
Merge "Add ability to disable launch-adjacent for a specific root task" into main
7 files changed, 175 insertions, 11 deletions
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 8e495ec1dc40..34abf3114925 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -525,6 +525,22 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Disables or enables activities to be started in adjacent tasks (see + * {@link FLAG_ACTIVITY_LAUNCH_ADJACENT}) for the specified root of any child tasks. This + * differs from {@link #setLaunchAdjacentFlagRoot(WindowContainerToken)} which controls the + * preferred launch-adjacent target and allows for selectively setting which root tasks can + * support launch-adjacent. + * @hide + */ + @NonNull + public WindowContainerTransaction setDisableLaunchAdjacent( + @NonNull WindowContainerToken container, boolean disabled) { + mHierarchyOps.add(HierarchyOp.createForSetDisableLaunchAdjacent(container.asBinder(), + disabled)); + return this; + } + + /** * Starts a task by id. The task is expected to already exist (eg. as a recent task). * @param taskId Id of task to start. * @param options bundle containing ActivityOptions for the task's top activity. @@ -1488,6 +1504,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20; public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21; public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22; + public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1556,6 +1573,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + public static HierarchyOp createForReparent( @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT) @@ -1644,6 +1663,15 @@ public final class WindowContainerTransaction implements Parcelable { .build(); } + /** Create a hierarchy op for disabling launch adjacent. */ + public static HierarchyOp createForSetDisableLaunchAdjacent(IBinder container, + boolean disabled) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT) + .setContainer(container) + .setLaunchAdjacentDisabled(disabled) + .build(); + } + /** create a hierarchy op for deleting a task **/ public static HierarchyOp createForRemoveTask(@NonNull IBinder container) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK) @@ -1695,6 +1723,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch; mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents; mExcludeInsetsTypes = copy.mExcludeInsetsTypes; + mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled; } protected HierarchyOp(Parcel in) { @@ -1719,6 +1748,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = in.readBoolean(); mIsTrimmableFromRecents = in.readBoolean(); mExcludeInsetsTypes = in.readInt(); + mLaunchAdjacentDisabled = in.readBoolean(); } public int getType() { @@ -1814,13 +1844,11 @@ public final class WindowContainerTransaction implements Parcelable { } /** Denotes whether the parents should also be included in the op. */ - @NonNull public boolean includingParents() { return mIncludingParents; } - /** Set the task to be trimmable */ - @NonNull + /** Denotes whether the task can be trimmable from recents */ public boolean isTrimmableFromRecents() { return mIsTrimmableFromRecents; } @@ -1829,6 +1857,11 @@ public final class WindowContainerTransaction implements Parcelable { return mExcludeInsetsTypes; } + /** Denotes whether launch-adjacent flag is respected from this task or its children */ + public boolean isLaunchAdjacentDisabled() { + return mLaunchAdjacentDisabled; + } + /** Gets a string representation of a hierarchy-op type. */ public static String hopToString(int type) { switch (type) { @@ -1839,6 +1872,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot"; case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot"; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + return "SetDisableLaunchAdjacent"; case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent"; case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut"; case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider"; @@ -1891,6 +1926,10 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop); break; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + sb.append("container=").append(mContainer).append(" disabled=") + .append(mLaunchAdjacentDisabled); + break; case HIERARCHY_OP_TYPE_START_SHORTCUT: sb.append("options=").append(mLaunchOptions) .append(" info=").append(mShortcutInfo); @@ -1971,6 +2010,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mReparentLeafTaskIfRelaunch); dest.writeBoolean(mIsTrimmableFromRecents); dest.writeInt(mExcludeInsetsTypes); + dest.writeBoolean(mLaunchAdjacentDisabled); } @Override @@ -2047,6 +2087,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + Builder(int type) { mType = type; } @@ -2153,6 +2195,11 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + return this; + } + HierarchyOp build() { final HierarchyOp hierarchyOp = new HierarchyOp(mType); hierarchyOp.mContainer = mContainer; @@ -2179,6 +2226,7 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch; hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents; hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes; + hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled; return hierarchyOp; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 47c5eec8cbd1..dc9ed9187548 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -169,6 +169,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private static final String TAG = StageCoordinator.class.getSimpleName(); + // The duration in ms to prevent launch-adjacent from working after split screen is first + // entered + private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000; + private final StageTaskListener mMainStage; private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final StageTaskListener mSideStage; @@ -235,6 +239,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private SplitScreen.SplitInvocationListener mSplitInvocationListener; private Executor mSplitInvocationListenerExecutor; + // Re-enables launch-adjacent handling on the split root task. This needs to be a member + // because we will be posting and removing it from the handler. + private final Runnable mReEnableLaunchAdjacentOnRoot = () -> setLaunchAdjacentDisabled(false); + /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. @@ -2662,6 +2670,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + /** + * Sets whether launch-adjacent is disabled or enabled. + */ + private void setLaunchAdjacentDisabled(boolean disabled) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLaunchAdjacentDisabled: disabled=%b", disabled); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setDisableLaunchAdjacent(mRootTaskInfo.token, disabled); + mTaskOrganizer.applyTransaction(wct); + } + /** Starts the pending transition animation. */ public boolean startPendingAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -2674,6 +2692,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); + + // Disable launch adjacent after an enter animation to prevent cases where apps are + // incorrectly trampolining and incorrectly triggering a double launch-adjacent task + // launch (ie. main -> split -> main). See b/344216031 + setLaunchAdjacentDisabled(true); + mMainHandler.removeCallbacks(mReEnableLaunchAdjacentOnRoot); + mMainHandler.postDelayed(mReEnableLaunchAdjacentOnRoot, + DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS); } else if (mSplitTransitions.isPendingDismiss(transition)) { final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; shouldAnimate = startPendingDismissAnimation( diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 87fa62ac0e3b..5b5bb88cac98 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -86,6 +86,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENS import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; import android.annotation.IntDef; @@ -2786,10 +2787,22 @@ class ActivityStarter { } } - if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 - && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) { - // ignore the flag if there is no the sourceRecord or without new_task flag - mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { + final boolean hasNewTaskFlag = (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + if (!hasNewTaskFlag || mSourceRecord == null) { + // ignore the flag if there is no the sourceRecord or without new_task flag + Slog.w(TAG_WM, !hasNewTaskFlag + ? "Launch adjacent ignored due to missing NEW_TASK" + : "Launch adjacent ignored due to missing source activity"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + // Ensure that the source task or its parents has not disabled launch-adjacent + if (mSourceRecord != null && mSourceRecord.getTask() != null && + mSourceRecord.getTask().isLaunchAdjacentDisabled()) { + Slog.w(TAG_WM, "Launch adjacent blocked by source task or ancestor"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 00704b3525c5..a4e4deb9ed7d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -503,6 +503,11 @@ class Task extends TaskFragment { boolean mIsTrimmableFromRecents; /** + * Sets whether the launch-adjacent flag is respected or not for this task or its child tasks. + */ + private boolean mLaunchAdjacentDisabled; + + /** * Bounds offset should be applied when calculating compatible configuration for apps targeting * SDK level 34 or before. */ @@ -3802,6 +3807,9 @@ class Task extends TaskFragment { pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); + if (mLaunchAdjacentDisabled) { + pw.println(prefix + "mLaunchAdjacentDisabled=true"); + } } @Override @@ -6274,6 +6282,28 @@ class Task extends TaskFragment { } /** + * Sets this task and its children to disable respecting launch-adjacent. + */ + void setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + } + + /** + * Returns whether this task or any of its ancestors have disabled respecting the + * launch-adjacent flag. + */ + boolean isLaunchAdjacentDisabled() { + Task t = this; + while (t != null) { + if (t.mLaunchAdjacentDisabled) { + return true; + } + t = t.getParent().asTask(); + } + return false; + } + + /** * Return true if the activityInfo has the same requiredDisplayCategory as this task. */ boolean isSameRequiredDisplayCategory(@NonNull ActivityInfo info) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 5dd3bbce4e96..2c71c1a1f4f3 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1074,6 +1074,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); // We only allow this for created by organizer tasks. if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) { + Slog.i(TAG_WM, "Using launch root task from activity options: taskId=" + + launchRootTask.mTaskId); return launchRootTask; } } @@ -1081,19 +1083,25 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { + final Task launchAdjacentRootAdjacentTask = + mLaunchAdjacentFlagRootTask.getAdjacentTask(); if (sourceTask != null && (sourceTask == candidateTask || sourceTask.topRunningActivity() == null)) { // Do nothing when task that is getting opened is same as the source or when // the source is no-longer valid. Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone."); } else if (sourceTask != null - && mLaunchAdjacentFlagRootTask.getAdjacentTask() != null + && launchAdjacentRootAdjacentTask != null && (sourceTask == mLaunchAdjacentFlagRootTask || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) { - // If the adjacent launch is coming from the same root, launch to - // adjacent root instead. - return mLaunchAdjacentFlagRootTask.getAdjacentTask(); + // If the adjacent launch is coming from the same root that was specified as the + // launch-adjacent task, so instead we launch to its adjacent root instead. + Slog.i(TAG_WM, "Using adjacent-to specified launch-adjacent task: taskId=" + + launchAdjacentRootAdjacentTask.mTaskId); + return launchAdjacentRootAdjacentTask; } else { + Slog.i(TAG_WM, "Using specified launch-adjacent task: taskId=" + + mLaunchAdjacentFlagRootTask.mTaskId); return mLaunchAdjacentFlagRootTask; } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 82c7a9350eca..166d74b132bd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -69,6 +69,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; @@ -1450,6 +1451,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.setTrimmableFromRecents(hop.isTrimmableFromRecents()); break; } + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: { + final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); + final Task task = container != null ? container.asTask() : null; + if (task == null || !task.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + + container); + break; + } + task.setLaunchAdjacentDisabled(hop.isLaunchAdjacentDisabled()); + break; + } case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: { if (mService.mBackNavigationController.restoreBackNavigation()) { effects |= TRANSACT_EFFECTS_LIFECYCLE; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index d0080d29f82b..d5f86b6feac8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1261,6 +1261,33 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testLaunchAdjacentDisabled() { + final ActivityStarter starter = + prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */); + final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityOptions options = ActivityOptions.makeBasic(); + final ActivityRecord[] outActivity = new ActivityRecord[1]; + + // Activity must not land on split-screen task if currently not in split-screen mode. + starter.setActivityOptions(options.toBundle()) + .setReason("testLaunchAdjacentDisabled") + .setOutActivity(outActivity).execute(); + assertThat(outActivity[0].inMultiWindowMode()).isFalse(); + + // Move activity to split-screen-primary task and make sure it has the focus. + TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent()); + top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM); + top.getRootTask().moveToFront("testLaunchAdjacentDisabled"); + top.getRootTask().setLaunchAdjacentDisabled(true); + + // Ensure activity does not launch into split-screen-secondary when launch adjacent is + // disabled + startActivityInner(starter, outActivity[0], top, options, null /* inTask */, + null /* taskFragment*/); + assertThat(outActivity[0].isDescendantOf(splitOrg.mSecondary)).isFalse(); + } + + @Test public void testTransientLaunchWithKeyguard() { final ActivityStarter starter = prepareStarter(0 /* flags */); final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build(); |