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

Commit e71724cb authored by Orhan Uysal's avatar Orhan Uysal
Browse files

Remove wallpaper to not end up in empty desktop

In some cases, such as back navigation on a task with multiple task
fragments, we are not able to tell that the desktop will close after the
closing of the task on handleRequest. This cl adds a method to create
a transition remove desktop wallpaper if there is a single task left in
desktop and that's being removed in a transition.

Bug: 359899708
Test: atest DesktopTasksTransitionObserverTest
Test: Manually do back navigation on settings, observe that we don't end
up with empty desktop
Flag: EXEMPT Bugfix
Change-Id: Ia96d931a998be2665716f3aaf5fca7476c0aa602

Change-Id: I83c352b9aba7e2db895387a493925f29656d8828
parent b82fcf5c
Loading
Loading
Loading
Loading
+41 −14
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Context
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -36,8 +37,8 @@ import com.android.wm.shell.transition.Transitions

/**
 * A [Transitions.TransitionObserver] that observes shell transitions and updates the
 * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
 * mode and other transitions that originate both within and outside shell.
 * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop mode and
 * other transitions that originate both within and outside shell.
 */
class DesktopTasksTransitionObserver(
    private val context: Context,
@@ -47,6 +48,8 @@ class DesktopTasksTransitionObserver(
    shellInit: ShellInit
) : Transitions.TransitionObserver {

    private var transitionToCloseWallpaper: IBinder? = null

    init {
        if (
            Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context)
@@ -72,6 +75,7 @@ class DesktopTasksTransitionObserver(
            handleBackNavigation(info)
            removeTaskIfNeeded(info)
        }
        removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
    }

    private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -83,13 +87,9 @@ class DesktopTasksTransitionObserver(
            val taskInfo = change.taskInfo
            if (taskInfo == null || taskInfo.taskId == -1) continue

            if (desktopRepository.isActiveTask(taskInfo.taskId)
                && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
            ) {
                desktopRepository.removeFreeformTask(
                    taskInfo.displayId,
                    taskInfo.taskId
                )
            if (desktopRepository.isActiveTask(taskInfo.taskId) &&
                taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) {
                desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
            }
        }
    }
@@ -106,14 +106,32 @@ class DesktopTasksTransitionObserver(

                if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
                    change.mode == TRANSIT_TO_BACK &&
                    taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                ) {
                    taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
                    desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                }
            }
        }
    }

    private fun removeWallpaperOnLastTaskClosingIfNeeded(
        transition: IBinder,
        info: TransitionInfo
    ) {
        for (change in info.changes) {
            val taskInfo = change.taskInfo
            if (taskInfo == null || taskInfo.taskId == -1) {
                continue
            }

            if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 &&
                change.mode == TRANSIT_CLOSE &&
                taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
                desktopRepository.wallpaperActivityToken != null) {
                transitionToCloseWallpaper = transition
            }
        }
    }

    override fun onTransitionStarting(transition: IBinder) {
        // TODO: b/332682201 Update repository state
    }
@@ -124,6 +142,16 @@ class DesktopTasksTransitionObserver(

    override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
        // TODO: b/332682201 Update repository state
        if (transitionToCloseWallpaper == transition) {
            // TODO: b/362469671 - Handle merging the animation when desktop is also closing.
            desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken ->
                transitions.startTransition(
                    TRANSIT_CLOSE,
                    WindowContainerTransaction().removeTask(wallpaperActivityToken),
                    null)
            }
            transitionToCloseWallpaper = null
        }
    }

    private fun updateWallpaperToken(info: TransitionInfo) {
@@ -141,10 +169,9 @@ class DesktopTasksTransitionObserver(
                            // task.
                            shellTaskOrganizer.applyTransaction(
                                WindowContainerTransaction()
                                    .setTaskTrimmableFromRecents(taskInfo.token, false)
                            )
                                    .setTaskTrimmableFromRecents(taskInfo.token, false))
                        }
                        WindowManager.TRANSIT_CLOSE ->
                        TRANSIT_CLOSE ->
                            desktopRepository.wallpaperActivityToken = null
                        else -> {}
                    }
+75 −0
Original line number Diff line number Diff line
@@ -22,26 +22,38 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isA
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
@@ -130,6 +142,27 @@ class DesktopTasksTransitionObserverTest {
        verify(taskRepository).removeFreeformTask(task.displayId, task.taskId)
    }

    @Test
    fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() {
        val mockTransition = Mockito.mock(IBinder::class.java)
        val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
        val wallpaperToken = MockToken().token()
        whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1)
        whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken)

        transitionObserver.onTransitionReady(
            transition = mockTransition,
            info = createCloseTransition(task),
            startTransaction = mock(),
            finishTransaction = mock(),
        )
        transitionObserver.onTransitionFinished(mockTransition, false)

        val wct = getLatestWct(type = TRANSIT_CLOSE)
        assertThat(wct.hierarchyOps).hasSize(1)
        wct.assertRemoveAt(index = 0, wallpaperToken)
    }

    private fun createBackNavigationTransition(
        task: RunningTaskInfo?
    ): TransitionInfo {
@@ -160,6 +193,48 @@ class DesktopTasksTransitionObserverTest {
        }
    }

    private fun createCloseTransition(
        task: RunningTaskInfo?
    ): TransitionInfo {
        return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply {
            addChange(
                Change(mock(), mock()).apply {
                    mode = TRANSIT_CLOSE
                    parent = null
                    taskInfo = task
                    flags = flags
                }
            )
        }
    }

    private fun getLatestWct(
        @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
        handlerClass: Class<out Transitions.TransitionHandler>? = null
    ): WindowContainerTransaction {
        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
        if (handlerClass == null) {
            Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull())
        } else {
            Mockito.verify(transitions)
                .startTransition(eq(type), arg.capture(), isA(handlerClass))
        }
        return arg.value
    }

    private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
        assertIndexInBounds(index)
        val op = hierarchyOps[index]
        assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
        assertThat(op.container).isEqualTo(token.asBinder())
    }

    private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
        assertWithMessage("WCT does not have a hierarchy operation at index $index")
            .that(hierarchyOps.size)
            .isGreaterThan(index)
    }

    private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
        RunningTaskInfo().apply {
            taskId = id