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

Commit f451c77c authored by Ivan Tkachenko's avatar Ivan Tkachenko
Browse files

Fix empty desktop mode after closing the last window

- Added `closingTasks` to repository to keep track of the tasks that are currently closing but not yet vanished.
- Checking for closing and minimized tasks in #isOnlyNonClosingTask
- Back navigation handled in DesktopTasksController#handleBackNavigation and closing task is cleaned up in onTaskInfoChanged.

Bug: 343726497
Test: atest WMShellUnitTests:DesktopTasksControllerTest WMShellUnitTests:DesktopModeTaskRepositoryTest
Flag: com.android.window.flags.enable_desktop_windowing_wallpaper_activity
Change-Id: I3a8ba46df3f97bb6f32283500f84d2ebb3ebe2fe
parent 0eaf4ed5
Loading
Loading
Loading
Loading
+52 −3
Original line number Diff line number Diff line
@@ -46,6 +46,9 @@ class DesktopModeTaskRepository {
        val activeTasks: ArraySet<Int> = ArraySet(),
        val visibleTasks: ArraySet<Int> = ArraySet(),
        val minimizedTasks: ArraySet<Int> = ArraySet(),
        // Tasks that are closing, but are still visible
        //  TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
        val closingTasks: ArraySet<Int> = ArraySet(),
        // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
        val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
    )
@@ -169,6 +172,42 @@ class DesktopModeTaskRepository {
        return result
    }

    /**
     * Mark a task with given [taskId] as closing on given [displayId]
     *
     * @return `true` if the task was not closing on given [displayId]
     */
    fun addClosingTask(displayId: Int, taskId: Int): Boolean {
        val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
        if (added) {
            KtProtoLog.d(
                WM_SHELL_DESKTOP_MODE,
                "DesktopTaskRepo: added closing task=%d displayId=%d",
                taskId,
                displayId
            )
        }
        return added
    }

    /**
     * Remove task with given [taskId] from closing tasks.
     *
     * @return `true` if the task was closing
     */
    fun removeClosingTask(taskId: Int): Boolean {
        var removed = false
        displayData.forEach { _, data ->
            if (data.closingTasks.remove(taskId)) {
                removed = true
            }
        }
        if (removed) {
            KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
        }
        return removed
    }

    /** Check if a task with the given [taskId] was marked as an active task */
    fun isActiveTask(taskId: Int): Boolean {
        return displayData.valueIterator().asSequence().any { data ->
@@ -176,6 +215,10 @@ class DesktopModeTaskRepository {
        }
    }

    /** Check if a task with the given [taskId] was marked as a closing task */
    fun isClosingTask(taskId: Int): Boolean =
        displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks }

    /** Whether a task is visible. */
    fun isVisibleTask(taskId: Int): Boolean {
        return displayData.valueIterator().asSequence().any { data ->
@@ -190,10 +233,16 @@ class DesktopModeTaskRepository {
        }
    }

    /** Check if a task with the given [taskId] is the only active task on its display */
    fun isOnlyActiveTask(taskId: Int): Boolean {
    /**
     * Check if a task with the given [taskId] is the only active, non-closing, not-minimized task
     * on its display
     */
    fun isOnlyActiveNonClosingTask(taskId: Int): Boolean {
        return displayData.valueIterator().asSequence().any { data ->
            data.activeTasks.singleOrNull() == taskId
            data.activeTasks
                .subtract(data.closingTasks)
                .subtract(data.minimizedTasks)
                .singleOrNull() == taskId
        }
    }

+27 −13
Original line number Diff line number Diff line
@@ -442,12 +442,21 @@ class DesktopTasksController(
     * active task.
     *
     * @param wct transaction to modify if the last active task is closed
     * @param displayId display id of the window that's being closed
     * @param taskId task id of the window that's being closed
     */
    fun onDesktopWindowClose(wct: WindowContainerTransaction, taskId: Int) {
        if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) {
    fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) {
        if (desktopModeTaskRepository.isOnlyActiveNonClosingTask(taskId)) {
            removeWallpaperActivity(wct)
        }
        if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
            // Could happen if the task hasn't been removed from closing list after it disappeared
            KtProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: the task with taskId=%d is already closing!",
                taskId
            )
        }
    }

    /** Move a task with given `taskId` to fullscreen */
@@ -871,7 +880,7 @@ class DesktopTasksController(
                    false
                }
                // Handle back navigation for the last window if wallpaper available
                shouldRemoveWallpaper(request) -> true
                shouldHandleBackNavigation(request) -> true
                // Only handle open or to front transitions
                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
                    reason = "transition type not handled (${request.type})"
@@ -951,13 +960,9 @@ class DesktopTasksController(
    private fun shouldLaunchAsModal(task: TaskInfo) =
        Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)

    private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
    private fun shouldHandleBackNavigation(request: TransitionRequestInfo): Boolean {
        return Flags.enableDesktopWindowingWallpaperActivity() &&
            request.type == TRANSIT_TO_BACK &&
            request.triggerTask?.let { task ->
                desktopModeTaskRepository.isOnlyActiveTask(task.taskId)
            }
                ?: false
            request.type == TRANSIT_TO_BACK
    }

    private fun handleFreeformTaskLaunch(
@@ -1026,15 +1031,24 @@ class DesktopTasksController(

    /** Handle back navigation by removing wallpaper activity if it's the last active task */
    private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? {
        if (
            desktopModeTaskRepository.isOnlyActiveTask(task.taskId) &&
        val wct = if (
            desktopModeTaskRepository.isOnlyActiveNonClosingTask(task.taskId) &&
                desktopModeTaskRepository.wallpaperActivityToken != null
        ) {
            // Remove wallpaper activity when the last active task is removed
            return WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
            WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
        } else {
            return null
            null
        }
        if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
            // Could happen if the task hasn't been removed from closing list after it disappeared
            KtProtoLog.w(
                WM_SHELL_DESKTOP_MODE,
                "DesktopTasksController: the task with taskId=%d is already closing!",
                task.taskId
            )
        }
        return wct
    }

    private fun addMoveToDesktopChanges(
+8 −0
Original line number Diff line number Diff line
@@ -122,6 +122,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
            mDesktopModeTaskRepository.ifPresent(repository -> {
                repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
                repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
                if (repository.removeClosingTask(taskInfo.taskId)) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                            "Removing closing freeform task: #%d", taskInfo.taskId);
                }
                if (repository.removeActiveTask(taskInfo.taskId)) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                            "Removing active freeform task: #%d", taskInfo.taskId);
@@ -150,6 +154,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                                "Adding active freeform task: #%d", taskInfo.taskId);
                    }
                } else if (repository.isClosingTask(taskInfo.taskId)
                        && repository.removeClosingTask(taskInfo.taskId)) {
                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                            "Removing closing freeform task: #%d", taskInfo.taskId);
                }
                repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
                        taskInfo.isVisible);
+1 −1
Original line number Diff line number Diff line
@@ -435,7 +435,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                            SplitScreenController.EXIT_REASON_DESKTOP_MODE);
                } else {
                    WindowContainerTransaction wct = new WindowContainerTransaction();
                    mDesktopTasksController.onDesktopWindowClose(wct, mTaskId);
                    mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId);
                    mTaskOperations.closeTask(mTaskToken, wct);
                }
            } else if (id == R.id.back_button) {
+46 −14
Original line number Diff line number Diff line
@@ -119,54 +119,86 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() {
    }

    @Test
    fun isOnlyActiveTask_noActiveTasks() {
    fun isOnlyActiveNonClosingTask_noActiveNonClosingTasks() {
        // Not an active task
        assertThat(repo.isOnlyActiveTask(1)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse()
        assertThat(repo.isClosingTask(1)).isFalse()
    }

    @Test
    fun isOnlyActiveTask_singleActiveTask() {
    fun isOnlyActiveNonClosingTask_singleActiveNonClosingTask() {
        repo.addActiveTask(DEFAULT_DISPLAY, 1)
        // The only active task
        assertThat(repo.isActiveTask(1)).isTrue()
        assertThat(repo.isOnlyActiveTask(1)).isTrue()
        assertThat(repo.isClosingTask(1)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isTrue()
        // Not an active task
        assertThat(repo.isActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveTask(99)).isFalse()
        assertThat(repo.isClosingTask(99)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse()
    }

    @Test
    fun isOnlyActiveTask_multipleActiveTasks() {
    fun isOnlyActiveNonClosingTask_singleActiveClosingTask() {
        repo.addActiveTask(DEFAULT_DISPLAY, 1)
        repo.addClosingTask(DEFAULT_DISPLAY, 1)
        // The active task that's closing
        assertThat(repo.isActiveTask(1)).isTrue()
        assertThat(repo.isClosingTask(1)).isTrue()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse()
        // Not an active task
        assertThat(repo.isActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse()
    }

    @Test
    fun isOnlyActiveNonClosingTask_singleActiveMinimizedTask() {
        repo.addActiveTask(DEFAULT_DISPLAY, 1)
        repo.minimizeTask(DEFAULT_DISPLAY, 1)
        // The active task that's closing
        assertThat(repo.isActiveTask(1)).isTrue()
        assertThat(repo.isMinimizedTask(1)).isTrue()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse()
        // Not an active task
        assertThat(repo.isActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse()
    }

    @Test
    fun isOnlyActiveNonClosingTask_multipleActiveNonClosingTasks() {
        repo.addActiveTask(DEFAULT_DISPLAY, 1)
        repo.addActiveTask(DEFAULT_DISPLAY, 2)
        // Not the only task
        assertThat(repo.isActiveTask(1)).isTrue()
        assertThat(repo.isOnlyActiveTask(1)).isFalse()
        assertThat(repo.isClosingTask(1)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse()
        // Not the only task
        assertThat(repo.isActiveTask(2)).isTrue()
        assertThat(repo.isOnlyActiveTask(2)).isFalse()
        assertThat(repo.isClosingTask(2)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(2)).isFalse()
        // Not an active task
        assertThat(repo.isActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveTask(99)).isFalse()
        assertThat(repo.isClosingTask(99)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse()
    }

    @Test
    fun isOnlyActiveTask_multipleDisplays() {
    fun isOnlyActiveNonClosingTask_multipleDisplays() {
        repo.addActiveTask(DEFAULT_DISPLAY, 1)
        repo.addActiveTask(DEFAULT_DISPLAY, 2)
        repo.addActiveTask(SECOND_DISPLAY, 3)
        // Not the only task on DEFAULT_DISPLAY
        assertThat(repo.isActiveTask(1)).isTrue()
        assertThat(repo.isOnlyActiveTask(1)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(1)).isFalse()
        // Not the only task on DEFAULT_DISPLAY
        assertThat(repo.isActiveTask(2)).isTrue()
        assertThat(repo.isOnlyActiveTask(2)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(2)).isFalse()
        // The only active task on SECOND_DISPLAY
        assertThat(repo.isActiveTask(3)).isTrue()
        assertThat(repo.isOnlyActiveTask(3)).isTrue()
        assertThat(repo.isOnlyActiveNonClosingTask(3)).isTrue()
        // Not an active task
        assertThat(repo.isActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveTask(99)).isFalse()
        assertThat(repo.isOnlyActiveNonClosingTask(99)).isFalse()
    }

    @Test
Loading