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 Original line Diff line number Diff line
@@ -1085,6 +1085,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
                return "FULLSCREEN_REQUEST";
                return "FULLSCREEN_REQUEST";
            case EXIT_REASON_DRAG_TO_FULLSCREEN:
            case EXIT_REASON_DRAG_TO_FULLSCREEN:
                return "EXIT_REASON_DRAG_TO_FULLSCREEN";
                return "EXIT_REASON_DRAG_TO_FULLSCREEN";
            case EXIT_REASON_CHILD_TASK_ENTER_BUBBLE:
                return "CHILD_TASK_ENTER_BUBBLE";
            default:
            default:
                return "unknown reason, reason int = " + exitReason;
                return "unknown reason, reason int = " + exitReason;
        }
        }
+39 −4
Original line number Original line Diff line number Diff line
@@ -490,7 +490,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    mSyncQueue,
                    iconProvider,
                    iconProvider,
                    mWindowDecorViewModel);
                    mWindowDecorViewModel,
                    mBubbleController);
        } else {
        } else {
            mMainStage = new StageTaskListener(
            mMainStage = new StageTaskListener(
                    mContext,
                    mContext,
@@ -499,7 +500,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    mSyncQueue,
                    iconProvider,
                    iconProvider,
                    mWindowDecorViewModel, STAGE_TYPE_MAIN);
                    mWindowDecorViewModel, STAGE_TYPE_MAIN,
                    bubbleController);
            mSideStage = new StageTaskListener(
            mSideStage = new StageTaskListener(
                    mContext,
                    mContext,
                    mTaskOrganizer,
                    mTaskOrganizer,
@@ -507,7 +509,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    this /*stageListenerCallbacks*/,
                    this /*stageListenerCallbacks*/,
                    mSyncQueue,
                    mSyncQueue,
                    iconProvider,
                    iconProvider,
                    mWindowDecorViewModel, STAGE_TYPE_SIDE);
                    mWindowDecorViewModel, STAGE_TYPE_SIDE,
                    mBubbleController);
        }
        }
        mTransitions = transitions;
        mTransitions = transitions;
        mDisplayController = displayController;
        mDisplayController = displayController;
@@ -1745,7 +1748,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    /**
    /**
     * Starts a new transition to dismiss split.
     * 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()) {
        if (!isSplitActive()) {
            return;
            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() {
    private void updateRecentTasksSplitPair() {
        // Preventing from single task update while processing recents.
        // Preventing from single task update while processing recents.
        if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) {
        if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) {
+8 −4
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
import com.android.internal.protolog.ProtoLog
import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.icons.IconProvider
import com.android.wm.shell.ShellTaskOrganizer
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.common.SyncTransactionQueue
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.shared.split.SplitScreenConstants
@@ -52,7 +53,8 @@ class StageOrderOperator (
        stageCallbacks: StageTaskListener.StageListenerCallbacks,
        stageCallbacks: StageTaskListener.StageListenerCallbacks,
        syncQueue: SyncTransactionQueue,
        syncQueue: SyncTransactionQueue,
        iconProvider: IconProvider,
        iconProvider: IconProvider,
        windowDecorViewModel: Optional<WindowDecorViewModel>
        windowDecorViewModel: Optional<WindowDecorViewModel>,
        bubbleController: Optional<BubbleController>,
) {
) {


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


    init {
    init {
        for(i in 0 until MAX_STAGES) {
        for(i in 0 until MAX_STAGES) {
            allStages.add(StageTaskListener(context,
            allStages.add(StageTaskListener(
                context,
                taskOrganizer,
                taskOrganizer,
                displayId,
                displayId,
                stageCallbacks,
                stageCallbacks,
                syncQueue,
                syncQueue,
                iconProvider,
                iconProvider,
                windowDecorViewModel,
                windowDecorViewModel,
                stageIds[i])
                stageIds[i],
                bubbleController)
            )
            )
        }
        }
    }
    }
+15 −3
Original line number Original line 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 android.view.RemoteAnimationTarget.MODE_OPENING;


import static com.android.wm.shell.Flags.enableFlexibleSplit;
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.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_ACTIVITY_TYPES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
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.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
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.SurfaceUtils;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitDecorManager;
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,
        void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
                boolean visible);
                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. */
        /** Called when the root task on current display vanishes. */
        void onRootTaskVanished(ActivityManager.RunningTaskInfo taskInfo);
        void onRootTaskVanished(ActivityManager.RunningTaskInfo taskInfo);
@@ -106,6 +110,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    private final SyncTransactionQueue mSyncQueue;
    private final SyncTransactionQueue mSyncQueue;
    private final IconProvider mIconProvider;
    private final IconProvider mIconProvider;
    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
    private final Optional<BubbleController> mBubbleController;


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