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

Commit 392c5dfc authored by Merissa Mitchell's avatar Merissa Mitchell
Browse files

[PiP on Desktop] Fix PiP issues with multiple_desktops_backend flag

This CL fixes a few issues with multi-activity PiP when the
multiple_desktops_backend flag is enabled.
- Instead of wct.reorder, the parent task for the multi-activity PiP
  needs to be minimized instead. In addition, when expanding PiP, the
parent/original task then needs to be unminimized.
- With the flag enabled, the child task that is newly created for the
  PiP window is reparented to the DeskRoot in Task#validateRootTask.
When InputMonitor updates the input consumer for the PiP window, the
root task's bounds are used, which ends up being fullscreen in this
case, causing PiP to consume input on the entire screen. This CL
removes the initial Builder#setParent call and adds a
TaskDisplayArea#addChild call after build() is called to ensure the new
pinned task is added to the TDA.

Bug: 419621266
Bug: 419908475
Bug: 421205793
Test: atest DesktopTasksControllerTest
DesktopPipTransitionControllerTest
Test: atest WMShellUnitTests:com.android.wm.shell.pip2
Test: atest WMShellUnitTests:com.android.wm.shell.pip
Test: atest WMShellUnitTests:com.android.wm.shell.common.pip
Test: Manual - launch ApiDemos in Desktop session and verify entering
PiP and expanding is WAI, and PiP window is only consuming input within
its bounds. Also verify that expanding PiP is WAI.
Test: Manual - launch ApiDemos in CD with empty desk. Verify PiP enters
on minimize and expanding PiP is WAI.
Flag: com.android.window.flags.enable_desktop_windowing_pip

Change-Id: I36971db0f86dec8312ba42d6b2ab60ba0c6e0e42
parent 201e6965
Loading
Loading
Loading
Loading
+37 −4
Original line number Diff line number Diff line
@@ -67,15 +67,15 @@ class DesktopPipTransitionController(
            parentTask.lastNonFullscreenBounds?.takeUnless { it.isEmpty }
                ?: calculateDefaultDesktopTaskBounds(pipDesktopState.getCurrentDisplayLayout())

        val newResolvedWinMode =
        val resolvedWinMode =
            if (pipDesktopState.isPipInDesktopMode()) WINDOWING_MODE_FREEFORM
            else WINDOWING_MODE_FULLSCREEN

        if (newResolvedWinMode != parentTask.windowingMode) {
            wct.setWindowingMode(parentTask.token, newResolvedWinMode)
        if (resolvedWinMode != parentTask.windowingMode) {
            wct.setWindowingMode(parentTask.token, resolvedWinMode)
            wct.setBounds(
                parentTask.token,
                if (newResolvedWinMode == WINDOWING_MODE_FREEFORM) defaultFreeformBounds else Rect()
                if (resolvedWinMode == WINDOWING_MODE_FREEFORM) defaultFreeformBounds else Rect(),
            )
        }
    }
@@ -113,6 +113,17 @@ class DesktopPipTransitionController(

        val deskId = getDeskId(desktopRepository, displayId)
        if (deskId == INVALID_DESK_ID) return

        val parentTaskId = runningTaskInfo.lastParentTaskIdBeforePip
        if (parentTaskId != ActivityTaskManager.INVALID_TASK_ID) {
            logD(
                "maybeReparentTaskToDesk: Multi-activity PiP, unminimize parent task in Desk" +
                    " instead of moving PiP task to Desk"
            )
            unminimizeParentInDesk(wct, parentTaskId, deskId)
            return
        }

        if (!desktopRepository.isDeskActive(deskId)) {
            logD(
                "maybeReparentTaskToDesk: addDeskActivationChanges, taskId=%d deskId=%d, " +
@@ -141,6 +152,28 @@ class DesktopPipTransitionController(
        )
    }

    private fun unminimizeParentInDesk(
        wct: WindowContainerTransaction,
        parentTaskId: Int,
        deskId: Int,
    ) {
        val parentTask = shellTaskOrganizer.getRunningTaskInfo(parentTaskId)
        if (parentTask == null) {
            logW(
                "unminimizeParentInDesk: Failed to find RunningTaskInfo for parentTaskId %d",
                parentTaskId,
            )
            return
        }

        logD("unminimizeParentInDesk: parentTaskId=%d deskId=%d", parentTask.taskId, deskId)
        desktopTasksController.addMoveTaskToFrontChanges(
            wct = wct,
            deskId = deskId,
            taskInfo = parentTask,
        )
    }

    /**
     * This is called by [PipTransition#handleRequest] when a request for entering PiP is received.
     *
+44 −14
Original line number Diff line number Diff line
@@ -1245,11 +1245,21 @@ class DesktopTasksController(
                transitions.dispatchRequest(SYNTHETIC_TRANSITION, requestInfo, /* skip= */ null)
            wct.merge(requestRes.second, true)

            // In multi-activity case, we explicitly reorder the parent task to the back so that it
            // is not brought to the front and shown when the child task breaks off into PiP.
            if (taskInfo.numActivities > 1) {
            // In multi-activity case, we either explicitly minimize the parent task, or reorder the
            // parent task to the back so that it is not brought to the front and shown when the
            // child task breaks off into PiP.
            val isMultiActivityPip = taskInfo.numActivities > 1
            if (isMultiActivityPip) {
                if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                    desksOrganizer.minimizeTask(
                        wct = wct,
                        deskId = checkNotNull(deskId) { "Expected non-null deskId" },
                        task = taskInfo,
                    )
                } else {
                    wct.reorder(taskInfo.token, /* onTop= */ false)
                }
            }

            // If the task minimizing to PiP is the last task, modify wct to perform Desktop cleanup
            var desktopExitRunnable: RunOnTransitStart? = null
@@ -1263,6 +1273,16 @@ class DesktopTasksController(
                    )
            }
            val transition = freeformTaskTransitionStarter.startPipTransition(wct)
            if (isMultiActivityPip) {
                desktopTasksLimiter.ifPresent {
                    it.addPendingMinimizeChange(
                        transition = transition,
                        displayId = displayId,
                        taskId = taskId,
                        minimizeReason = minimizeReason,
                    )
                }
            }
            desktopExitRunnable?.invoke(transition)
        } else {
            val willExitDesktop =
@@ -1562,12 +1582,31 @@ class DesktopTasksController(
    ) {
        val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
        logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId)
        val wct = WindowContainerTransaction()
        addMoveTaskToFrontChanges(wct = wct, deskId = deskId, taskInfo = taskInfo)
        startLaunchTransition(
            transitionType = TRANSIT_TO_FRONT,
            wct = wct,
            launchingTaskId = taskInfo.taskId,
            remoteTransition = remoteTransition,
            deskId = deskId,
            displayId = taskInfo.displayId,
            unminimizeReason = unminimizeReason,
        )
    }

    /** Applies the necessary [wct] changes to move [taskInfo] to front. */
    fun addMoveTaskToFrontChanges(
        wct: WindowContainerTransaction,
        deskId: Int?,
        taskInfo: RunningTaskInfo,
    ) {
        logV("addMoveTaskToFrontChanges taskId=%s deskId=%s", taskInfo.taskId, deskId)
        // If a task is tiled, another task should be brought to foreground with it so let
        // tiling controller handle the request.
        if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) {
            return
        }
        val wct = WindowContainerTransaction()
        if (deskId == null || !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            // Not a desktop task, just move to the front.
            wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true)
@@ -1575,15 +1614,6 @@ class DesktopTasksController(
            // A desktop task with multiple desks enabled, reorder it within its desk.
            desksOrganizer.reorderTaskToFront(wct, deskId, taskInfo)
        }
        startLaunchTransition(
            transitionType = TRANSIT_TO_FRONT,
            wct = wct,
            launchingTaskId = taskInfo.taskId,
            remoteTransition = remoteTransition,
            deskId = deskId,
            displayId = taskInfo.displayId,
            unminimizeReason = unminimizeReason,
        )
    }

    /**
+21 −17
Original line number Diff line number Diff line
@@ -69,7 +69,10 @@ class DesktopPipTransitionControllerTest(flags: FlagsParameterization) : ShellTe

    private val transition = Binder()
    private val wct = WindowContainerTransaction()
    private val taskInfo = createFreeformTask()
    private val taskInfo =
        createFreeformTask().apply {
            lastParentTaskIdBeforePip = ActivityTaskManager.INVALID_TASK_ID
        }
    private val freeformParentTask =
        createFreeformTask().apply { lastNonFullscreenBounds = FREEFORM_BOUNDS }
    private val fullscreenParentTask =
@@ -85,7 +88,6 @@ class DesktopPipTransitionControllerTest(flags: FlagsParameterization) : ShellTe
        whenever(mockPipDesktopState.isDisplayDesktopFirst(any())).thenReturn(false)
        whenever(mockPipDesktopState.isPipInDesktopMode()).thenReturn(true)
        whenever(mockDesktopUserRepositories.getProfile(any())).thenReturn(mockDesktopRepository)
        whenever(mockDesktopRepository.isAnyDeskActive(any())).thenReturn(true)
        whenever(mockDesktopRepository.getActiveDeskId(any())).thenReturn(DESK_ID)
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(taskInfo.taskId)).thenReturn(taskInfo)
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(freeformParentTask.taskId))
@@ -165,20 +167,32 @@ class DesktopPipTransitionControllerTest(flags: FlagsParameterization) : ShellTe

    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @Test
    fun maybeReparentTaskToDesk_noDeskActive_notDesktopFirstDisplay_noWctChanges() {
    fun maybeReparentTaskToDesk_multiActivity_addMoveTaskToFrontChanges() {
        val wct = WindowContainerTransaction()
        whenever(mockDesktopRepository.isAnyDeskActive(eq(taskInfo.displayId))).thenReturn(false)
        taskInfo.lastParentTaskIdBeforePip = freeformParentTask.taskId

        controller.maybeReparentTaskToDesk(wct, taskInfo.taskId)

        assertThat(wct.changes.isEmpty()).isTrue()
        verify(mockDesktopTasksController)
            .addMoveTaskToFrontChanges(wct = wct, deskId = DESK_ID, taskInfo = freeformParentTask)
    }

    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @Test
    fun maybeReparentTaskToDesk_noDeskActive_noAddMoveToDeskTaskChanges() {
        val wct = WindowContainerTransaction()
        whenever(mockDesktopRepository.getActiveDeskId(any())).thenReturn(null)

        controller.maybeReparentTaskToDesk(wct, taskInfo.taskId)

        verify(mockDesktopTasksController, never())
            .addMoveToDeskTaskChanges(wct = wct, task = taskInfo, deskId = DESK_ID)
    }

    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @Test
    fun maybeReparentTaskToDesk_deskActive_addMoveToDeskTaskChanges() {
        val wct = WindowContainerTransaction()
        whenever(mockDesktopRepository.isAnyDeskActive(eq(taskInfo.displayId))).thenReturn(true)

        controller.maybeReparentTaskToDesk(wct, taskInfo.taskId)

@@ -188,10 +202,9 @@ class DesktopPipTransitionControllerTest(flags: FlagsParameterization) : ShellTe

    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    @Test
    fun maybeReparentTaskToDesk_noDeskActive_desktopFirstDisplay_addDeskActivationChanges() {
    fun maybeReparentTaskToDesk_desktopFirstDisplay_addDeskActivationChanges() {
        val wct = WindowContainerTransaction()
        whenever(mockDesktopRepository.getActiveDeskId(any())).thenReturn(null)
        whenever(mockDesktopRepository.isAnyDeskActive(eq(taskInfo.displayId))).thenReturn(false)
        whenever(mockPipDesktopState.isDisplayDesktopFirst(any())).thenReturn(true)
        whenever(mockDesktopRepository.getDefaultDeskId(any())).thenReturn(DESK_ID)

@@ -208,15 +221,6 @@ class DesktopPipTransitionControllerTest(flags: FlagsParameterization) : ShellTe
            .addMoveToDeskTaskChanges(wct = wct, task = taskInfo, deskId = DESK_ID)
    }

    @Test
    fun handlePipTransition_noDeskActive_doesntPerformDesktopExitCleanup() {
        whenever(mockDesktopRepository.isAnyDeskActive(eq(taskInfo.displayId))).thenReturn(false)

        controller.handlePipTransition(wct, transition, taskInfo)

        verifyPerformDesktopExitCleanupAfterPip(isCalled = false)
    }

    @Test
    fun handlePipTransition_notLastTask_doesntPerformDesktopExitCleanup() {
        whenever(
+19 −0
Original line number Diff line number Diff line
@@ -4471,6 +4471,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
    @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun onPipTaskMinimize_multiActivity_reordersParentToBack() {
        val task = setUpPipTask(autoEnterEnabled = true).apply { numActivities = 2 }
        // Add a second task so that entering PiP does not trigger Desktop cleanup
@@ -4484,6 +4485,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
        arg.lastValue.assertReorderAt(index = 0, task, toTop = false)
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
        Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
    )
    fun onPipTaskMinimize_multiActivity_minimizeParent() {
        val task = setUpPipTask(autoEnterEnabled = true).apply { numActivities = 2 }
        // Add a second task so that entering PiP does not trigger Desktop cleanup
        setUpFreeformTask(deskId = DEFAULT_DISPLAY)

        minimizePipTask(task)

        val arg = argumentCaptor<WindowContainerTransaction>()
        verify(freeformTaskTransitionStarter).startPipTransition(arg.capture())
        verify(desksOrganizer)
            .minimizeTask(wct = arg.lastValue, deskId = DEFAULT_DISPLAY, task = task)
    }

    @Test
    fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
        val task = setUpFreeformTask(active = true)
+9 −6
Original line number Diff line number Diff line
@@ -2166,16 +2166,19 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
                        .setActivityType(r.getActivityType())
                        .setOnTop(true)
                        .setActivityInfo(r.info)
                        .setParent(taskDisplayArea)
                        .setIntent(r.intent)
                        .setDeferTaskAppear(true)
                        .setHasBeenVisible(true)
                        // In case the activity is in system split screen, or Activity Embedding
                        .build();

                taskDisplayArea.addChild(rootTask, POSITION_TOP);
                // The windowing mode should be set after attaching to display area or it will abort
                // silently. In case the activity is in system split screen, or Activity Embedding
                // split, we need to animate the PIP Task from the original TaskFragment
                // bounds, so also setting the windowing mode, otherwise the bounds may
                // be reset to fullscreen.
                        .setWindowingMode(taskFragment.getWindowingMode())
                        .build();
                rootTask.setWindowingMode(taskFragment.getWindowingMode());

                // Establish bi-directional link between the original and pinned task.
                r.setLastParentBeforePip(launchIntoPipHostActivity);
                // It's possible the task entering PIP is in freeform, so save the last