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

Commit 438d181c authored by Ats Jenk's avatar Ats Jenk
Browse files

Exit split if task moves to bubble via trampoline

If a split screen task moves to bubble, via a trampoline, we only find
out about it when the transition animation starts.
This means we can't exit split screen as part of the same transition.

As split clears up the split screen state based on the TaskListener,
and this is triggered before the transition animation, bubbles can't
check that the task was previously in split.

Adding logic to the split StageTaskListener to detect cases when a task
is moving out of split screen and into a bubble. Exiting split screen in
such cases.

Bug: 440647718
Test: atest StageCoordinatorTests
Test: atest StageTaskListenerTests
Flag: com.android.wm.shell.fix_exit_split_on_enter_bubble
Change-Id: I47a97f08d50205b08d56972449b5cf0fc88eabdb
parent 893a1854
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1085,6 +1085,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
                return "FULLSCREEN_REQUEST";
            case EXIT_REASON_DRAG_TO_FULLSCREEN:
                return "EXIT_REASON_DRAG_TO_FULLSCREEN";
            case EXIT_REASON_CHILD_TASK_ENTER_BUBBLE:
                return "CHILD_TASK_ENTER_BUBBLE";
            default:
                return "unknown reason, reason int = " + exitReason;
        }
+39 −4
Original line number Diff line number Diff line
@@ -490,7 +490,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    iconProvider,
                    mWindowDecorViewModel);
                    mWindowDecorViewModel,
                    mBubbleController);
        } else {
            mMainStage = new StageTaskListener(
                    mContext,
@@ -499,7 +500,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    iconProvider,
                    mWindowDecorViewModel, STAGE_TYPE_MAIN);
                    mWindowDecorViewModel, STAGE_TYPE_MAIN,
                    bubbleController);
            mSideStage = new StageTaskListener(
                    mContext,
                    mTaskOrganizer,
@@ -507,7 +509,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    iconProvider,
                    mWindowDecorViewModel, STAGE_TYPE_SIDE);
                    mWindowDecorViewModel, STAGE_TYPE_SIDE,
                    mBubbleController);
        }
        mTransitions = transitions;
        mDisplayController = displayController;
@@ -1745,7 +1748,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    /**
     * Starts a new transition to dismiss split.
     */
    private void dismissSplit(@StageType int stageToTop, @ExitReason int exitReason) {
    @VisibleForTesting
    void dismissSplit(@StageType int stageToTop, @ExitReason int exitReason) {
        if (!isSplitActive()) {
            return;
        }
@@ -2359,6 +2363,37 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
    }

    @Override
    public void onChildTaskMovedToBubble(StageTaskListener stage, int taskId) {
        if (stage.getChildCount() != 0) {
            ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                    "onChildTaskMovedToBubble: task=%d in stage=%s moved to bubble, ignore, "
                            + "childCount=%d",
                    taskId, stageTypeToString(stage.getId()), stage.getChildCount());
            return;
        }
        int stageToTop = STAGE_TYPE_UNDEFINED;
        if (enableFlexibleSplit()) {
            for (StageTaskListener activeStage : mStageOrderOperator.getActiveStages()) {
                // See if any other stage still has children
                if (activeStage.getChildCount() > 0) {
                    stageToTop = activeStage.getId();
                    break;
                }
            }
        } else {
            final StageTaskListener remainingStage = stage == mMainStage ? mSideStage : mMainStage;
            if (remainingStage.getChildCount() > 0) {
                stageToTop = remainingStage.getId();
            }
        }
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
                "onChildTaskMovedToBubble: taskId=%d in stage=%s moved to bubble exit split "
                        + "stageToTop=%s",
                taskId, stageTypeToString(stage.getId()), stageTypeToString(stageToTop));
        dismissSplit(stageToTop, EXIT_REASON_CHILD_TASK_ENTER_BUBBLE);
    }

    private void updateRecentTasksSplitPair() {
        // Preventing from single task update while processing recents.
        if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) {
+8 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.IconProvider
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.split.SplitScreenConstants
@@ -52,7 +53,8 @@ class StageOrderOperator (
        stageCallbacks: StageTaskListener.StageListenerCallbacks,
        syncQueue: SyncTransactionQueue,
        iconProvider: IconProvider,
        windowDecorViewModel: Optional<WindowDecorViewModel>
        windowDecorViewModel: Optional<WindowDecorViewModel>,
        bubbleController: Optional<BubbleController>,
) {

    private val MAX_STAGES = 3
@@ -77,14 +79,16 @@ class StageOrderOperator (

    init {
        for(i in 0 until MAX_STAGES) {
            allStages.add(StageTaskListener(context,
            allStages.add(StageTaskListener(
                context,
                taskOrganizer,
                displayId,
                stageCallbacks,
                syncQueue,
                iconProvider,
                windowDecorViewModel,
                stageIds[i])
                stageIds[i],
                bubbleController)
            )
        }
    }
+15 −3
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFIN
import static android.view.RemoteAnimationTarget.MODE_OPENING;

import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.Flags.fixExitSplitOnEnterBubble;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -51,6 +52,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
@@ -93,6 +95,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
                boolean visible);

        /** Called when a child task vanished and is now in a bubble. */
        void onChildTaskMovedToBubble(StageTaskListener stage, int taskId);

        /** Called when the root task on current display vanishes. */
        void onRootTaskVanished(ActivityManager.RunningTaskInfo taskInfo);
@@ -106,6 +110,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    private final SyncTransactionQueue mSyncQueue;
    private final IconProvider mIconProvider;
    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
    private final Optional<BubbleController> mBubbleController;

    /** Whether or not the root task has been created. */
    boolean mHasRootTask = false;
@@ -124,12 +129,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            IconProvider iconProvider,
            Optional<WindowDecorViewModel> windowDecorViewModel, int id) {
            Optional<WindowDecorViewModel> windowDecorViewModel, int id,
            Optional<BubbleController> bubbleController) {
        mContext = context;
        mCallbacks = callbacks;
        mSyncQueue = syncQueue;
        mIconProvider = iconProvider;
        mWindowDecorViewModel = windowDecorViewModel;
        mBubbleController = bubbleController;
        taskOrganizer.createRootTask(
                new TaskOrganizer.CreateRootTaskRequest()
                        .setName(stageTypeToString(id).toLowerCase())
@@ -334,8 +341,13 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        } else if (mChildrenTaskInfo.contains(taskId)) {
            mChildrenTaskInfo.remove(taskId);
            mChildrenLeashes.remove(taskId);
            if (fixExitSplitOnEnterBubble()
                    && mBubbleController.map(c -> c.shouldBeAppBubble(taskInfo)).orElse(false)) {
                mCallbacks.onChildTaskMovedToBubble(this, taskId);
            } else {
                mCallbacks.onChildTaskStatusChanged(this, taskId, false /* present */,
                        taskInfo.isVisible);
            }
        } else {
            throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                    + "\n mRootTaskInfo: " + mRootTaskInfo);
+6 −2
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -138,6 +139,7 @@ public class SplitTransitionTests extends ShellTestCase {
    private FakeDesktopState mDesktopState;
    @Mock private IActivityTaskManager mActivityTaskManager;
    @Mock private MSDLPlayer mMSDLPlayer;
    @Mock private BubbleController mBubbleController;
    private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
    private SplitLayout mSplitLayout;
    private StageTaskListener mMainStage;
@@ -164,11 +166,13 @@ public class SplitTransitionTests extends ShellTestCase {
        mSplitLayout = SplitTestUtils.createMockSplitLayout();
        mMainStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                StageTaskListener.StageListenerCallbacks.class), mSyncQueue,
                mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_MAIN));
                mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_MAIN,
                Optional.of(mBubbleController)));
        mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
        mSideStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                StageTaskListener.StageListenerCallbacks.class), mSyncQueue,
                mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_SIDE));
                mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_SIDE,
                Optional.of(mBubbleController)));
        mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
        mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
Loading