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

Commit 80ce1c97 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Clean up split after trampolines" into main

parents 43bb4255 85ad95e6
Loading
Loading
Loading
Loading
+96 −23
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPos
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
@@ -1619,21 +1620,40 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
     * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we
     * will do a no-op.
     */
    void dismissSplitKeepingLastActiveStage(@ExitReason int reason) {
        if (!isSplitActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
    void dismissSplitKeepingLastActiveStage(@ExitReason int exitReason) {
        if (mLastActiveStage == STAGE_TYPE_UNDEFINED) {
            // no-op
            return;
        }

        // Need manually clear here due to this transition might be aborted due to keyguard
        // on top and lead to no visible change.
        clearSplitPairedInRecents(reason);
        dismissSplit(mLastActiveStage, exitReason);
        mBreakOnNextWake = false;
    }

    /**
     * Dismisses split in the background.
     */
    public void dismissSplitInBackground(@ExitReason int exitReason) {
        dismissSplit(STAGE_TYPE_UNDEFINED, exitReason);
    }

    /**
     * Starts a new transition to dismiss split.
     */
    private void dismissSplit(@StageType int stageToTop, @ExitReason int exitReason) {
        if (!isSplitActive()) {
            return;
        }
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "dismissSplit: stageToTop=%s reason=%s",
                stageTypeToString(stageToTop), exitReasonToString(exitReason));

        // Need manually clear here due to this transition might be aborted due to no visible change
        clearSplitPairedInRecents(exitReason);
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        prepareExitSplitScreen(mLastActiveStage, wct, reason);
        mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
        prepareExitSplitScreen(stageToTop, wct, exitReason);
        mSplitTransitions.startDismissTransition(wct, this, stageToTop, exitReason);
        setSplitsVisible(false);
        mBreakOnNextWake = false;
        logExit(reason);
        logExit(exitReason);
    }

    void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
@@ -3175,20 +3195,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    // the remote handler.
                    return null;
                }
                boolean anyStageContainsSingleFullscreenTask;
                if (enableFlexibleSplit()) {
                    anyStageContainsSingleFullscreenTask =
                            mStageOrderOperator.getActiveStages().stream()
                                    .anyMatch(stageListener ->
                                            stageListener.containsTask(triggerTask.taskId)
                                                    && stageListener.getChildCount() == 1);
                } else {
                    anyStageContainsSingleFullscreenTask =
                            (mMainStage.containsTask(triggerTask.taskId)
                                    && mMainStage.getChildCount() == 1)
                                    || (mSideStage.containsTask(triggerTask.taskId)
                                    && mSideStage.getChildCount() == 1);
                }
                boolean anyStageContainsSingleFullscreenTask = isLastTaskInAnyStage(
                        triggerTask.taskId);
                if (anyStageContainsSingleFullscreenTask) {
                    // A splitting task is opening to fullscreen causes one side of the split empty,
                    // so appends operations to exit split.
@@ -3249,6 +3257,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        return out;
    }

    /**
     * @return The provided taskId is the last child of any stage.
     */
    private boolean isLastTaskInAnyStage(int taskId) {
        if (enableFlexibleSplit()) {
            return mStageOrderOperator.getActiveStages().stream()
                    .anyMatch(stageListener ->
                            stageListener.containsTask(taskId)
                                    && stageListener.getChildCount() == 1);
        } else {
            return (mMainStage.containsTask(taskId)
                    && mMainStage.getChildCount() == 1)
                    || (mSideStage.containsTask(taskId)
                    && mSideStage.getChildCount() == 1);
        }
    }

    /** @return whether the transition-request implies entering pip from split. */
    public boolean requestImpliesSplitToPip(TransitionRequestInfo request) {
        final TaskInfo triggerTask = request.getTriggerTask();
@@ -3398,6 +3423,53 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        prepareExitSplitScreen(topStage, outWCT, EXIT_REASON_UNKNOWN);
    }

    /**
     * When we launch an app, there are times when the app trampolines itself into another activity
     * and ends up breaking a split pair (because that second activity already existed as part of a
     * pair). This method is used to detect whether that happened, so we can clean up the split
     * state.
     * @return whether the transition implies a split task being launched in fullscreen resulting
     *         in splitscreen being broken.
     */
    public boolean transitionImpliesSplitToFullscreen(TransitionInfo info) {
        if (!isSplitActive() || isSplitScreenVisible()) {
            // Not in split, or if visible then we would have already handled it.
            return false;
        }

        for (int i = 0; i < info.getChanges().size(); i++) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            final TaskInfo task = change.getTaskInfo();
            if (task == null || task.getActivityType() == ACTIVITY_TYPE_HOME
                    || task.getActivityType() == ACTIVITY_TYPE_RECENTS) {
                continue;
            }

            final boolean isOpening = isOpeningMode(change.getMode());
            final boolean inFullscreen = task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
            if (isOpening && inFullscreen) {
                // Note: currently we still rely on non-transition signals to update the stage
                // children so we may end up with an empty stage, but once we migrate away from that
                // we'll actually need to check that the task being launched into fullscreen is
                // actually the last child of a stage
                final boolean hasEmptyStage;
                if (enableFlexibleSplit()) {
                    hasEmptyStage = mStageOrderOperator.getActiveStages().stream()
                            .anyMatch(stage -> stage.getChildCount() == 0);
                } else {
                    hasEmptyStage = mMainStage.getChildCount() == 0
                            || mSideStage.getChildCount() == 0;
                }
                if (isLastTaskInAnyStage(task.taskId) || hasEmptyStage) {
                    ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                            "Fullscreen task launch transition will break split");
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void mergeAnimation(IBinder transition, TransitionInfo info,
            @NonNull SurfaceControl.Transaction startT,
@@ -4408,6 +4480,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
        pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
        pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
        pw.println(innerPrefix + "mLastActiveStage=" + mLastActiveStage);
        pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
        pw.println(innerPrefix + "isLeftRightSplit="
                + (mSplitLayout != null ? isLeftRightSplit() : "null"));
+17 −4
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;

import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST;
import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
@@ -113,7 +114,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
                            mKeyguardHandler, mPipHandler);
            case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE ->
                    animateOpenIntentWithRemoteAndPipOrDesktop(transition, info, startTransaction,
                            finishTransaction, finishCallback);
                            finishTransaction, finishCallback, mSplitHandler);
            case TYPE_UNFOLD ->
                    animateUnfold(transition, info, startTransaction, finishTransaction,
                            finishCallback);
@@ -197,11 +198,12 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
            @NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
            @NonNull Transitions.TransitionFinishCallback finishCallback,
            @NonNull StageCoordinator splitHandler) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent"
                + " with a remote transition and PIP or Desktop #%d", info.getDebugId());
        boolean handledToPipOrDesktop = tryAnimateOpenIntentWithRemoteAndPipOrDesktop(
                info, startTransaction, finishTransaction, finishCallback);
                info, startTransaction, finishTransaction, finishCallback, splitHandler);
        // Consume the transition on remote handler if the leftover handler already handle this
        // transition. And if it cannot, the transition will be handled by remote handler, so don't
        // consume here.
@@ -218,9 +220,20 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
            @NonNull Transitions.TransitionFinishCallback finishCallback,
            @NonNull StageCoordinator splitHandler) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "tryAnimateOpenIntentWithRemoteAndPipOrDesktop");

        // This is specifically for handling trampolines w/ previously split tasks. In the case that
        // an activity (which will trampoline into a previously split task) is launched into
        // fullscreen, the StageCoordinator does not have enough info at transition-request time to
        // decide whether to handle the transition and never gets a chance to clean up the split
        // state. So we check here to see if that happened, and clean up if so.
        if (splitHandler.transitionImpliesSplitToFullscreen(info)) {
            splitHandler.dismissSplitInBackground(EXIT_REASON_FULLSCREEN_REQUEST);
        }

        TransitionInfo.Change pipChange = null;
        TransitionInfo.Change pipActivityChange = null;
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {