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

Commit 6d5dd398 authored by Shuming Hao's avatar Shuming Hao
Browse files

Use current display as parent when exiting split screen

When split pair is dismissed in connected display, the stage tasks should stay in current display area. This CL updates the stage task's new parent as current display token.

Test: manual
Flag: com.android.window.flags.enable_non_default_display_split
Bug: 437145187
Bug: 438324415
Change-Id: Ib0c569b6a39da513688e6ead4f86352e820c54e7
parent 7219a792
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -26,15 +26,22 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.window.DesktopExperienceFlags;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;

import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.shared.split.SplitScreenConstants;
import com.android.wm.shell.splitscreen.StageTaskListener;

/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
@@ -152,4 +159,30 @@ public class SplitScreenUtils {
                return false;
        }
    }

    /**
     * Retrieves the new parent WindowContainerToken for tasks in the main or stage display area
     * after the split pair is dismissed. This token will be used for reparenting tasks. The
     * specific stage (main or side) from which the display ID is obtained does not alter the
     * resulting parent token, as it's based on the display area of the display itself.
     *
     * @param stage The StageTaskListener representing the current stage.
     * @param rootTDAOrganizer The RootTaskDisplayAreaOrganizer to query for DisplayAreaInfo.
     * @return The WindowContainerToken of the parent display area if
     *         DesktopExperienceFlags.ENABLE_NON_DEFAULT_DISPLAY_SPLIT is true and a valid
     *         DisplayAreaInfo is found for the main stage's display; otherwise, returns null.
     */
    @Nullable
    public static WindowContainerToken getNewParentTokenForStage(
            @Nullable StageTaskListener stage,
            @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
        if (!DesktopExperienceFlags.ENABLE_NON_DEFAULT_DISPLAY_SPLIT.isTrue()
                || stage == null || stage.getRunningTaskInfo() == null) {
            return null;
        }

        final int displayId = stage.getRunningTaskInfo().displayId;
        final DisplayAreaInfo displayAreaInfo = rootTDAOrganizer.getDisplayAreaInfo(displayId);
        return displayAreaInfo != null ? displayAreaInfo.token : null;
    }
}
+25 −16
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX_HYBRID
import static com.android.wm.shell.common.split.SplitLayout.RESTING_DIM_LAYER;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.common.split.SplitScreenUtils.getNewParentTokenForStage;
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;
@@ -329,15 +330,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }

        // Find the current split-screen root task info.
        RunningTaskInfo currentRootTaskInfo = null;
        for (int id : mSplitMultiDisplayHelper.getCachedOrSystemDisplayIds()) {
            final RunningTaskInfo rootTaskInfo =
                    mSplitMultiDisplayHelper.getDisplayRootTaskInfo(id);
            if (rootTaskInfo != null) {
                currentRootTaskInfo = rootTaskInfo;
                break;
            }
        }
        RunningTaskInfo currentRootTaskInfo = mSplitMultiDisplayHelper.getCachedOrSystemDisplayIds()
                .stream()
                .map(id -> mSplitMultiDisplayHelper.getDisplayRootTaskInfo(id))
                .filter(Objects::nonNull)
                .findFirst()
                .orElse(null);

        if (currentRootTaskInfo == null) {
            throw new IllegalStateException("Failed to find current split screen root task info.");
@@ -662,16 +660,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    .filter(stage -> stage.getId() == stageToTop)
                    .findFirst().orElse(null);
            if (stageToDeactivate != null) {
                stageToDeactivate.deactivate(wct, true /*toTop*/);
                stageToDeactivate.deactivate(wct, true /*reparentTasksToTop*/,
                        getNewParentTokenForStage(stageToDeactivate, mRootTDAOrganizer));
            } else {
                // If no one stage is meant to go to the top, deactivate all stages to move any
                // child tasks out from under their respective stage root tasks.
                mStageOrderOperator.getAllStages().forEach(stage ->
                        stage.deactivate(wct, false /*reparentTasksToTop*/));
                        stage.deactivate(wct, false /*reparentTasksToTop*/, null));
            }
            mStageOrderOperator.onExitingSplit();
        } else {
            mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
            mMainStage.deactivate(
                    wct, stageToTop == STAGE_TYPE_MAIN,
                    getNewParentTokenForStage(mMainStage, mRootTDAOrganizer));
        }
    }

@@ -1790,8 +1791,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        // TODO: b/393217881 - replace DEFAULT DISPLAY with the current display id
        RunningTaskInfo rootTaskInfo =
                mSplitMultiDisplayHelper.getDisplayRootTaskInfo(DEFAULT_DISPLAY);

        if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
            mSideStage.removeAllTasks(wct, false /* toTop */);
            mSideStage.removeAllTasks(
                    wct, false /* toTop */,
                    getNewParentTokenForStage(mSideStage, mRootTDAOrganizer));
            deactivateSplit(wct, STAGE_TYPE_UNDEFINED);
            wct.reorder(rootTaskInfo.token, false /* onTop */);
            setRootForceTranslucent(true, wct);
@@ -1822,7 +1826,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
                    mIsExiting = false;
                    deactivateSplit(finishedWCT, childrenToTop.getId());
                    mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                    mSideStage.removeAllTasks(
                            finishedWCT, childrenToTop == mSideStage /* toTop */,
                            getNewParentTokenForStage(mSideStage, mRootTDAOrganizer));
                    finishedWCT.reorder(rootTaskInfo.token, false /* toTop */);
                    setRootForceTranslucent(true, finishedWCT);
                    finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1981,9 +1987,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        if (enableFlexibleSplit()) {
            mStageOrderOperator.getActiveStages().stream()
                    .filter(stage -> stage.getId() != stageToTop)
                    .forEach(stage -> stage.removeAllTasks(wct, false /*toTop*/));
                    .forEach(stage ->
                            stage.removeAllTasks(wct, false /*toTop*/,
                                    getNewParentTokenForStage(stage, mRootTDAOrganizer)));
        } else {
            mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
            mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE,
                    getNewParentTokenForStage(mSideStage, mRootTDAOrganizer));
        }

        if (exitReason != EXIT_REASON_DESKTOP_MODE) {
+7 −5
Original line number Diff line number Diff line
@@ -533,10 +533,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    }

    void deactivate(WindowContainerTransaction wct) {
        deactivate(wct, false /* toTop */);
        deactivate(wct, false /* toTop */, null /* newParent */);
    }

    void deactivate(WindowContainerTransaction wct, boolean reparentTasksToTop) {
    void deactivate(WindowContainerTransaction wct, boolean reparentTasksToTop,
            @Nullable WindowContainerToken newParent) {
        if (!mIsActive && !enableFlexibleSplit()) return;
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: reparentTasksToTop=%b "
                        + "rootTaskInfo=%s stage=%s",
@@ -549,7 +550,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        final WindowContainerToken rootToken = mRootTaskInfo.token;
        wct.reparentTasks(
                rootToken,
                null /* newParent */,
                newParent,
                null /* windowingModes */,
                null /* activityTypes */,
                reparentTasksToTop);
@@ -557,14 +558,15 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {

    // --------
    // Previously only used in SideStage. With flexible split this is called for all stages
    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop,
            @Nullable WindowContainerToken newParent) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b "
                        + " stageI=%s",
                mChildrenTaskInfo.size(), toTop, stageTypeToString(mId));
        if (mChildrenTaskInfo.size() == 0) return false;
        wct.reparentTasks(
                mRootTaskInfo.token,
                null /* newParent */,
                newParent,
                null /* windowingModes */,
                null /* activityTypes */,
                toTop);
+7 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;

import static com.android.wm.shell.common.split.SplitScreenUtils.getNewParentTokenForStage;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
@@ -624,11 +625,15 @@ public class SplitTransitionTests extends ShellTestCase {
        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
            WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
            if (op.getType() == HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT) {
                IBinder expectedNewParentBinder = Optional.ofNullable(
                        getNewParentTokenForStage(mMainStage, mRootTDAOrganizer))
                                .map(WindowContainerToken::asBinder)
                                .orElse(null);
                if (op.getContainer() == mMainStage.mRootTaskInfo.token.asBinder()
                        && op.getNewParent() == null) {
                        && op.getNewParent() == expectedNewParentBinder) {
                    reparentedMain = true;
                } else if (op.getContainer() == mSideStage.mRootTaskInfo.token.asBinder()
                        && op.getNewParent() == null) {
                        && op.getNewParent() == expectedNewParentBinder) {
                    reparentedSide = true;
                }
            }