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

Commit bf2c11dc authored by Kazuki Takise's avatar Kazuki Takise
Browse files

Clean up invisible tasks in task repo when they are removed

Removing an invisible task is an invisible->invisible change,
so no shell transition runs for this, and DesktopRespository misses
cleaning up task data, which could lead to DesktopTasksController
incorrectly trying to restore the tasks when the desk is
reactivated next time.

This change adds clean-up logic to
RootTaskDesksOrganizer#onTaskVanished() to handle this case.

Flag: com.android.window.flags.enable_desktop_invisible_task_removal_cleanup_bugfix
Bug: 427869488
Test: RootTaskDesksOrganizerTest#testOnTaskVanished_removesChildTask_invokesNonTransitionTaskClosing
Test: DesktopTaskChangeListenerTest#onNonTransitionTaskClosing_invisibleFreeformTask_removesTaskFromRepo
Change-Id: Ic063d287d35a80b7b2abebfa831702232166cf73
parent 1cf0749b
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -868,10 +868,11 @@ public abstract class WMShellModule {
            @NonNull ShellCommandHandler shellCommandHandler,
            @NonNull ShellTaskOrganizer shellTaskOrganizer,
            @NonNull LaunchAdjacentController launchAdjacentController,
            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
    ) {
            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            @NonNull Optional<TaskChangeListener> taskChangeListener) {
        return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer,
                launchAdjacentController, rootTaskDisplayAreaOrganizer);
                launchAdjacentController, rootTaskDisplayAreaOrganizer,
                taskChangeListener);
    }

    @WMSingleton
+27 −0
Original line number Diff line number Diff line
@@ -176,6 +176,33 @@ class DesktopTaskChangeListener(
        )
    }

    // This method should only be used for scenarios where the task close events are not propagated
    // to [DesktopTaskChangeListener#onTaskClosing] via [TransitionsObserver].
    // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk
    // of race conditions and possible duplications with [onTaskClosing].
    override fun onNonTransitionTaskClosing(taskInfo: RunningTaskInfo) {
        logD(
            "onNonTransitionTaskClosing for taskId=%d, displayId=%d",
            taskInfo.taskId,
            taskInfo.displayId,
        )

        if (DesktopExperienceFlags.ENABLE_DESKTOP_INVISIBLE_TASK_REMOVAL_CLEANUP_BUGFIX.isTrue) {
            // Removing an invisible task is an invisible->invisible change, so no shell transition
            // runs for this, and DesktopRepository misses cleaning up task data, which could lead
            // to DesktopTasksController incorrectly trying to restore the tasks when the desk is
            // reactivated next time. See b/361419732.
            val repository: DesktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
            if (
                repository.getDeskIdForTask(taskInfo.taskId) != null &&
                    !repository.isVisibleTask(taskInfo.taskId)
            ) {
                repository.removeClosingTask(taskInfo.taskId)
                repository.removeTask(taskInfo.taskId)
            }
        }
    }

    override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) {
        if (
            !desktopState.isDesktopModeSupportedOnDisplay(taskInfo.displayId) &&
+13 −2
Original line number Diff line number Diff line
@@ -41,10 +41,12 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
import com.android.wm.shell.freeform.TaskChangeListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
import java.util.Optional

/**
 * A [DesksOrganizer] that uses root tasks as the container of each desk.
@@ -59,6 +61,7 @@ class RootTaskDesksOrganizer(
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val launchAdjacentController: LaunchAdjacentController,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    private val taskChangeListener: Optional<TaskChangeListener>,
) : DesksOrganizer, ShellTaskOrganizer.TaskListener {

    private val createDeskRootRequests = mutableListOf<CreateDeskRequest>()
@@ -619,7 +622,7 @@ class RootTaskDesksOrganizer(
        deskRootsByDeskId.forEach { deskId, deskRoot ->
            if (deskRoot.children.remove(taskInfo.taskId)) {
                logV("Task #${taskInfo.taskId} vanished from desk #$deskId")
                childLeashes.remove(taskInfo.taskId)
                cleanUpChildTask(taskInfo)
                return
            }
        }
@@ -628,12 +631,20 @@ class RootTaskDesksOrganizer(
            val taskId = taskInfo.taskId
            if (root.children.remove(taskId)) {
                logV("Task #$taskId vanished from minimization root of desk #${root.deskId}")
                childLeashes.remove(taskInfo.taskId)
                cleanUpChildTask(taskInfo)
                return
            }
        }
    }

    private fun cleanUpChildTask(taskInfo: RunningTaskInfo) {
        childLeashes.remove(taskInfo.taskId)

        // Notify task close events to the [TaskChangeListener] since [TransitionsObserver]
        // does not trigger them when invisible tasks are removed.
        taskChangeListener.ifPresent { listener -> listener.onNonTransitionTaskClosing(taskInfo) }
    }

    private fun createDeskMinimizationRoot(
        displayId: Int,
        deskId: Int,
+9 −0
Original line number Diff line number Diff line
@@ -39,6 +39,15 @@ interface TaskChangeListener {
     */
    fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo)

    /**
     * Notifies a task close event on the given task from [RootTaskDesksOrganizer].
     *
     * This is used to propagate task close signals since not all task close events are propagated
     * from [TransitionObserver] in [onTaskClosing]. It is recommended to use [onTaskClosing]
     * instead of this method where possible.
     */
    fun onNonTransitionTaskClosing(taskInfo: RunningTaskInfo)

    /** Notifies a task moving to the front. */
    fun onTaskMovingToFront(taskInfo: RunningTaskInfo)

+15 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.server.am.Flags.FLAG_PERCEPTIBLE_TASKS
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_INVISIBLE_TASK_REMOVAL_CLEANUP_BUGFIX
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.window.flags.Flags.FLAG_MOVE_TO_NEXT_DISPLAY_SHORTCUT_WITH_PROJECTED_MODE
@@ -46,6 +47,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@@ -530,6 +532,19 @@ class DesktopTaskChangeListenerTest : ShellTestCase() {
        assertThat(desktopTaskChangeListener.isTaskPerceptible(task.taskId)).isFalse()
    }

    @Test
    @EnableFlags(FLAG_ENABLE_DESKTOP_INVISIBLE_TASK_REMOVAL_CLEANUP_BUGFIX)
    fun onNonTransitionTaskClosing_invisibleFreeformTask_removesTaskFromRepo() {
        val task = createFreeformTask().apply { isVisible = false }
        desktopTaskChangeListener.onTaskOpening(task)

        whenever(desktopUserRepositories.current.isVisibleTask(task.taskId)).thenReturn(false)
        desktopTaskChangeListener.onNonTransitionTaskClosing(task)

        verify(desktopUserRepositories.current, times(1)).removeClosingTask(task.taskId)
        verify(desktopUserRepositories.current, times(1)).removeTask(task.taskId)
    }

    private fun createWallpaperTaskInfo(windowingMode: Int): RunningTaskInfo =
        TestRunningTaskInfoBuilder()
            .setBaseIntent(
Loading