Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −3 Original line number Diff line number Diff line Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +27 −0 Original line number Diff line number Diff line Loading @@ -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) && Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +13 −2 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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>() Loading Loading @@ -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 } } Loading @@ -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, Loading libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt +9 −0 Original line number Diff line number Diff line Loading @@ -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) Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +4 −3 Original line number Diff line number Diff line Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +27 −0 Original line number Diff line number Diff line Loading @@ -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) && Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +13 −2 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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>() Loading Loading @@ -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 } } Loading @@ -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, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/freeform/TaskChangeListener.kt +9 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +15 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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