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

Commit b470f9a6 authored by Jorge Gil's avatar Jorge Gil Committed by Android (Google) Code Review
Browse files

Merge changes I1f9a8e93,I74ec2bf3 into main

* changes:
  Exit immersive state on rotation
  Restore to pre-immersive bounds on immersive exit
parents 96576086 d40c740f
Loading
Loading
Loading
Loading
+54 −17
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.animation.DecelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.window.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
import android.window.TransitionInfo
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction
@@ -102,10 +103,8 @@ class DesktopFullImmersiveTransitionHandler(
            return
            return
        }
        }


        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
        val destinationBounds = calculateMaximizeBounds(displayLayout, taskInfo)
        val wct = WindowContainerTransaction().apply {
        val wct = WindowContainerTransaction().apply {
            setBounds(taskInfo.token, destinationBounds)
            setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
        }
        }
        logV("Moving task ${taskInfo.taskId} out of immersive mode")
        logV("Moving task ${taskInfo.taskId} out of immersive mode")
        val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
        val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
@@ -145,11 +144,10 @@ class DesktopFullImmersiveTransitionHandler(
        displayId: Int
        displayId: Int
    ): ((IBinder) -> Unit)? {
    ): ((IBinder) -> Unit)? {
        if (!Flags.enableFullyImmersiveInDesktop()) return null
        if (!Flags.enableFullyImmersiveInDesktop()) return null
        val displayLayout = displayController.getDisplayLayout(displayId) ?: return null
        val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null
        val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) ?: return null
        val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null
        val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return null
        logV("Appending immersive exit for task: $immersiveTask in display: $displayId")
        logV("Appending immersive exit for task: $immersiveTask in display: $displayId")
        wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo))
        wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
        return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) }
        return { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) }
    }
    }


@@ -168,8 +166,7 @@ class DesktopFullImmersiveTransitionHandler(
        if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
        if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
            // A full immersive task is being minimized, make sure the immersive state is broken
            // A full immersive task is being minimized, make sure the immersive state is broken
            // (i.e. resize back to max bounds).
            // (i.e. resize back to max bounds).
            displayController.getDisplayLayout(taskInfo.displayId)?.let { displayLayout ->
            wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo))
                wct.setBounds(taskInfo.token, calculateMaximizeBounds(displayLayout, taskInfo))
            logV("Appending immersive exit for task: ${taskInfo.taskId}")
            logV("Appending immersive exit for task: ${taskInfo.taskId}")
            return { transition ->
            return { transition ->
                addPendingImmersiveExit(
                addPendingImmersiveExit(
@@ -179,7 +176,6 @@ class DesktopFullImmersiveTransitionHandler(
                )
                )
            }
            }
        }
        }
        }
        return null
        return null
    }
    }


@@ -302,14 +298,19 @@ class DesktopFullImmersiveTransitionHandler(
                        taskId = pendingExit.taskId,
                        taskId = pendingExit.taskId,
                        immersive = false
                        immersive = false
                    )
                    )
                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                        desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId)
                    }
                }
                }
            }
            }
            return
            return
        }
        }


        // Check if this is a direct immersive enter/exit transition.
        // Check if this is a direct immersive enter/exit transition.
        val state = this.state ?: return
        if (transition == state?.transition) {
        if (transition == state.transition) {
            val state = requireState()
            val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId }
                .startAbsBounds
            logV("Direct move for task ${state.taskId} in ${state.direction} direction verified")
            logV("Direct move for task ${state.taskId} in ${state.direction} direction verified")
            when (state.direction) {
            when (state.direction) {
                Direction.ENTER -> {
                Direction.ENTER -> {
@@ -318,6 +319,9 @@ class DesktopFullImmersiveTransitionHandler(
                        taskId = state.taskId,
                        taskId = state.taskId,
                        immersive = true
                        immersive = true
                    )
                    )
                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                        desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds)
                    }
                }
                }
                Direction.EXIT -> {
                Direction.EXIT -> {
                    desktopRepository.setTaskInFullImmersiveState(
                    desktopRepository.setTaskInFullImmersiveState(
@@ -325,15 +329,48 @@ class DesktopFullImmersiveTransitionHandler(
                        taskId = state.taskId,
                        taskId = state.taskId,
                        immersive = false
                        immersive = false
                    )
                    )
                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
                        desktopRepository.removeBoundsBeforeFullImmersive(state.taskId)
                    }
                    }
                }
                }
            }
            }
            return
        }

        // Check if this is an untracked exit transition, like display rotation.
        info.changes
            .filter { c -> c.taskInfo != null }
            .filter { c -> desktopRepository.isTaskInFullImmersiveState(c.taskInfo!!.taskId) }
            .filter { c -> c.startRotation != c.endRotation }
            .forEach { c ->
                logV("Detected immersive exit due to rotation for task: ${c.taskInfo!!.taskId}")
                desktopRepository.setTaskInFullImmersiveState(
                    displayId = c.taskInfo!!.displayId,
                    taskId = c.taskInfo!!.taskId,
                    immersive = false
                )
            }
    }
    }


    private fun clearState() {
    private fun clearState() {
        state = null
        state = null
    }
    }


    private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect {
        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
            ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}")
        return if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
            desktopRepository.removeBoundsBeforeFullImmersive(taskInfo.taskId)
                ?: if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
                    calculateInitialBounds(displayLayout, taskInfo)
                } else {
                    calculateDefaultDesktopTaskBounds(displayLayout)
                }
        } else {
            return calculateMaximizeBounds(displayLayout, taskInfo)
        }
    }

    private fun requireState(): TransitionState =
    private fun requireState(): TransitionState =
        state ?: error("Expected non-null transition state")
        state ?: error("Expected non-null transition state")


+17 −0
Original line number Original line Diff line number Diff line
@@ -36,6 +36,23 @@ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float =
val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int =
val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int =
    SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)
    SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25)


/**
 * Calculates the initial bounds to enter desktop, centered on the display.
 */
fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
    // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
    val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
    val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
    val heightOffset = (displayLayout.height() - desiredHeight) / 2
    val widthOffset = (displayLayout.width() - desiredWidth) / 2
    return Rect(
        widthOffset,
        heightOffset,
        desiredWidth + widthOffset,
        desiredHeight + heightOffset
    )
}

/**
/**
 * Calculates the initial bounds required for an application to fill a scale of the display bounds
 * Calculates the initial bounds required for an application to fill a scale of the display bounds
 * without any letterboxing. This is done by taking into account the applications fullscreen size,
 * without any letterboxing. This is done by taking into account the applications fullscreen size,
+12 −0
Original line number Original line Diff line number Diff line
@@ -102,6 +102,9 @@ class DesktopRepository (
    /* Tracks last bounds of task before toggled to stable bounds. */
    /* Tracks last bounds of task before toggled to stable bounds. */
    private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
    private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()


    /* Tracks last bounds of task before toggled to immersive state. */
    private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()

    private var desktopGestureExclusionListener: Consumer<Region>? = null
    private var desktopGestureExclusionListener: Consumer<Region>? = null
    private var desktopGestureExclusionExecutor: Executor? = null
    private var desktopGestureExclusionExecutor: Executor? = null


@@ -414,6 +417,7 @@ class DesktopRepository (
        logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
        logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId)
        desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
        desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
        boundsBeforeMaximizeByTaskId.remove(taskId)
        boundsBeforeMaximizeByTaskId.remove(taskId)
        boundsBeforeFullImmersiveByTaskId.remove(taskId)
        logD("Remaining freeform tasks: %s",
        logD("Remaining freeform tasks: %s",
            desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
            desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString())
        // Remove task from unminimized task if it is minimized.
        // Remove task from unminimized task if it is minimized.
@@ -472,6 +476,14 @@ class DesktopRepository (
    fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
    fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
        boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
        boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))


    /** Removes and returns the bounds saved before entering immersive with the given task. */
    fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? =
        boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId)

    /** Saves the bounds of the given task before entering immersive. */
    fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) =
        boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds))

    private fun updatePersistentRepository(displayId: Int) {
    private fun updatePersistentRepository(displayId: Int) {
        // Create a deep copy of the data
        // Create a deep copy of the data
        desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
        desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
+3 −17
Original line number Original line Diff line number Diff line
@@ -751,7 +751,7 @@ class DesktopTasksController(
                if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
                if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
                    destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
                    destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo))
                } else {
                } else {
                    destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
                    destinationBounds.set(calculateDefaultDesktopTaskBounds(displayLayout))
                }
                }
            }
            }
        } else {
        } else {
@@ -920,20 +920,6 @@ class DesktopTasksController(
        }
        }
    }
    }


    private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
        // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
        val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
        val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
        val heightOffset = (displayLayout.height() - desiredHeight) / 2
        val widthOffset = (displayLayout.width() - desiredWidth) / 2
        return Rect(
            widthOffset,
            heightOffset,
            desiredWidth + widthOffset,
            desiredHeight + heightOffset
        )
    }

    private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
    private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()


@@ -1487,7 +1473,7 @@ class DesktopTasksController(
        val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
        val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) {
            calculateInitialBounds(displayLayout, taskInfo)
            calculateInitialBounds(displayLayout, taskInfo)
        } else {
        } else {
            getDefaultDesktopTaskBounds(displayLayout)
            calculateDefaultDesktopTaskBounds(displayLayout)
        }
        }


        if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
        if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) {
@@ -1883,7 +1869,7 @@ class DesktopTasksController(
        when (indicatorType) {
        when (indicatorType) {
            IndicatorType.TO_DESKTOP_INDICATOR -> {
            IndicatorType.TO_DESKTOP_INDICATOR -> {
                // Use default bounds, but with the top-center at the drop point.
                // Use default bounds, but with the top-center at the drop point.
                newWindowBounds.set(getDefaultDesktopTaskBounds(displayLayout))
                newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout))
                newWindowBounds.offsetTo(
                newWindowBounds.offsetTo(
                    dragEvent.x.toInt() - (newWindowBounds.width() / 2),
                    dragEvent.x.toInt() - (newWindowBounds.width() / 2),
                    dragEvent.y.toInt()
                    dragEvent.y.toInt()
+220 −3
Original line number Original line Diff line number Diff line
@@ -16,12 +16,15 @@
package com.android.wm.shell.desktopmode
package com.android.wm.shell.desktopmode


import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
import android.graphics.Rect
import android.os.Binder
import android.os.Binder
import android.os.IBinder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.DEFAULT_DISPLAY
import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TransitionFlags
import android.view.WindowManager.TransitionFlags
@@ -68,6 +71,7 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
    private lateinit var desktopRepository: DesktopRepository
    private lateinit var desktopRepository: DesktopRepository
    @Mock private lateinit var mockDisplayController: DisplayController
    @Mock private lateinit var mockDisplayController: DisplayController
    @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
    @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
    @Mock private lateinit var mockDisplayLayout: DisplayLayout
    private val transactionSupplier = { SurfaceControl.Transaction() }
    private val transactionSupplier = { SurfaceControl.Transaction() }


    private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler
    private lateinit var immersiveHandler: DesktopFullImmersiveTransitionHandler
@@ -78,7 +82,10 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
            context, ShellInit(TestShellExecutor()), mock(), mock()
            context, ShellInit(TestShellExecutor()), mock(), mock()
        )
        )
        whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
        whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
            .thenReturn(DisplayLayout())
            .thenReturn(mockDisplayLayout)
        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation ->
            (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
        }
        immersiveHandler = DesktopFullImmersiveTransitionHandler(
        immersiveHandler = DesktopFullImmersiveTransitionHandler(
            transitions = mockTransitions,
            transitions = mockTransitions,
            desktopRepository = desktopRepository,
            desktopRepository = desktopRepository,
@@ -101,11 +108,49 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
        )
        )


        immersiveHandler.moveTaskToImmersive(task)
        immersiveHandler.moveTaskToImmersive(task)
        immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo())
        immersiveHandler.onTransitionReady(
            transition = mockBinder,
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply {
                        taskInfo = task
                    }
                )
            )
        )


        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue()
        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isTrue()
    }
    }


    @Test
    @EnableFlags(Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE)
    fun enterImmersive_savesPreImmersiveBounds() {
        val task = createFreeformTask()
        val mockBinder = mock(IBinder::class.java)
        whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
            .thenReturn(mockBinder)
        desktopRepository.setTaskInFullImmersiveState(
            displayId = task.displayId,
            taskId = task.taskId,
            immersive = false
        )
        assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNull()

        immersiveHandler.moveTaskToImmersive(task)
        immersiveHandler.onTransitionReady(
            transition = mockBinder,
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply {
                        taskInfo = task
                    }
                )
            )
        )

        assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNotNull()
    }

    @Test
    @Test
    fun exitImmersive_transitionReady_updatesRepository() {
    fun exitImmersive_transitionReady_updatesRepository() {
        val task = createFreeformTask()
        val task = createFreeformTask()
@@ -119,7 +164,69 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
        )
        )


        immersiveHandler.moveTaskToNonImmersive(task)
        immersiveHandler.moveTaskToNonImmersive(task)
        immersiveHandler.onTransitionReady(mockBinder, createTransitionInfo())
        immersiveHandler.onTransitionReady(
            transition = mockBinder,
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply {
                        taskInfo = task
                    }
                )
            )
        )

        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE)
    fun exitImmersive_onTransitionReady_removesBoundsBeforeImmersive() {
        val task = createFreeformTask()
        val mockBinder = mock(IBinder::class.java)
        whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(immersiveHandler)))
            .thenReturn(mockBinder)
        desktopRepository.setTaskInFullImmersiveState(
            displayId = task.displayId,
            taskId = task.taskId,
            immersive = true
        )
        desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600))

        immersiveHandler.moveTaskToNonImmersive(task)
        immersiveHandler.onTransitionReady(
            transition = mockBinder,
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply {
                        taskInfo = task
                    }
                )
            )
        )

        assertThat(desktopRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
    }

    @Test
    fun onTransitionReady_displayRotation_exitsImmersive() {
        val task = createFreeformTask()
        desktopRepository.setTaskInFullImmersiveState(
            displayId = task.displayId,
            taskId = task.taskId,
            immersive = true
        )

        immersiveHandler.onTransitionReady(
            transition = mock(IBinder::class.java),
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply {
                        taskInfo = task
                        setRotation(/* start= */ Surface.ROTATION_0, /* end= */ Surface.ROTATION_90)
                    }
                )
            )
        )


        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
    }
    }
@@ -361,6 +468,103 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
        assertThat(desktopRepository.isTaskInFullImmersiveState(task.taskId)).isFalse()
    }
    }


    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
        Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE
    )
    fun onTransitionReady_pendingExit_removesBoundsBeforeImmersive() {
        val task = createFreeformTask()
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        val wct = WindowContainerTransaction()
        val transition = Binder()
        desktopRepository.setTaskInFullImmersiveState(
            displayId = DEFAULT_DISPLAY,
            taskId = task.taskId,
            immersive = true
        )
        desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600))
        immersiveHandler.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY)

        immersiveHandler.onTransitionReady(
            transition = transition,
            info = createTransitionInfo(
                changes = listOf(
                    TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
                )
            )
        )

        assertThat(desktopRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
    @DisableFlags(Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE)
    fun exitImmersiveIfApplicable_changesBoundsToMaximize() {
        val task = createFreeformTask()
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        val wct = WindowContainerTransaction()
        desktopRepository.setTaskInFullImmersiveState(
            displayId = DEFAULT_DISPLAY,
            taskId = task.taskId,
            immersive = true
        )

        immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task)

        assertThat(
            wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task))
        ).isTrue()
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
        Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE
    )
    fun exitImmersiveIfApplicable_preImmersiveBoundsSaved_changesBoundsToPreImmersiveBounds() {
        val task = createFreeformTask()
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        val wct = WindowContainerTransaction()
        desktopRepository.setTaskInFullImmersiveState(
            displayId = DEFAULT_DISPLAY,
            taskId = task.taskId,
            immersive = true
        )
        val preImmersiveBounds = Rect(100, 100, 500, 500)
        desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds)

        immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task)

        assertThat(
            wct.hasBoundsChange(task.token, preImmersiveBounds)
        ).isTrue()
    }

    @Test
    @EnableFlags(
        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
        Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE,
        Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
    )
    fun exitImmersiveIfApplicable_preImmersiveBoundsNotSaved_changesBoundsToInitialBounds() {
        val task = createFreeformTask()
        whenever(mockShellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        val wct = WindowContainerTransaction()
        desktopRepository.setTaskInFullImmersiveState(
            displayId = DEFAULT_DISPLAY,
            taskId = task.taskId,
            immersive = true
        )

        immersiveHandler.exitImmersiveIfApplicable(wct = wct, taskInfo = task)

        assertThat(
            wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task))
        ).isTrue()
    }

    private fun createTransitionInfo(
    private fun createTransitionInfo(
        @TransitionType type: Int = TRANSIT_CHANGE,
        @TransitionType type: Int = TRANSIT_CHANGE,
        @TransitionFlags flags: Int = 0,
        @TransitionFlags flags: Int = 0,
@@ -374,4 +578,17 @@ class DesktopFullImmersiveTransitionHandlerTest : ShellTestCase() {
            change.key == token.asBinder()
            change.key == token.asBinder()
                    && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
                    && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
        }
        }

    private fun WindowContainerTransaction.hasBoundsChange(
        token: WindowContainerToken,
        bounds: Rect,
    ): Boolean = this.changes.any { change ->
        change.key == token.asBinder()
                && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
                && change.value.configuration.windowConfiguration.bounds == bounds
    }

    companion object {
        private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900)
    }
}
}
Loading