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

Commit 2034639b authored by Jorge Gil's avatar Jorge Gil
Browse files

Exit immersive on new instance launches

Adds missing immersive check during Shell-started new task instance
launches (e.g. from the "New Window" button in the app header). This
ensures that when launching a new instance over an immersive task, the
immersive task exits immersive and is resized back to its previous
bounds.

This change also changes the new instance transition handler to use the
Desktop Mixed handler, so that both the launching change and the
immersive change are animated nicely.

Flag: com.android.window.flags.enable_fully_immersive_in_desktop
Fix: 377812444
Test: With Chrome and multi-instance desktop flag enabled, enter desktop
immersive, open a new window using the "New Window" button in the app
header and verify the immersive window exits and both windows animate.

Change-Id: I27110e3fc1984b513865bdac82b69511825b93e2
parent f9f90962
Loading
Loading
Loading
Loading
+25 −8
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_OPEN
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
@@ -95,7 +96,7 @@ class DesktopMixedTransitionHandler(
    fun startLaunchTransition(
        @WindowManager.TransitionType transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        taskId: Int?,
        minimizingTaskId: Int? = null,
        exitingImmersiveTask: Int? = null,
    ): IBinder {
@@ -216,12 +217,12 @@ class DesktopMixedTransitionHandler(
    ): Boolean {
        // Check if there's also an immersive change during this launch.
        val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
            findDesktopTaskChange(info, exitingTask)
            findTaskChange(info, exitingTask)
        }
        val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
            findDesktopTaskChange(info, minimizingTask)
            findTaskChange(info, minimizingTask)
        }
        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
        val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask)
        if (launchChange == null) {
            check(minimizeChange == null)
            check(immersiveExitChange == null)
@@ -291,7 +292,7 @@ class DesktopMixedTransitionHandler(
    ): Boolean {
        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false

        val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask)
        val minimizeChange = findTaskChange(info, pending.minimizingTask)
        if (minimizeChange == null) {
            logW("Should have minimizing desktop task")
            return false
@@ -417,8 +418,24 @@ class DesktopMixedTransitionHandler(
        }
    }

    private fun findDesktopTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? {
        return info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }
    private fun findTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? =
        info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId }

    private fun findDesktopTaskLaunchChange(
        info: TransitionInfo,
        launchTaskId: Int?
    ): TransitionInfo.Change? {
        return if (launchTaskId != null) {
            // Launching a known task (probably from background or moving to front), so
            // specifically look for it.
            findTaskChange(info, launchTaskId)
        } else {
            // Launching a new task, so the first opening freeform task.
            info.changes.firstOrNull { change ->
                change.mode == TRANSIT_OPEN
                        && change.taskInfo != null && change.taskInfo!!.isFreeform
            }
        }
    }

    private fun WindowContainerTransaction?.merge(
@@ -441,7 +458,7 @@ class DesktopMixedTransitionHandler(
        /** A task is opening or moving to front. */
        data class Launch(
            override val transition: IBinder,
            val launchingTask: Int,
            val launchingTask: Int?,
            val minimizingTask: Int?,
            val exitingImmersiveTask: Int?,
        ) : PendingMixedTransition()
+29 −26
Original line number Diff line number Diff line
@@ -89,7 +89,6 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener
@@ -618,25 +617,18 @@ class DesktopTasksController(
    private fun moveBackgroundTaskToFront(taskId: Int, remoteTransition: RemoteTransition?) {
        logV("moveBackgroundTaskToFront taskId=%s", taskId)
        val wct = WindowContainerTransaction()
        // TODO: b/342378842 - Instead of using default display, support multiple displays
        val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = DEFAULT_DISPLAY,
            excludeTaskId = taskId,
        )
        wct.startTask(
            taskId,
            ActivityOptions.makeBasic().apply {
                launchWindowingMode = WINDOWING_MODE_FREEFORM
            }.toBundle(),
        )
        val transition = startLaunchTransition(
        startLaunchTransition(
            TRANSIT_OPEN,
            wct,
            taskId,
            remoteTransition = remoteTransition
        )
        exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
    }

    /**
@@ -655,47 +647,53 @@ class DesktopTasksController(
        }
        val wct = WindowContainerTransaction()
        wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */)
        val result = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = taskInfo.displayId,
            excludeTaskId = taskInfo.taskId,
        )
        val exitResult = if (result is ExitResult.Exit) { result } else { null }
        val transition = startLaunchTransition(
        startLaunchTransition(
            transitionType = TRANSIT_TO_FRONT,
            wct = wct,
            taskId = taskInfo.taskId,
            exitingImmersiveTask = exitResult?.exitingTask,
            launchingTaskId = taskInfo.taskId,
            remoteTransition = remoteTransition,
            displayId = taskInfo.displayId,
        )
        exitResult?.runOnTransitionStart?.invoke(transition)
    }

    private fun startLaunchTransition(
        transitionType: Int,
        wct: WindowContainerTransaction,
        taskId: Int,
        exitingImmersiveTask: Int? = null,
        launchingTaskId: Int?,
        remoteTransition: RemoteTransition? = null,
        displayId: Int = DEFAULT_DISPLAY,
    ): IBinder {
        val taskIdToMinimize = addAndGetMinimizeChanges(displayId, wct, taskId)
        val taskIdToMinimize = if (launchingTaskId != null) {
            addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId)
        } else {
            logW("Starting desktop task launch without checking the task-limit")
            // TODO(b/378920066): This currently does not respect the desktop window limit.
            //  It's possible that |launchingTaskId| is null when launching using an intent, and
            //  the task-limit should be respected then too.
            null
        }
        val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable(
            wct = wct,
            displayId = displayId,
            excludeTaskId = launchingTaskId,
        )
        if (remoteTransition == null) {
            val t = desktopMixedTransitionHandler.startLaunchTransition(
                transitionType = transitionType,
                wct = wct,
                taskId = taskId,
                taskId = launchingTaskId,
                minimizingTaskId = taskIdToMinimize,
                exitingImmersiveTask = exitingImmersiveTask,
                exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask,
            )
            taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
            exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
            return t
        }
        if (taskIdToMinimize == null) {
            val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
            val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
            remoteTransitionHandler.setTransition(t)
            exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
            return t
        }
        val remoteTransitionHandler =
@@ -704,6 +702,7 @@ class DesktopTasksController(
        val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
        remoteTransitionHandler.setTransition(t)
        taskIdToMinimize.let { addPendingMinimizeTransition(t, it) }
        exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
        return t
    }

@@ -1472,10 +1471,14 @@ class DesktopTasksController(
                )
            }
            WINDOWING_MODE_FREEFORM -> {
                // TODO(b/336289597): This currently does not respect the desktop window limit.
                val wct = WindowContainerTransaction()
                wct.sendPendingIntent(launchIntent, fillIn, options.toBundle())
                transitions.startTransition(TRANSIT_OPEN, wct, null)
                startLaunchTransition(
                    transitionType = TRANSIT_OPEN,
                    wct = wct,
                    launchingTaskId = null,
                    displayId = callingTaskInfo.displayId
                )
            }
        }
    }
+74 −1
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TransitionType
@@ -47,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMix
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -490,6 +493,72 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
        assertFalse("Should not start animation without launching desktop task", started)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun startLaunchTransition_unknownLaunchingTask_animates() {
        val wct = WindowContainerTransaction()
        val task = createTask(WINDOWING_MODE_FREEFORM)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)
        whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any()))
            .thenReturn(mock())

        mixedHandler.startLaunchTransition(
            transitionType = TRANSIT_OPEN,
            wct = wct,
            taskId = null,
        )

        val started = mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(createChange(task, mode = TRANSIT_OPEN))
            ),
            StubTransaction(),
            StubTransaction(),
        ) { }

        assertThat(started).isEqualTo(true)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    fun startLaunchTransition_unknownLaunchingTaskOverImmersive_animatesImmersiveChange() {
        val wct = WindowContainerTransaction()
        val immersiveTask = createTask(WINDOWING_MODE_FREEFORM)
        val openingTask = createTask(WINDOWING_MODE_FREEFORM)
        val transition = Binder()
        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
            .thenReturn(transition)
        whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any()))
            .thenReturn(mock())

        mixedHandler.startLaunchTransition(
            transitionType = TRANSIT_OPEN,
            wct = wct,
            taskId = null,
            exitingImmersiveTask = immersiveTask.taskId,
        )

        val immersiveChange = createChange(immersiveTask, mode = TRANSIT_CHANGE)
        val openingChange = createChange(openingTask, mode = TRANSIT_OPEN)
        val started = mixedHandler.startAnimation(
            transition,
            createTransitionInfo(
                TRANSIT_OPEN,
                listOf(immersiveChange, openingChange)
            ),
            StubTransaction(),
            StubTransaction(),
        ) { }

        assertThat(started).isEqualTo(true)
        verify(desktopImmersiveController)
            .animateResizeChange(eq(immersiveChange), any(), any(), any())
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
    fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
@@ -712,9 +781,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
        changes.forEach { change -> addChange(change) }
    }

    private fun createChange(task: RunningTaskInfo): TransitionInfo.Change =
    private fun createChange(
        task: RunningTaskInfo,
        @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE
    ): TransitionInfo.Change =
        TransitionInfo.Change(task.token, SurfaceControl()).apply {
            taskInfo = task
            setMode(mode)
        }

    private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+49 −11
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
@@ -167,6 +168,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
@@ -294,10 +296,10 @@ class DesktopTasksControllerTest : ShellTestCase() {
    whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>()))
      .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
      .thenReturn(ExitResult.NoExit)
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
      .thenReturn(DesktopImmersiveController.ExitResult.NoExit)
      .thenReturn(ExitResult.NoExit)

    controller = createController()
    controller.setSplitScreenController(splitScreenController)
@@ -1833,7 +1835,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
      .thenReturn(transition)
    whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task)))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = task.taskId,
        runOnTransitionStart = runOnTransit,
      ))
@@ -3214,13 +3217,43 @@ class DesktopTasksControllerTest : ShellTestCase() {
  fun newWindow_fromFreeformAddsNewWindow() {
    setUpLandscapeDisplay()
    val task = setUpFreeformTask()
    val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
    val wctCaptor = argumentCaptor<WindowContainerTransaction>()
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
      .thenReturn(ExitResult.NoExit)
    whenever(desktopMixedTransitionHandler
      .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
      .thenReturn(transition)

    runOpenNewWindow(task)
    verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
    assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)

    verify(desktopMixedTransitionHandler)
      .startLaunchTransition(anyInt(), wctCaptor.capture(), anyOrNull(), anyOrNull(), anyOrNull())
    assertThat(ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions)
      .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
  }

  @Test
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
  fun newWindow_fromFreeform_exitsImmersiveIfNeeded() {
    setUpLandscapeDisplay()
    val immersiveTask = setUpFreeformTask()
    val task = setUpFreeformTask()
    val runOnStart = RunOnStartTransitionCallback()
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull()))
      .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart))
    whenever(desktopMixedTransitionHandler
      .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
      .thenReturn(transition)

    runOpenNewWindow(task)

    runOnStart.assertOnlyInvocation(transition)
  }

  private fun runOpenNewWindow(task: RunningTaskInfo) {
    markTaskVisible(task)
    task.baseActivity = mock(ComponentName::class.java)
@@ -3314,7 +3347,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
      .thenReturn(transition)
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = immersiveTask.taskId,
        runOnTransitionStart = runOnStartTransit,
      ))
@@ -3719,7 +3753,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = 5,
        runOnTransitionStart = runOnStartTransit,
      ))
@@ -3740,7 +3775,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(wct, task.displayId, task.taskId))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = 5,
        runOnTransitionStart = runOnStartTransit,
      ))
@@ -3760,7 +3796,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = 5,
        runOnTransitionStart = runOnStartTransit,
      ))
@@ -3782,7 +3819,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val transition = Binder()
    whenever(mMockDesktopImmersiveController
      .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)))
      .thenReturn(DesktopImmersiveController.ExitResult.Exit(
      .thenReturn(
        ExitResult.Exit(
        exitingTask = 5,
        runOnTransitionStart = runOnStartTransit,
      ))