Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit faff9bd1 authored by Winson Chung's avatar Winson Chung
Browse files

Add ability to disable launch-adjacent for a specific root task

- There are some scenarios in which we need to disable launch adjacent
  handling due apps incorrectly trampolining from one launch-adjacent
  task immediately to another launch-adjcent task (thereby clobbering
  the original task).

  eg. User launches A fullscreen -> A
      A launches trampoline B with LAUNCH_ADJACENT -> A | B
      B launches final target C but also with LAUNCH_ADJACENT -> C | B
      but then trampoline B finishes itself -> C

  We attempt to work around this by disabling launch-adjacent for the
  split root task and its children.
- This can also be useful for desktop mode in which we want to disable
  launch adjacent from accidentally launching into the adjacent root
  task specified by SplitScreen (we do this currently by temporarily
  disabling the launch adjacent root), but when we have multi-display
  we may just want to disable launch adjacent specifically for all
  tasks while they are in desktop mode.

Bug: 344216031
Test: atest ActivityStarterTests
Flag: EXEMPT bugfix
Change-Id: I955914c3ee8239b0fd1fec31176b111f5365e7b0
parent 832e8eda
Loading
Loading
Loading
Loading
+51 −3
Original line number Diff line number Diff line
@@ -524,6 +524,22 @@ public final class WindowContainerTransaction implements Parcelable {
        return this;
    }

    /**
     * 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.
@@ -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;
            }
+26 −0
Original line number Diff line number Diff line
@@ -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.
@@ -2677,6 +2685,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,
@@ -2689,6 +2707,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(
+17 −4
Original line number Diff line number Diff line
@@ -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,11 +2787,23 @@ class ActivityStarter {
            }
        }

        if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
                && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
        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;
            }

        }
    }

    /**
+30 −0
Original line number Diff line number Diff line
@@ -502,6 +502,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
@@ -6267,6 +6275,28 @@ class Task extends TaskFragment {
        mIsTrimmableFromRecents = isTrimmable;
    }

    /**
     * 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.
     */
+12 −4
Original line number Diff line number Diff line
@@ -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;
            }
        }
Loading