diff options
28 files changed, 7576 insertions, 6977 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index d83109a1a986..5e0428bab467 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -18,7 +18,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp tests/ tools/ bpfmt = -d -ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode +ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt index 9b4cc17e19d9..db00f41f723b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt @@ -64,7 +64,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { context, testExecutor, testExecutor, - transactionSupplier + transactionSupplier, ) } @@ -81,11 +81,11 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = WindowManager.TRANSIT_OPEN, - task = createTask(WINDOWING_MODE_FREEFORM) + task = createTask(WINDOWING_MODE_FREEFORM), ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate open transition", animates) @@ -99,7 +99,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate fullscreen task close transition", animates) @@ -113,11 +113,11 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( changeMode = WindowManager.TRANSIT_OPEN, - task = createTask(WINDOWING_MODE_FREEFORM) + task = createTask(WINDOWING_MODE_FREEFORM), ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate opening freeform task close transition", animates) @@ -131,7 +131,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertTrue("Should animate closing freeform task close transition", animates) @@ -140,7 +140,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { private fun createTransitionInfo( type: Int = WindowManager.TRANSIT_CLOSE, changeMode: Int = WindowManager.TRANSIT_CLOSE, - task: RunningTaskInfo + task: RunningTaskInfo, ): TransitionInfo = TransitionInfo(type, 0 /* flags */).apply { addChange( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt index 4cc641cd1d81..ecad5217b87f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt @@ -36,7 +36,6 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.window.flags.Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE -import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor @@ -47,6 +46,7 @@ import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import junit.framework.Assert.assertEquals @@ -129,16 +129,22 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { persistentRepository, repositoryInitializer, testScope, - userManager + userManager, ) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( - Desktop.getDefaultInstance() - ) + whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }) + .thenReturn(Desktop.getDefaultInstance()) - handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, userRepositories) + handler = + DesktopActivityOrientationChangeHandler( + context, + shellInit, + shellTaskOrganizer, + taskStackListener, + resizeTransitionHandler, + userRepositories, + ) shellInit.init() } @@ -161,19 +167,28 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false) clearInvocations(shellInit) - handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, userRepositories) + handler = + DesktopActivityOrientationChangeHandler( + context, + shellInit, + shellTaskOrganizer, + taskStackListener, + resizeTransitionHandler, + userRepositories, + ) - verify(shellInit, never()).addInitCallback(any(), - any<DesktopActivityOrientationChangeHandler>()) + verify(shellInit, never()) + .addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>()) } @Test fun handleActivityOrientationChange_resizeable_doNothing() { val task = setUpFreeformTask() - taskStackListener.onActivityRequestedOrientationChanged(task.taskId, - SCREEN_ORIENTATION_LANDSCAPE) + taskStackListener.onActivityRequestedOrientationChanged( + task.taskId, + SCREEN_ORIENTATION_LANDSCAPE, + ) verify(resizeTransitionHandler, never()).startTransition(any(), any()) } @@ -189,8 +204,10 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true) runningTasks.add(task) - taskStackListener.onActivityRequestedOrientationChanged(task.taskId, - SCREEN_ORIENTATION_LANDSCAPE) + taskStackListener.onActivityRequestedOrientationChanged( + task.taskId, + SCREEN_ORIENTATION_LANDSCAPE, + ) verify(resizeTransitionHandler, never()).startTransition(any(), any()) } @@ -198,8 +215,11 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() { val task = setUpFreeformTask(isResizeable = false) - val newTask = setUpFreeformTask(isResizeable = false, - orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT) + val newTask = + setUpFreeformTask( + isResizeable = false, + orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT, + ) handler.handleActivityOrientationChange(task, newTask) @@ -211,8 +231,10 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { val task = setUpFreeformTask(isResizeable = false) userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false) - taskStackListener.onActivityRequestedOrientationChanged(task.taskId, - SCREEN_ORIENTATION_LANDSCAPE) + taskStackListener.onActivityRequestedOrientationChanged( + task.taskId, + SCREEN_ORIENTATION_LANDSCAPE, + ) verify(resizeTransitionHandler, never()).startTransition(any(), any()) } @@ -221,8 +243,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() { val task = setUpFreeformTask(isResizeable = false) val oldBounds = task.configuration.windowConfiguration.bounds - val newTask = setUpFreeformTask(isResizeable = false, - orientation = SCREEN_ORIENTATION_LANDSCAPE) + val newTask = + setUpFreeformTask(isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE) handler.handleActivityOrientationChange(task, newTask) @@ -242,9 +264,12 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() { val oldBounds = Rect(0, 0, 500, 200) - val task = setUpFreeformTask( - isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE, bounds = oldBounds - ) + val task = + setUpFreeformTask( + isResizeable = false, + orientation = SCREEN_ORIENTATION_LANDSCAPE, + bounds = oldBounds, + ) val newTask = setUpFreeformTask(isResizeable = false, bounds = oldBounds) handler.handleActivityOrientationChange(task, newTask) @@ -266,7 +291,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { displayId: Int = DEFAULT_DISPLAY, isResizeable: Boolean = true, orientation: Int = SCREEN_ORIENTATION_PORTRAIT, - bounds: Rect? = Rect(0, 0, 200, 500) + bounds: Rect? = Rect(0, 0, 200, 500), ): RunningTaskInfo { val task = createFreeformTask(displayId, bounds) val activityInfo = ActivityInfo() @@ -291,4 +316,4 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt index 6df8d6fd7717..d14c6402982d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt @@ -56,11 +56,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { handler = - DesktopBackNavigationTransitionHandler( - testExecutor, - testExecutor, - displayController - ) + DesktopBackNavigationTransitionHandler(testExecutor, testExecutor, displayController) whenever(displayController.getDisplayContext(any())).thenReturn(mContext) } @@ -75,13 +71,13 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { handler.startAnimation( transition = mock(), info = - createTransitionInfo( - type = WindowManager.TRANSIT_OPEN, - task = createTask(WINDOWING_MODE_FREEFORM) - ), + createTransitionInfo( + type = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM), + ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate open transition", animates) @@ -95,7 +91,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate fullscreen task to back transition", animates) @@ -107,13 +103,13 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { handler.startAnimation( transition = mock(), info = - createTransitionInfo( - changeMode = WindowManager.TRANSIT_OPEN, - task = createTask(WINDOWING_MODE_FREEFORM) - ), + createTransitionInfo( + changeMode = WindowManager.TRANSIT_OPEN, + task = createTask(WINDOWING_MODE_FREEFORM), + ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertFalse("Should not animate opening freeform task to back transition", animates) @@ -127,7 +123,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertTrue("Should animate going to back freeform task close transition", animates) @@ -138,22 +134,24 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { val animates = handler.startAnimation( transition = mock(), - info = createTransitionInfo( - type = TRANSIT_CLOSE, - changeMode = TRANSIT_CLOSE, - task = createTask(WINDOWING_MODE_FREEFORM) - ), + info = + createTransitionInfo( + type = TRANSIT_CLOSE, + changeMode = TRANSIT_CLOSE, + task = createTask(WINDOWING_MODE_FREEFORM), + ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) assertTrue("Should animate going to back freeform task close transition", animates) } + private fun createTransitionInfo( type: Int = WindowManager.TRANSIT_TO_BACK, changeMode: Int = WindowManager.TRANSIT_TO_BACK, - task: RunningTaskInfo + task: RunningTaskInfo, ): TransitionInfo = TransitionInfo(type, 0 /* flags */).apply { addChange( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index fea82365c1a0..6a3717427e93 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -63,109 +63,115 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class DesktopDisplayEventHandlerTest : ShellTestCase() { - @Mock lateinit var testExecutor: ShellExecutor - @Mock lateinit var transitions: Transitions - @Mock lateinit var displayController: DisplayController - @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer - @Mock private lateinit var mockWindowManager: IWindowManager + @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var transitions: Transitions + @Mock lateinit var displayController: DisplayController + @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var mockWindowManager: IWindowManager - private lateinit var shellInit: ShellInit - private lateinit var handler: DesktopDisplayEventHandler + private lateinit var shellInit: ShellInit + private lateinit var handler: DesktopDisplayEventHandler - @Before - fun setUp() { - shellInit = spy(ShellInit(testExecutor)) - whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) - handler = - DesktopDisplayEventHandler( - context, - shellInit, - transitions, - displayController, - rootTaskDisplayAreaOrganizer, - mockWindowManager, - ) - shellInit.init() - } + @Before + fun setUp() { + shellInit = spy(ShellInit(testExecutor)) + whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } + val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + handler = + DesktopDisplayEventHandler( + context, + shellInit, + transitions, + displayController, + rootTaskDisplayAreaOrganizer, + mockWindowManager, + ) + shellInit.init() + } - private fun testDisplayWindowingModeSwitch( - defaultWindowingMode: Int, - extendedDisplayEnabled: Boolean, - expectTransition: Boolean - ) { - val externalDisplayId = 100 - val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java) - verify(displayController).addDisplayWindowListener(captor.capture()) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode - whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } - val settingsSession = ExtendedDisplaySettingsSession( - context.contentResolver, if (extendedDisplayEnabled) 1 else 0) + private fun testDisplayWindowingModeSwitch( + defaultWindowingMode: Int, + extendedDisplayEnabled: Boolean, + expectTransition: Boolean, + ) { + val externalDisplayId = 100 + val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java) + verify(displayController).addDisplayWindowListener(captor.capture()) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } + val settingsSession = + ExtendedDisplaySettingsSession( + context.contentResolver, + if (extendedDisplayEnabled) 1 else 0, + ) - settingsSession.use { - // The external display connected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) - captor.value.onDisplayAdded(externalDisplayId) - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - // The external display disconnected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY)) - captor.value.onDisplayRemoved(externalDisplayId) + settingsSession.use { + // The external display connected. + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) + captor.value.onDisplayAdded(externalDisplayId) + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + // The external display disconnected. + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + captor.value.onDisplayRemoved(externalDisplayId) - if (expectTransition) { - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode) - .isEqualTo(defaultWindowingMode) - } else { - verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) - } + if (expectTransition) { + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(2)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode) + .isEqualTo(defaultWindowingMode) + } else { + verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) + } + } } - } - @Test - fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, - extendedDisplayEnabled = false, - expectTransition = false - ) - } + @Test + fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = false, + expectTransition = false, + ) + } - @Test - fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, - extendedDisplayEnabled = true, - expectTransition = true - ) - } + @Test + fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + extendedDisplayEnabled = true, + expectTransition = true, + ) + } - @Test - fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() { - testDisplayWindowingModeSwitch( - defaultWindowingMode = WINDOWING_MODE_FREEFORM, - extendedDisplayEnabled = true, - expectTransition = false - ) - } + @Test + fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() { + testDisplayWindowingModeSwitch( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + extendedDisplayEnabled = true, + expectTransition = false, + ) + } - private class ExtendedDisplaySettingsSession( - private val contentResolver: ContentResolver, - private val overrideValue: Int - ) : AutoCloseable { - private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS - private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) + private class ExtendedDisplaySettingsSession( + private val contentResolver: ContentResolver, + private val overrideValue: Int, + ) : AutoCloseable { + private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS + private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) - init { Settings.Global.putInt(contentResolver, settingName, overrideValue) } + init { + Settings.Global.putInt(contentResolver, settingName, overrideValue) + } - override fun close() { - Settings.Global.putInt(contentResolver, settingName, initialValue) + override fun close() { + Settings.Global.putInt(contentResolver, settingName, initialValue) + } } - } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index b87f20023796..47d133b974e6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -88,9 +88,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @Before fun setUp() { - userRepositories = DesktopUserRepositories( - context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock(), mock() - ) + userRepositories = + DesktopUserRepositories( + context, + ShellInit(TestShellExecutor()), + mock(), + mock(), + mock(), + mock(), + mock(), + ) whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY)) .thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation -> @@ -98,15 +105,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() { } whenever(mockDisplayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(mockDisplayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) - controller = DesktopImmersiveController( - shellInit = mock(), - transitions = mockTransitions, - desktopUserRepositories = userRepositories, - displayController = mockDisplayController, - shellTaskOrganizer = mockShellTaskOrganizer, - shellCommandHandler = mock(), - transactionSupplier = transactionSupplier, - ) + controller = + DesktopImmersiveController( + shellInit = mock(), + transitions = mockTransitions, + desktopUserRepositories = userRepositories, + displayController = mockDisplayController, + shellTaskOrganizer = mockShellTaskOrganizer, + shellCommandHandler = mock(), + transactionSupplier = transactionSupplier, + ) desktopRepository = userRepositories.current } @@ -119,15 +127,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = false + immersive = false, ) controller.moveTaskToImmersive(task) controller.onTransitionReady( transition = mockBinder, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -145,16 +151,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = false + immersive = false, ) assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNull() controller.moveTaskToImmersive(task) controller.onTransitionReady( transition = mockBinder, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -171,15 +175,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = true + immersive = true, ) controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -197,16 +199,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = true + immersive = true, ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -220,16 +220,23 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = true + immersive = true, ) controller.onTransitionReady( transition = mock(IBinder::class.java), - info = createTransitionInfo( - changes = listOf(createChange(task).apply { - setRotation(/* start= */ Surface.ROTATION_0, /* end= */ Surface.ROTATION_90) - }) - ), + info = + createTransitionInfo( + changes = + listOf( + createChange(task).apply { + setRotation( + /* start= */ Surface.ROTATION_0, + /* end= */ Surface.ROTATION_90, + ) + } + ) + ), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -247,8 +254,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.moveTaskToImmersive(task) controller.moveTaskToImmersive(task) - verify(mockTransitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) + verify(mockTransitions, times(1)).startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) } @Test @@ -261,8 +267,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.moveTaskToNonImmersive(task, USER_INTERACTION) - verify(mockTransitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) + verify(mockTransitions, times(1)).startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) } @Test @@ -275,7 +280,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) @@ -284,7 +289,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transition = transition, taskId = task.taskId, direction = Direction.EXIT, - animate = false + animate = false, ) } @@ -298,7 +303,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = false + immersive = false, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) @@ -307,7 +312,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transition = transition, taskId = task.taskId, direction = Direction.EXIT, - animate = false + animate = false, ) } @@ -321,7 +326,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) @@ -339,7 +344,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = false + immersive = false, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) @@ -357,21 +362,25 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) - controller.exitImmersiveIfApplicable( - wct = wct, - displayId = DEFAULT_DISPLAY, - excludeTaskId = task.taskId, - reason = USER_INTERACTION, - ).asExit()?.runOnTransitionStart?.invoke(transition) + controller + .exitImmersiveIfApplicable( + wct = wct, + displayId = DEFAULT_DISPLAY, + excludeTaskId = task.taskId, + reason = USER_INTERACTION, + ) + .asExit() + ?.runOnTransitionStart + ?.invoke(transition) assertTransitionNotPending( transition = transition, taskId = task.taskId, animate = false, - direction = Direction.EXIT + direction = Direction.EXIT, ) } @@ -384,7 +393,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) @@ -401,7 +410,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = false + immersive = false, ) controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) @@ -419,17 +428,20 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) - controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) - .asExit()?.runOnTransitionStart?.invoke(transition) + controller + .exitImmersiveIfApplicable(wct, task, USER_INTERACTION) + .asExit() + ?.runOnTransitionStart + ?.invoke(transition) assertTransitionPending( transition = transition, taskId = task.taskId, direction = Direction.EXIT, - animate = false + animate = false, ) } @@ -442,7 +454,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = false + immersive = false, ) val result = controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) @@ -459,11 +471,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = false + immersive = false, ) - val result = controller.exitImmersiveIfApplicable( - wct, task.displayId, excludeTaskId = null, USER_INTERACTION) + val result = + controller.exitImmersiveIfApplicable( + wct, + task.displayId, + excludeTaskId = null, + USER_INTERACTION, + ) assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit) } @@ -478,15 +495,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -496,7 +511,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transition = transition, taskId = task.taskId, direction = Direction.EXIT, - animate = false + animate = false, ) } @@ -511,15 +526,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -530,13 +543,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transition = transition, taskId = task.taskId, animate = false, - direction = Direction.EXIT + direction = Direction.EXIT, ) assertTransitionNotPending( transition = mergedToTransition, taskId = task.taskId, animate = false, - direction = Direction.EXIT + direction = Direction.EXIT, ) } @@ -550,15 +563,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -569,7 +580,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, - Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE + Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, ) fun onTransitionReady_pendingExit_removesBoundsBeforeImmersive() { val task = createFreeformTask() @@ -579,16 +590,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, - info = createTransitionInfo( - changes = listOf(createChange(task)) - ), + info = createTransitionInfo(changes = listOf(createChange(task))), startTransaction = SurfaceControl.Transaction(), finishTransaction = SurfaceControl.Transaction(), ) @@ -606,20 +615,21 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( - wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task)) - ).isTrue() + 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 + Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE, ) fun exitImmersiveIfApplicable_preImmersiveBoundsSaved_changesBoundsToPreImmersiveBounds() { val task = createFreeformTask() @@ -628,23 +638,21 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) val preImmersiveBounds = Rect(100, 100, 500, 500) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds) controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) - assertThat( - wct.hasBoundsChange(task.token, preImmersiveBounds) - ).isTrue() + 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 + Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS, ) fun exitImmersiveIfApplicable_preImmersiveBoundsNotSaved_changesBoundsToInitialBounds() { val task = createFreeformTask() @@ -653,14 +661,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) - assertThat( - wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task)) - ).isTrue() + assertThat(wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task))) + .isTrue() } @Test @@ -672,10 +679,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = true + immersive = true, ) - controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) - .asExit()?.runOnTransitionStart?.invoke(Binder()) + controller + .exitImmersiveIfApplicable(wct, task, USER_INTERACTION) + .asExit() + ?.runOnTransitionStart + ?.invoke(Binder()) controller.moveTaskToNonImmersive(task, USER_INTERACTION) @@ -693,7 +703,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = DEFAULT_DISPLAY, taskId = task.taskId, - immersive = true + immersive = true, ) controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) @@ -711,25 +721,23 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = true + immersive = true, ) controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.animateResizeChange( - change = TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - }, + change = TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }, startTransaction = StubTransaction(), finishTransaction = StubTransaction(), - finishCallback = { } + finishCallback = {}, ) animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS) assertTransitionPending( transition = mockBinder, taskId = task.taskId, - direction = Direction.EXIT + direction = Direction.EXIT, ) } @@ -743,7 +751,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { desktopRepository.setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, - immersive = false + immersive = false, ) controller.moveTaskToImmersive(task) @@ -753,13 +761,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { info = createTransitionInfo(changes = emptyList()), startTransaction = StubTransaction(), finishTransaction = StubTransaction(), - finishCallback = {} + finishCallback = {}, ) assertTransitionNotPending( transition = mockBinder, taskId = task.taskId, - direction = Direction.ENTER + direction = Direction.ENTER, ) } @@ -768,15 +776,18 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId: Int, direction: Direction, animate: Boolean = true, - displayId: Int = DEFAULT_DISPLAY + displayId: Int = DEFAULT_DISPLAY, ) { - assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> - pendingTransition.transition == transition - && pendingTransition.displayId == displayId - && pendingTransition.taskId == taskId - && pendingTransition.animate == animate - && pendingTransition.direction == direction - }).isTrue() + assertThat( + controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition && + pendingTransition.displayId == displayId && + pendingTransition.taskId == taskId && + pendingTransition.animate == animate && + pendingTransition.direction == direction + } + ) + .isTrue() } private fun assertTransitionNotPending( @@ -784,43 +795,44 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId: Int, direction: Direction, animate: Boolean = true, - displayId: Int = DEFAULT_DISPLAY + displayId: Int = DEFAULT_DISPLAY, ) { - assertThat(controller.pendingImmersiveTransitions.any { pendingTransition -> - pendingTransition.transition == transition - && pendingTransition.displayId == displayId - && pendingTransition.taskId == taskId - && pendingTransition.direction == direction - }).isFalse() + assertThat( + controller.pendingImmersiveTransitions.any { pendingTransition -> + pendingTransition.transition == transition && + pendingTransition.displayId == displayId && + pendingTransition.taskId == taskId && + pendingTransition.direction == direction + } + ) + .isFalse() } private fun createTransitionInfo( @TransitionType type: Int = TRANSIT_CHANGE, @TransitionFlags flags: Int = 0, - changes: List<TransitionInfo.Change> = emptyList() - ): TransitionInfo = TransitionInfo(type, flags).apply { - changes.forEach { change -> addChange(change) } - } + changes: List<TransitionInfo.Change> = emptyList(), + ): TransitionInfo = + TransitionInfo(type, flags).apply { changes.forEach { change -> addChange(change) } } private fun createChange(task: RunningTaskInfo): TransitionInfo.Change = - TransitionInfo.Change(task.token, SurfaceControl()).apply { - taskInfo = task - } + TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } private fun WindowContainerTransaction.hasBoundsChange(token: WindowContainerToken): Boolean = this.changes.any { change -> - change.key == token.asBinder() - && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0 + change.key == token.asBinder() && + (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 - } + ): 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) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 49a7e2951a7e..3cf84d92a625 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -85,34 +85,22 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() - @Mock - lateinit var transitions: Transitions - @Mock - lateinit var userRepositories: DesktopUserRepositories - @Mock - lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler - @Mock - lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler + @Mock lateinit var transitions: Transitions + @Mock lateinit var userRepositories: DesktopUserRepositories + @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler + @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler @Mock lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler - @Mock - lateinit var desktopImmersiveController: DesktopImmersiveController - @Mock - lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock - lateinit var mockHandler: Handler - @Mock - lateinit var closingTaskLeash: SurfaceControl - @Mock - lateinit var shellInit: ShellInit - @Mock - lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer - @Mock - private lateinit var desktopRepository: DesktopRepository + @Mock lateinit var desktopImmersiveController: DesktopImmersiveController + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var mockHandler: Handler + @Mock lateinit var closingTaskLeash: SurfaceControl + @Mock lateinit var shellInit: ShellInit + @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var desktopRepository: DesktopRepository private lateinit var mixedHandler: DesktopMixedTransitionHandler - @Before fun setUp() { whenever(userRepositories.current).thenReturn(desktopRepository) @@ -157,11 +145,11 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @DisableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + ) fun startRemoveTransition_callsFreeformTaskTransitionHandler() { val wct = WindowContainerTransaction() - whenever(freeformTaskTransitionHandler.startRemoveTransition(wct)) - .thenReturn(mock()) + whenever(freeformTaskTransitionHandler.startRemoveTransition(wct)).thenReturn(mock()) mixedHandler.startRemoveTransition(wct) @@ -171,7 +159,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + ) fun startRemoveTransition_startsCloseTransition() { val wct = WindowContainerTransaction() whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler)) @@ -193,18 +182,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val transitionInfo = createCloseTransitionInfo( changeMode = TRANSIT_OPEN, - task = createTask(WINDOWING_MODE_FREEFORM) + task = createTask(WINDOWING_MODE_FREEFORM), ) whenever(freeformTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any())) .thenReturn(true) - val started = mixedHandler.startAnimation( - transition = transition, - info = transitionInfo, - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} - ) + val started = + mixedHandler.startAnimation( + transition = transition, + info = transitionInfo, + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) assertFalse("Should not start animation without closing desktop task", started) } @@ -212,7 +202,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + ) fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() { val wct = WindowContainerTransaction() val transition = mock<IBinder>() @@ -225,13 +216,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { .thenReturn(transition) mixedHandler.startRemoveTransition(wct) - val started = mixedHandler.startAnimation( - transition = transition, - info = transitionInfo, - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} - ) + val started = + mixedHandler.startAnimation( + transition = transition, + info = transitionInfo, + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) assertTrue("Should delegate animation to close transition handler", started) verify(closeDesktopTaskTransitionHandler) @@ -241,12 +233,16 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX, + ) fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() { val wct = WindowContainerTransaction() val transition = mock<IBinder>() - val transitionInfo = createCloseTransitionInfo( - task = createTask(WINDOWING_MODE_FREEFORM), withWallpaper = true) + val transitionInfo = + createCloseTransitionInfo( + task = createTask(WINDOWING_MODE_FREEFORM), + withWallpaper = true, + ) whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any())) .thenReturn(mock()) whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler)) @@ -258,7 +254,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { info = transitionInfo, startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) verify(transitions) @@ -268,14 +264,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { any(), any(), any(), - eq(mixedHandler) + eq(mixedHandler), ) verify(interactionJankMonitor) .begin( closingTaskLeash, context, mockHandler, - CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE + CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE, ) } @@ -283,7 +279,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @DisableFlags( Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() { val wct = WindowContainerTransaction() val task = createTask(WINDOWING_MODE_FREEFORM) @@ -294,7 +291,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { transitionType = TRANSIT_OPEN, wct = wct, taskId = task.taskId, - exitingImmersiveTask = null + exitingImmersiveTask = null, ) verify(transitions).startTransition(TRANSIT_OPEN, wct, /* handler= */ null) @@ -312,7 +309,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { transitionType = TRANSIT_OPEN, wct = wct, taskId = task.taskId, - exitingImmersiveTask = null + exitingImmersiveTask = null, ) verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler) @@ -321,7 +318,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() { val wct = WindowContainerTransaction() val task = createTask(WINDOWING_MODE_FREEFORM) @@ -332,7 +330,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { transitionType = TRANSIT_OPEN, wct = wct, taskId = task.taskId, - exitingImmersiveTask = null + exitingImmersiveTask = null, ) verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler) @@ -357,24 +355,22 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange, otherChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, otherChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } - - verify(transitions).dispatchTransition( - eq(transition), - argThat { info -> - info.changes.contains(launchTaskChange) && info.changes.contains(otherChange) - }, - any(), - any(), - any(), - eq(mixedHandler), - ) + ) {} + + verify(transitions) + .dispatchTransition( + eq(transition), + argThat { info -> + info.changes.contains(launchTaskChange) && info.changes.contains(otherChange) + }, + any(), + any(), + any(), + eq(mixedHandler), + ) } @Test @@ -397,32 +393,32 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val immersiveChange = createChange(immersiveTask) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange, immersiveChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, immersiveChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} verify(desktopImmersiveController) .animateResizeChange(eq(immersiveChange), any(), any(), any()) - verify(transitions).dispatchTransition( - eq(transition), - argThat { info -> - info.changes.contains(launchTaskChange) && !info.changes.contains(immersiveChange) - }, - any(), - any(), - any(), - eq(mixedHandler), - ) + verify(transitions) + .dispatchTransition( + eq(transition), + argThat { info -> + info.changes.contains(launchTaskChange) && + !info.changes.contains(immersiveChange) + }, + any(), + any(), + any(), + eq(mixedHandler), + ) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -439,22 +435,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} - verify(rootTaskDisplayAreaOrganizer, times(0)) - .reparentToDisplayArea(anyInt(), any(), any()) + verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any()) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -473,22 +466,20 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange, minimizeChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} - verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea( - anyInt(), eq(minimizeChange.leash), any()) + verify(rootTaskDisplayAreaOrganizer) + .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any()) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -505,15 +496,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) ) - val started = mixedHandler.startAnimation( - transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(nonLaunchTaskChange) - ), - SurfaceControl.Transaction(), - SurfaceControl.Transaction(), - ) { } + val started = + mixedHandler.startAnimation( + transition, + createCloseTransitionInfo(TRANSIT_OPEN, listOf(nonLaunchTaskChange)), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) {} assertFalse("Should not start animation without launching desktop task", started) } @@ -529,21 +518,18 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any())) .thenReturn(mock()) - mixedHandler.startLaunchTransition( - transitionType = TRANSIT_OPEN, - wct = wct, - taskId = null, - ) + mixedHandler.startLaunchTransition(transitionType = TRANSIT_OPEN, wct = wct, taskId = null) - val started = mixedHandler.startAnimation( - transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(createChange(task, mode = TRANSIT_OPEN)) - ), - StubTransaction(), - StubTransaction(), - ) { } + val started = + mixedHandler.startAnimation( + transition, + createCloseTransitionInfo( + TRANSIT_OPEN, + listOf(createChange(task, mode = TRANSIT_OPEN)), + ), + StubTransaction(), + StubTransaction(), + ) {} assertThat(started).isEqualTo(true) } @@ -569,15 +555,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val immersiveChange = createChange(immersiveTask, mode = TRANSIT_CHANGE) val openingChange = createChange(openingTask, mode = TRANSIT_OPEN) - val started = mixedHandler.startAnimation( - transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(immersiveChange, openingChange) - ), - StubTransaction(), - StubTransaction(), - ) { } + val started = + mixedHandler.startAnimation( + transition, + createCloseTransitionInfo(TRANSIT_OPEN, listOf(immersiveChange, openingChange)), + StubTransaction(), + StubTransaction(), + ) {} assertThat(started).isEqualTo(true) verify(desktopImmersiveController) @@ -587,7 +571,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -606,22 +591,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} - verify(rootTaskDisplayAreaOrganizer, times(0)) - .reparentToDisplayArea(anyInt(), any(), any()) + verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any()) } @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + ) fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -642,16 +624,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange, minimizeChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} - verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea( - anyInt(), eq(minimizeChange.leash), any()) + verify(rootTaskDisplayAreaOrganizer) + .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any()) } @Test @@ -672,13 +651,10 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val launchTaskChange = createChange(launchingTask) mixedHandler.startAnimation( transition, - createCloseTransitionInfo( - TRANSIT_OPEN, - listOf(launchTaskChange) - ), + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)), SurfaceControl.Transaction(), SurfaceControl.Transaction(), - ) { } + ) {} assertThat(mixedHandler.pendingMixedTransitions).isEmpty() } @@ -701,7 +677,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { mixedHandler.onTransitionConsumed( transition = transition, aborted = true, - finishTransaction = SurfaceControl.Transaction() + finishTransaction = SurfaceControl.Transaction(), ) assertThat(mixedHandler.pendingMixedTransitions).isEmpty() @@ -714,8 +690,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val transition = Binder() whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2) whenever( - desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any()) - ) + desktopBackNavigationTransitionHandler.startAnimation( + any(), + any(), + any(), + any(), + any(), + ) + ) .thenReturn(true) mixedHandler.addPendingMixedTransition( PendingMixedTransition.Minimize( @@ -726,24 +708,24 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) val minimizingTaskChange = createChange(minimizingTask) - val started = mixedHandler.startAnimation( - transition = transition, - info = - createCloseTransitionInfo( - TRANSIT_TO_BACK, - listOf(minimizingTaskChange) - ), - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} - ) + val started = + mixedHandler.startAnimation( + transition = transition, + info = createCloseTransitionInfo(TRANSIT_TO_BACK, listOf(minimizingTaskChange)), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {}, + ) assertTrue("Should delegate animation to back navigation transition handler", started) verify(desktopBackNavigationTransitionHandler) .startAnimation( eq(transition), argThat { info -> info.changes.contains(minimizingTaskChange) }, - any(), any(), any()) + any(), + any(), + any(), + ) } @Test @@ -753,8 +735,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val transition = Binder() whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2) whenever( - desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any()) - ) + desktopBackNavigationTransitionHandler.startAnimation( + any(), + any(), + any(), + any(), + any(), + ) + ) .thenReturn(true) mixedHandler.addPendingMixedTransition( PendingMixedTransition.Minimize( @@ -767,14 +755,10 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val minimizingTaskChange = createChange(minimizingTask) mixedHandler.startAnimation( transition = transition, - info = - createCloseTransitionInfo( - TRANSIT_TO_BACK, - listOf(minimizingTaskChange) - ), + info = createCloseTransitionInfo(TRANSIT_TO_BACK, listOf(minimizingTaskChange)), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) verify(transitions) @@ -784,7 +768,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { any(), any(), any(), - eq(mixedHandler) + eq(mixedHandler), ) } @@ -814,14 +798,15 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { private fun createCloseTransitionInfo( @TransitionType type: Int, - changes: List<TransitionInfo.Change> = emptyList() - ): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply { - changes.forEach { change -> addChange(change) } - } + changes: List<TransitionInfo.Change> = emptyList(), + ): TransitionInfo = + TransitionInfo(type, /* flags= */ 0).apply { + changes.forEach { change -> addChange(change) } + } private fun createChange( task: RunningTaskInfo, - @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE + @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE, ): TransitionInfo.Change = TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task @@ -838,8 +823,6 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { RunningTaskInfo().apply { token = WindowContainerToken(mock<IWindowContainerToken>()) baseIntent = - Intent().apply { - component = DesktopWallpaperActivity.wallpaperActivityComponent - } + Intent().apply { component = DesktopWallpaperActivity.wallpaperActivityComponent } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index 2f225f22cce0..abd707817621 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -53,9 +53,7 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -/** - * Tests for [DesktopModeEventLogger]. - */ +/** Tests for [DesktopModeEventLogger]. */ class DesktopModeEventLoggerTest : ShellTestCase() { private val desktopModeEventLogger = DesktopModeEventLogger() @@ -64,13 +62,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() { @JvmField @Rule(order = 0) - val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java) - .mockStatic(EventLogTags::class.java).build()!! + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(EventLogTags::class.java) + .build()!! - @JvmField - @Rule(order = 1) - val setFlagsRule = SetFlagsRule() + @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule() @Before fun setUp() { @@ -95,14 +93,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* exit_reason */ eq(0), /* sessionId */ - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellEnterDesktopMode( eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -127,14 +125,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* exit_reason */ eq(0), /* sessionId */ - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellEnterDesktopMode( eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -164,14 +162,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* exit_reason */ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT), /* sessionId */ - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellExitDesktopMode( eq(ExitReason.DRAG_TO_EXIT.reason), - eq(sessionId) + eq(sessionId), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -214,16 +212,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq( - FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED - ), + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), @@ -233,7 +228,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -275,16 +270,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq( - FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED - ), + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), @@ -294,7 +286,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -339,7 +331,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) @@ -358,7 +350,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -399,7 +391,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* unminimize_reason */ eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) @@ -418,7 +410,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), eq(MinimizeReason.TASK_LIMIT.reason), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -459,7 +451,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* unminimize_reason */ eq(UnminimizeReason.TASKBAR_TAP.reason), /* visible_task_count */ - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) @@ -478,7 +470,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UnminimizeReason.TASKBAR_TAP.reason), - eq(TASK_COUNT) + eq(TASK_COUNT), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -486,8 +478,11 @@ class DesktopModeEventLoggerTest : ShellTestCase() { @Test fun logTaskResizingStarted_noOngoingSession_doesNotLog() { - desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER, - InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo()) + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.CORNER, + InputMethod.UNKNOWN_INPUT_METHOD, + createTaskInfo(), + ) verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -498,19 +493,33 @@ class DesktopModeEventLoggerTest : ShellTestCase() { fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() { val sessionId = startDesktopModeSession() - desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER, - InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo(), TASK_SIZE_UPDATE.taskWidth, - TASK_SIZE_UPDATE.taskHeight, displayController) + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.CORNER, + InputMethod.UNKNOWN_INPUT_METHOD, + createTaskInfo(), + TASK_SIZE_UPDATE.taskWidth, + TASK_SIZE_UPDATE.taskHeight, + displayController, + ) verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER + ), /* resizing_stage */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE + ), /* input_method */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD + ), /* desktop_mode_session_id */ eq(sessionId), /* instance_id */ @@ -530,8 +539,11 @@ class DesktopModeEventLoggerTest : ShellTestCase() { @Test fun logTaskResizingEnded_noOngoingSession_doesNotLog() { - desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER, - InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo()) + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.CORNER, + InputMethod.UNKNOWN_INPUT_METHOD, + createTaskInfo(), + ) verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -542,18 +554,31 @@ class DesktopModeEventLoggerTest : ShellTestCase() { fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() { val sessionId = startDesktopModeSession() - desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER, - InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo(), displayController = displayController) + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.CORNER, + InputMethod.UNKNOWN_INPUT_METHOD, + createTaskInfo(), + displayController = displayController, + ) verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER + ), /* resizing_stage */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE + ), /* input_method */ - eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), + eq( + FrameworkStatsLog + .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD + ), /* desktop_mode_session_id */ eq(sessionId), /* instance_id */ @@ -582,9 +607,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() { fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() { desktopModeEventLogger.logTaskInfoStateInit() verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD), + eq( + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD + ), /* instance_id */ eq(0), /* uid */ @@ -604,13 +632,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* unminimize_reason */ eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(0) + eq(0), ) } } private fun createTaskInfo(): RunningTaskInfo { - return TestRunningTaskInfoBuilder().setTaskId(TASK_ID) + return TestRunningTaskInfoBuilder() + .setTaskId(TASK_ID) .setUid(TASK_UID) .setBounds(Rect(TASK_X, TASK_Y, TASK_WIDTH, TASK_HEIGHT)) .build() @@ -628,27 +657,42 @@ class DesktopModeEventLoggerTest : ShellTestCase() { private const val DISPLAY_HEIGHT = 500 private const val DISPLAY_AREA = DISPLAY_HEIGHT * DISPLAY_WIDTH - private val TASK_UPDATE = TaskUpdate( - TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, - visibleTaskCount = TASK_COUNT, - ) + private val TASK_UPDATE = + TaskUpdate( + TASK_ID, + TASK_UID, + TASK_HEIGHT, + TASK_WIDTH, + TASK_X, + TASK_Y, + visibleTaskCount = TASK_COUNT, + ) - private val TASK_SIZE_UPDATE = TaskSizeUpdate( - resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, - inputMethod = InputMethod.UNKNOWN_INPUT_METHOD, - TASK_ID, - TASK_UID, - TASK_HEIGHT, - TASK_WIDTH, - DISPLAY_AREA, - ) + private val TASK_SIZE_UPDATE = + TaskSizeUpdate( + resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, + inputMethod = InputMethod.UNKNOWN_INPUT_METHOD, + TASK_ID, + TASK_UID, + TASK_HEIGHT, + TASK_WIDTH, + DISPLAY_AREA, + ) private fun createTaskUpdate( minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, - ) = TaskUpdate( - TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, - unminimizeReason, TASK_COUNT - ) + ) = + TaskUpdate( + TASK_ID, + TASK_UID, + TASK_HEIGHT, + TASK_WIDTH, + TASK_X, + TASK_Y, + minimizeReason, + unminimizeReason, + TASK_COUNT, + ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index e57ae2a86859..413e7bc5d1d6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -30,49 +30,49 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.KeyEvent import android.window.DisplayAreaInfo import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT +import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask -import com.android.wm.shell.transition.FocusTransitionObserver -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.kotlin.eq -import org.mockito.kotlin.whenever -import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer -import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn -import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession -import com.android.dx.mockito.inline.extended.StaticMockitoSession -import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel +import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.setMain import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.anyInt import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness /** @@ -130,21 +130,24 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) doAnswer { - keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler) - null - }.whenever(inputManager).registerKeyGestureEventHandler(any()) + keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler) + null + } + .whenever(inputManager) + .registerKeyGestureEventHandler(any()) shellInit.init() - desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler( - context, - Optional.of(desktopModeWindowDecorViewModel), - Optional.of(desktopTasksController), - inputManager, - shellTaskOrganizer, - focusTransitionObserver, - testExecutor, - displayController - ) + desktopModeKeyGestureHandler = + DesktopModeKeyGestureHandler( + context, + Optional.of(desktopModeWindowDecorViewModel), + Optional.of(desktopTasksController), + inputManager, + shellTaskOrganizer, + focusTransitionObserver, + testExecutor, + displayController, + ) } @After @@ -160,7 +163,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { @EnableFlags( FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, - FLAG_USE_KEY_GESTURE_EVENT_HANDLER + FLAG_USE_KEY_GESTURE_EVENT_HANDLER, ) fun keyGestureMoveToNextDisplay_shouldMoveToNextDisplay() { // Set up two display ids @@ -176,12 +179,13 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true) - val event = KeyGestureEvent.Builder() - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY) - .setDisplayId(SECOND_DISPLAY) - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D)) - .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) - .build() + val event = + KeyGestureEvent.Builder() + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY) + .setDisplayId(SECOND_DISPLAY) + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D)) + .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) + .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() @@ -190,108 +194,102 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { } @Test - @EnableFlags( - FLAG_USE_KEY_GESTURE_EVENT_HANDLER, - FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS - ) + @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) fun keyGestureSnapLeft_shouldSnapResizeTaskToLeft() { val task = setUpFreeformTask() task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true) - val event = KeyGestureEvent.Builder() - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW) - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET)) - .setModifierState(KeyEvent.META_META_ON) - .build() + val event = + KeyGestureEvent.Builder() + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW) + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET)) + .setModifierState(KeyEvent.META_META_ON) + .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() assertThat(result).isTrue() - verify(desktopModeWindowDecorViewModel).onSnapResize( - task.taskId, - true, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - /* fromMenu= */ false - ) + verify(desktopModeWindowDecorViewModel) + .onSnapResize( + task.taskId, + true, + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + /* fromMenu= */ false, + ) } @Test - @EnableFlags( - FLAG_USE_KEY_GESTURE_EVENT_HANDLER, - FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS - ) + @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) fun keyGestureSnapRight_shouldSnapResizeTaskToRight() { val task = setUpFreeformTask() task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true) - val event = KeyGestureEvent.Builder() - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW) - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET)) - .setModifierState(KeyEvent.META_META_ON) - .build() + val event = + KeyGestureEvent.Builder() + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW) + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET)) + .setModifierState(KeyEvent.META_META_ON) + .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() assertThat(result).isTrue() - verify(desktopModeWindowDecorViewModel).onSnapResize( - task.taskId, - false, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - /* fromMenu= */ false - ) + verify(desktopModeWindowDecorViewModel) + .onSnapResize( + task.taskId, + false, + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + /* fromMenu= */ false, + ) } @Test - @EnableFlags( - FLAG_USE_KEY_GESTURE_EVENT_HANDLER, - FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS - ) + @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) fun keyGestureToggleFreeformWindowSize_shouldToggleTaskSize() { val task = setUpFreeformTask() task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true) - val event = KeyGestureEvent.Builder() - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW) - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS)) - .setModifierState(KeyEvent.META_META_ON) - .build() + val event = + KeyGestureEvent.Builder() + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW) + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS)) + .setModifierState(KeyEvent.META_META_ON) + .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() assertThat(result).isTrue() - verify(desktopTasksController).toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - isMaximized = isTaskMaximized(task, displayController), - source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT, - inputMethod = - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - ), - ) + verify(desktopTasksController) + .toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + isMaximized = isTaskMaximized(task, displayController), + source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT, + inputMethod = DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + ), + ) } @Test - @EnableFlags( - FLAG_USE_KEY_GESTURE_EVENT_HANDLER, - FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS - ) + @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS) fun keyGestureMinimizeFreeformWindow_shouldMinimizeTask() { val task = setUpFreeformTask() task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true) - val event = KeyGestureEvent.Builder() - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW) - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS)) - .setModifierState(KeyEvent.META_META_ON) - .build() + val event = + KeyGestureEvent.Builder() + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW) + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS)) + .setModifierState(KeyEvent.META_META_ON) + .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 7c4ce4acfc9c..43684fb92b64 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -87,700 +87,735 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { - @JvmField - @Rule - val extendedMockitoRule = - ExtendedMockitoRule.Builder(this) - .mockStatic(DesktopModeStatus::class.java) - .mockStatic(SystemProperties::class.java) - .mockStatic(Trace::class.java) - .build()!! - - private val testExecutor = mock<ShellExecutor>() - private val mockShellInit = mock<ShellInit>() - private val transitions = mock<Transitions>() - private val context = mock<Context>() - - private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver - private lateinit var shellInit: ShellInit - private lateinit var desktopModeEventLogger: DesktopModeEventLogger - - @Before - fun setup() { - whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) - shellInit = spy(ShellInit(testExecutor)) - desktopModeEventLogger = mock<DesktopModeEventLogger>() - - transitionObserver = DesktopModeLoggerTransitionObserver( - context, mockShellInit, transitions, desktopModeEventLogger) - val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) - verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver)) - initRunnableCaptor.value.run() - // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a - // consistent state with no outstanding interactions when test cases start executing. - verify(desktopModeEventLogger).logTaskInfoStateInit() - } - - @Test - fun testInitialiseVisibleTasksSystemProperty() { - ExtendedMockito.verify { - SystemProperties.set( - eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY), - eq(DesktopModeLoggerTransitionObserver - .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)) - } - } - - @Test - fun testRegistersObserverAtInit() { - verify(transitions).registerObserver(same(transitionObserver)) - } - - @Test - fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verify(desktopModeEventLogger, never()).logSessionEnter(any()) - verify(desktopModeEventLogger, never()).logTaskAdded(any()) - } - - @Test - fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - // task change is finalised when drag ends - val transitionInfo = - TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) - .addChange(change) - .build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0) - .addChange(change) - .build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) - .addChange(change) - .build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0) - .addChange(change) - .build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitToFront_logTaskAddedAndEnterReasonOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { - // previous exit to overview transition - // add a freeform task - val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.isSessionActive = true - val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) - .build() - - callOnTransitionReady(previousTransitionInfo) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - - // Enter desktop mode from cancelled recents has no transition. Enter is detected on the - // next transition involving freeform windows - - // TRANSIT_TO_FRONT - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { - // previous exit to overview transition - // add a freeform task - val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.isSessionActive = true - val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) - .build() - - callOnTransitionReady(previousTransitionInfo) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - - // Enter desktop mode from cancelled recents has no transition. Enter is detected on the - // next transition involving freeform windows - - // TRANSIT_CHANGE - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { - // previous exit to overview transition - // add a freeform task - val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.isSessionActive = true - val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) - .build() - - callOnTransitionReady(previousTransitionInfo) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - - // Enter desktop mode from cancelled recents has no transition. Enter is detected on the - // next transition involving freeform windows - - // TRANSIT_OPEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - @Suppress("ktlint:standard:max-line-length") - fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() { - // Tests for AppFromOverview precedence in compared to cancelled Overview - - // previous exit to overview transition - // add a freeform task - val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.isSessionActive = true - val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) - .build() - - callOnTransitionReady(previousTransitionInfo) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - - // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) - .addChange(change) - .build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitWake_logTaskAddedAndEnterReasonScreenOn() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() { - val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) - // Previous Exit reason recorded as Screen Off - transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.isSessionActive = true - callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) - // Enter desktop through back transition, this happens when user enters after dismissing - // keyguard - val change = createChange(TRANSIT_TO_FRONT, freeformTask) - val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK, 0).addChange(change).build() - - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() { - val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) - // Previous Exit reason recorded as Screen Off - transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.isSessionActive = true - callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) - - // Enter desktop through app handle drag. This represents cases where instead of moving to - // desktop right after turning the screen on, we move to fullscreen then move another task - // to desktop - val transitionInfo = - TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) - .addChange(createChange(TRANSIT_TO_FRONT, freeformTask)) - .build() - callOnTransitionReady(transitionInfo) - - verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun transitSleep_logTaskRemovedAndExitReasonScreenOff() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON) - .addChange(change) - .build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // recents transition - val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - } - - @Test - fun transitClose_logTaskRemovedAndExitReasonTaskFinished() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // task closing - val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) - } - - @Test - fun transitMinimize_logExitReasongMinimized() { - // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // minimize the task - val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build() - callOnTransitionReady(transitionInfo) - - assertFalse(transitionObserver.isSessionActive) - verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED)) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE)) - verifyZeroInteractions(desktopModeEventLogger) - } - - @Test - fun sessionExitByRecents_cancelledAnimation_sessionRestored() { - // add a freeform task to an existing session - val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.isSessionActive = true - - // recents transition sent freeform window to back - val change = createChange(TRANSIT_TO_BACK, taskInfo) - val transitionInfo1 = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() - callOnTransitionReady(transitionInfo1) - - verifyTaskRemovedAndExitLogging( - ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE - ) - - val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() - callOnTransitionReady(transitionInfo2) - - verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1)) - } - - @Test - fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { - // add an existing freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.isSessionActive = true - - // new freeform task added - val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verify(desktopModeEventLogger, times(1)) - .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) - verify(desktopModeEventLogger, never()).logSessionEnter(any()) - } - - @Test - fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { - // add an existing freeform task - val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.isSessionActive = true - - // task position changed - val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_CHANGE, 0) - .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) - .build() - callOnTransitionReady(transitionInfo) - - verify(desktopModeEventLogger, times(1)) - .logTaskInfoChanged( - eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)) + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeStatus::class.java) + .mockStatic(SystemProperties::class.java) + .mockStatic(Trace::class.java) + .build()!! + + private val testExecutor = mock<ShellExecutor>() + private val mockShellInit = mock<ShellInit>() + private val transitions = mock<Transitions>() + private val context = mock<Context>() + + private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver + private lateinit var shellInit: ShellInit + private lateinit var desktopModeEventLogger: DesktopModeEventLogger + + @Before + fun setup() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + shellInit = spy(ShellInit(testExecutor)) + desktopModeEventLogger = mock<DesktopModeEventLogger>() + + transitionObserver = + DesktopModeLoggerTransitionObserver( + context, + mockShellInit, + transitions, + desktopModeEventLogger, + ) + val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) + verify(mockShellInit) + .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver)) + initRunnableCaptor.value.run() + // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a + // consistent state with no outstanding interactions when test cases start executing. + verify(desktopModeEventLogger).logTaskInfoStateInit() + } + + @Test + fun testInitialiseVisibleTasksSystemProperty() { + ExtendedMockito.verify { + SystemProperties.set( + eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY), + eq( + DesktopModeLoggerTransitionObserver + .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE + ), + ) + } + } + + @Test + fun testRegistersObserverAtInit() { + verify(transitions).registerObserver(same(transitionObserver)) + } + + @Test + fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() { + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, never()).logSessionEnter(any()) + verify(desktopModeEventLogger, never()).logTaskAdded(any()) + } + + @Test + fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() { + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_FREEFORM_INTENT, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), ) - verifyZeroInteractions(desktopModeEventLogger) - } - - @Test - fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { - // add an existing freeform task - val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) - transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.isSessionActive = true - - // task resized - val newTaskInfo = - createTaskInfo( - WINDOWING_MODE_FREEFORM, - taskWidth = DEFAULT_TASK_WIDTH + 100, - taskHeight = DEFAULT_TASK_HEIGHT - 100) - val transitionInfo = - TransitionInfoBuilder(TRANSIT_CHANGE, 0) - .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) - .build() - callOnTransitionReady(transitionInfo) - - verify(desktopModeEventLogger, times(1)) - .logTaskInfoChanged( - eq( - DEFAULT_TASK_UPDATE.copy( - taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100, - visibleTaskCount = 1)) + } + + @Test + fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + // task change is finalised when drag ends + val transitionInfo = + TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_HANDLE_DRAG, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), ) - verifyZeroInteractions(desktopModeEventLogger) - } - - @Test - fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { - // add 2 existing freeform task - val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) - val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) - transitionObserver.addTaskInfosToCachedMap(taskInfo1) - transitionObserver.addTaskInfosToCachedMap(taskInfo2) - transitionObserver.isSessionActive = true - - // task 1 position update - val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) - val transitionInfo1 = - TransitionInfoBuilder(TRANSIT_CHANGE, 0) - .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1)) - .build() - callOnTransitionReady(transitionInfo1) - - verify(desktopModeEventLogger, times(1)) - .logTaskInfoChanged( - eq(DEFAULT_TASK_UPDATE.copy( - taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)) + } + + @Test + fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_HANDLE_MENU_BUTTON, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), ) - verifyZeroInteractions(desktopModeEventLogger) - - // task 2 resize - val newTaskInfo2 = - createTaskInfo( - WINDOWING_MODE_FREEFORM, - id = 2, - taskWidth = DEFAULT_TASK_WIDTH + 100, - taskHeight = DEFAULT_TASK_HEIGHT - 100) - val transitionInfo2 = - TransitionInfoBuilder(TRANSIT_CHANGE, 0) - .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2)) - .build() - - callOnTransitionReady(transitionInfo2) - - verify(desktopModeEventLogger, times(1)) - .logTaskInfoChanged( - eq( - DEFAULT_TASK_UPDATE.copy( - instanceId = 2, - taskWidth = DEFAULT_TASK_WIDTH + 100, - taskHeight = DEFAULT_TASK_HEIGHT - 100, - visibleTaskCount = 2)), - ) - verifyZeroInteractions(desktopModeEventLogger) - } - - @Test - fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { - // add two existing freeform tasks - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) - transitionObserver.isSessionActive = true - - // new freeform task closed - val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) - val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build() - callOnTransitionReady(transitionInfo) - - verify(desktopModeEventLogger, times(1)) - .logTaskRemoved( - eq(DEFAULT_TASK_UPDATE.copy( - instanceId = 2, visibleTaskCount = 1)) + } + + @Test + fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_FROM_OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), ) - verify(desktopModeEventLogger, never()).logSessionExit(any()) - } - - /** Simulate calling the onTransitionReady() method */ - private fun callOnTransitionReady(transitionInfo: TransitionInfo) { - val transition = mock<IBinder>() - val startT = mock<SurfaceControl.Transaction>() - val finishT = mock<SurfaceControl.Transaction>() - - transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) - } - - private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { - assertTrue(transitionObserver.isSessionActive) - verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate)) - ExtendedMockito.verify { - Trace.setCounter( - eq(Trace.TRACE_TAG_WINDOW_MANAGER), - eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME), - eq(taskUpdate.visibleTaskCount.toLong())) - } - ExtendedMockito.verify { - SystemProperties.set( - eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY), - eq(taskUpdate.visibleTaskCount.toString())) - } - verifyZeroInteractions(desktopModeEventLogger) - } - - private fun verifyTaskRemovedAndExitLogging( - exitReason: ExitReason, - taskUpdate: TaskUpdate - ) { - assertFalse(transitionObserver.isSessionActive) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate)) - verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason)) - verifyZeroInteractions(desktopModeEventLogger) - } - - private companion object { - const val DEFAULT_TASK_ID = 1 - const val DEFAULT_TASK_UID = 2 - const val DEFAULT_TASK_HEIGHT = 100 - const val DEFAULT_TASK_WIDTH = 200 - const val DEFAULT_TASK_X = 30 - const val DEFAULT_TASK_Y = 70 - const val DEFAULT_VISIBLE_TASK_COUNT = 0 - val DEFAULT_TASK_UPDATE = - TaskUpdate( - DEFAULT_TASK_ID, - DEFAULT_TASK_UID, - DEFAULT_TASK_HEIGHT, - DEFAULT_TASK_WIDTH, - DEFAULT_TASK_X, - DEFAULT_TASK_Y, - visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT, + } + + @Test + fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.KEYBOARD_SHORTCUT_ENTER, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), ) + } + + @Test + fun transitToFront_logTaskAddedAndEnterReasonOverview() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() - fun createTaskInfo( - windowMode: Int, - id: Int = DEFAULT_TASK_ID, - uid: Int = DEFAULT_TASK_UID, - taskHeight: Int = DEFAULT_TASK_HEIGHT, - taskWidth: Int = DEFAULT_TASK_WIDTH, - taskX: Int = DEFAULT_TASK_X, - taskY: Int = DEFAULT_TASK_Y, - ) = - ActivityManager.RunningTaskInfo().apply { - taskId = id - effectiveUid = uid - configuration.windowConfiguration.apply { - windowingMode = windowMode - positionInParent = Point(taskX, taskY) - bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight)) - } + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + // add a freeform task + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) + transitionObserver.isSessionActive = true + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_TO_FRONT + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + // add a freeform task + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) + transitionObserver.isSessionActive = true + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_CHANGE + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { + // previous exit to overview transition + // add a freeform task + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) + transitionObserver.isSessionActive = true + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + + // Enter desktop mode from cancelled recents has no transition. Enter is detected on the + // next transition involving freeform windows + + // TRANSIT_OPEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + @Suppress("ktlint:standard:max-line-length") + fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() { + // Tests for AppFromOverview precedence in compared to cancelled Overview + + // previous exit to overview transition + // add a freeform task + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) + transitionObserver.isSessionActive = true + val previousTransitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() + + callOnTransitionReady(previousTransitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + + // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) + .addChange(change) + .build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_FROM_OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.UNKNOWN_ENTER, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitWake_logTaskAddedAndEnterReasonScreenOn() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.SCREEN_ON, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() { + val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) + // Previous Exit reason recorded as Screen Off + transitionObserver.addTaskInfosToCachedMap(freeformTask) + transitionObserver.isSessionActive = true + callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + // Enter desktop through back transition, this happens when user enters after dismissing + // keyguard + val change = createChange(TRANSIT_TO_FRONT, freeformTask) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.SCREEN_ON, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() { + val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) + // Previous Exit reason recorded as Screen Off + transitionObserver.addTaskInfosToCachedMap(freeformTask) + transitionObserver.isSessionActive = true + callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + + // Enter desktop through app handle drag. This represents cases where instead of moving to + // desktop right after turning the screen on, we move to fullscreen then move another task + // to desktop + val transitionInfo = + TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) + .addChange(createChange(TRANSIT_TO_FRONT, freeformTask)) + .build() + callOnTransitionReady(transitionInfo) + + verifyTaskAddedAndEnterLogging( + EnterReason.APP_HANDLE_DRAG, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun transitSleep_logTaskRemovedAndExitReasonScreenOff() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // window mode changing from FREEFORM to FULLSCREEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // window mode changing from FREEFORM to FULLSCREEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON) + .addChange(change) + .build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // window mode changing from FREEFORM to FULLSCREEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT) + .addChange(change) + .build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // window mode changing from FREEFORM to FULLSCREEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // recents transition + val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(change) + .build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitClose_logTaskRemovedAndExitReasonTaskFinished() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // task closing + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) + } + + @Test + fun transitMinimize_logExitReasongMinimized() { + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // minimize the task + val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build() + callOnTransitionReady(transitionInfo) + + assertFalse(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED)) + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE)) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionExitByRecents_cancelledAnimation_sessionRestored() { + // add a freeform task to an existing session + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.isSessionActive = true + + // recents transition sent freeform window to back + val change = createChange(TRANSIT_TO_BACK, taskInfo) + val transitionInfo1 = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(change) + .build() + callOnTransitionReady(transitionInfo1) + + verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + + val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() + callOnTransitionReady(transitionInfo2) + + verifyTaskAddedAndEnterLogging( + EnterReason.OVERVIEW, + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + ) + } + + @Test + fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { + // add an existing freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.isSessionActive = true + + // new freeform task added + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) + verify(desktopModeEventLogger, never()).logSessionEnter(any()) + } + + @Test + fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.isSessionActive = true + + // task position changed + val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)) + ) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.isSessionActive = true + + // task resized + val newTaskInfo = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100, + ) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq( + DEFAULT_TASK_UPDATE.copy( + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100, + visibleTaskCount = 1, + ) + ) + ) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { + // add 2 existing freeform task + val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo1) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + transitionObserver.isSessionActive = true + + // task 1 position update + val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo1 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1)) + .build() + callOnTransitionReady(transitionInfo1) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)) + ) + verifyZeroInteractions(desktopModeEventLogger) + + // task 2 resize + val newTaskInfo2 = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + id = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100, + ) + val transitionInfo2 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2)) + .build() + + callOnTransitionReady(transitionInfo2) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100, + visibleTaskCount = 2, + ) + ) + ) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { + // add two existing freeform tasks + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) + transitionObserver.isSessionActive = true + + // new freeform task closed + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 1))) + verify(desktopModeEventLogger, never()).logSessionExit(any()) + } + + /** Simulate calling the onTransitionReady() method */ + private fun callOnTransitionReady(transitionInfo: TransitionInfo) { + val transition = mock<IBinder>() + val startT = mock<SurfaceControl.Transaction>() + val finishT = mock<SurfaceControl.Transaction>() + + transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) + } + + private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { + assertTrue(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate)) + ExtendedMockito.verify { + Trace.setCounter( + eq(Trace.TRACE_TAG_WINDOW_MANAGER), + eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME), + eq(taskUpdate.visibleTaskCount.toLong()), + ) } + ExtendedMockito.verify { + SystemProperties.set( + eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY), + eq(taskUpdate.visibleTaskCount.toString()), + ) + } + verifyZeroInteractions(desktopModeEventLogger) + } + + private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) { + assertFalse(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate)) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason)) + verifyZeroInteractions(desktopModeEventLogger) + } + + private companion object { + const val DEFAULT_TASK_ID = 1 + const val DEFAULT_TASK_UID = 2 + const val DEFAULT_TASK_HEIGHT = 100 + const val DEFAULT_TASK_WIDTH = 200 + const val DEFAULT_TASK_X = 30 + const val DEFAULT_TASK_Y = 70 + const val DEFAULT_VISIBLE_TASK_COUNT = 0 + val DEFAULT_TASK_UPDATE = + TaskUpdate( + DEFAULT_TASK_ID, + DEFAULT_TASK_UID, + DEFAULT_TASK_HEIGHT, + DEFAULT_TASK_WIDTH, + DEFAULT_TASK_X, + DEFAULT_TASK_Y, + visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT, + ) - fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change { - val change = - Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>()) - change.mode = mode - change.taskInfo = taskInfo - return change + fun createTaskInfo( + windowMode: Int, + id: Int = DEFAULT_TASK_ID, + uid: Int = DEFAULT_TASK_UID, + taskHeight: Int = DEFAULT_TASK_HEIGHT, + taskWidth: Int = DEFAULT_TASK_WIDTH, + taskX: Int = DEFAULT_TASK_X, + taskY: Int = DEFAULT_TASK_Y, + ) = + ActivityManager.RunningTaskInfo().apply { + taskId = id + effectiveUid = uid + configuration.windowConfiguration.apply { + windowingMode = windowMode + positionInParent = Point(taskX, taskY) + bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight)) + } + } + + fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change { + val change = + Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>()) + change.mode = mode + change.taskInfo = taskInfo + return change + } } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt index db4e93de9541..f6eed5da6cad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt @@ -18,18 +18,18 @@ package com.android.wm.shell.desktopmode import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG +import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType -import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW +import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN @@ -53,8 +53,7 @@ class DesktopModeTransitionTypesTest { .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON) assertThat(APP_FROM_OVERVIEW.getEnterTransitionType()) .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW) - assertThat(TASK_DRAG.getEnterTransitionType()) - .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN) + assertThat(TASK_DRAG.getEnterTransitionType()).isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN) assertThat(KEYBOARD_SHORTCUT.getEnterTransitionType()) .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt index 94698e2fc0fb..72b1fd9af117 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.desktopmode - import android.content.ComponentName import android.content.pm.ApplicationInfo import android.content.pm.PackageManager @@ -67,8 +66,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() { @Test fun log_eventLogged() { - val event = - DESKTOP_WINDOW_EDGE_DRAG_RESIZE + val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE logger.log(UID, PACKAGE_NAME, event) assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) @@ -97,8 +95,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() { @Test fun logWithInstanceId_eventLogged() { - val event = - DESKTOP_WINDOW_EDGE_DRAG_RESIZE + val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event) assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) @@ -109,12 +106,12 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() { @Test fun logWithTaskInfo_eventLogged() { - val event = - DESKTOP_WINDOW_EDGE_DRAG_RESIZE - val taskInfo = TestRunningTaskInfoBuilder() - .setUserId(USER_ID) - .setBaseActivity(ComponentName(PACKAGE_NAME, "test")) - .build() + val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE + val taskInfo = + TestRunningTaskInfoBuilder() + .setUserId(USER_ID) + .setBaseActivity(ComponentName(PACKAGE_NAME, "test")) + .build() whenever(mockPackageManager.getApplicationInfoAsUser(PACKAGE_NAME, /* flags= */ 0, USER_ID)) .thenReturn(ApplicationInfo().apply { uid = UID }) logger.log(taskInfo, event) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index 935e6d052f5e..e46d2c7147ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -66,74 +66,109 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { fun testFullscreenRegionCalculation() { createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, - 2 * STABLE_INSETS.top)) + assertThat(testRegion.bounds) + .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * STABLE_INSETS.top)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) val transitionHeight = SystemBarUtils.getStatusBarHeight(context) - val toFullscreenScale = mContext.resources.getFloat( - R.dimen.desktop_mode_fullscreen_region_scale - ) + val toFullscreenScale = + mContext.resources.getFloat(R.dimen.desktop_mode_fullscreen_region_scale) val toFullscreenWidth = displayLayout.width() * toFullscreenScale - assertThat(testRegion.bounds).isEqualTo(Rect( - (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(), - Short.MIN_VALUE.toInt(), - (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(), - transitionHeight)) + assertThat(testRegion.bounds) + .isEqualTo( + Rect( + (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(), + Short.MIN_VALUE.toInt(), + (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(), + transitionHeight, + ) + ) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, - 2 * STABLE_INSETS.top)) + assertThat(testRegion.bounds) + .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * STABLE_INSETS.top)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, - transitionHeight)) + assertThat(testRegion.bounds) + .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, transitionHeight)) } @Test fun testSplitLeftRegionCalculation() { - val transitionHeight = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_split_from_desktop_height) + val transitionHeight = + context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) - var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + var testRegion = + visualIndicator.calculateSplitLeftRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) - testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitLeftRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) - testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitLeftRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) - testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitLeftRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600)) } @Test fun testSplitRightRegionCalculation() { - val transitionHeight = context.resources.getDimensionPixelSize( - R.dimen.desktop_mode_split_from_desktop_height) + val transitionHeight = + context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) - var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + var testRegion = + visualIndicator.calculateSplitRightRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM) - testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitRightRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) - testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitRightRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) - testRegion = visualIndicator.calculateSplitRightRegion(displayLayout, - TRANSITION_AREA_WIDTH, CAPTION_HEIGHT) + testRegion = + visualIndicator.calculateSplitRightRegion( + displayLayout, + TRANSITION_AREA_WIDTH, + CAPTION_HEIGHT, + ) assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600)) } @@ -141,10 +176,12 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { fun testDefaultIndicators() { createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f)) - assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR) + assertThat(result) + .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT) result = visualIndicator.updateIndicatorType(PointF(10000f, 500f)) - assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR) + assertThat(result) + .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR) createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) result = visualIndicator.updateIndicatorType(PointF(500f, 10000f)) assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) @@ -154,8 +191,16 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { } private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) { - visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, - context, taskSurface, taskDisplayAreaOrganizer, dragStartState) + visualIndicator = + DesktopModeVisualIndicator( + syncQueue, + taskInfo, + displayController, + context, + taskSurface, + taskDisplayAreaOrganizer, + dragStartState, + ) } companion object { @@ -163,11 +208,12 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { private const val CAPTION_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private const val NAVBAR_HEIGHT = 50 - private val STABLE_INSETS = Rect( - DISPLAY_BOUNDS.left, - DISPLAY_BOUNDS.top + CAPTION_HEIGHT, - DISPLAY_BOUNDS.right, - DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT - ) + private val STABLE_INSETS = + Rect( + DISPLAY_BOUNDS.left, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, + DISPLAY_BOUNDS.right, + DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, + ) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 344140d91ab3..e777ec7b55f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -76,15 +76,9 @@ class DesktopRepositoryTest : ShellTestCase() { datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - repo = - DesktopRepository( - persistentRepository, - datastoreScope, - DEFAULT_USER_ID - ) - whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( - Desktop.getDefaultInstance() - ) + repo = DesktopRepository(persistentRepository, datastoreScope, DEFAULT_USER_ID) + whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }) + .thenReturn(Desktop.getDefaultInstance()) shellInit.init() } @@ -245,7 +239,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(1)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf() + freeformTasksInZOrder = arrayListOf(), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -253,7 +247,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(1, 2)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf() + freeformTasksInZOrder = arrayListOf(), ) } } @@ -441,8 +435,8 @@ class DesktopRepositoryTest : ShellTestCase() { } /** - * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY. - * This tests that task is removed from the last parent display when it vanishes. + * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY. This tests that + * task is removed from the last parent display when it vanishes. */ @Test fun updateTask_removeVisibleTasksRemovesTaskWithInvalidDisplay() { @@ -562,7 +556,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(5) + freeformTasksInZOrder = arrayListOf(5), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -570,7 +564,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(5)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(6, 5) + freeformTasksInZOrder = arrayListOf(6, 5), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -578,10 +572,10 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(5, 6)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(7, 6, 5) + freeformTasksInZOrder = arrayListOf(7, 6, 5), ) } - } + } @Test fun addTask_alreadyExists_movesToTop() { @@ -628,7 +622,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(5) + freeformTasksInZOrder = arrayListOf(5), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -636,7 +630,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(5)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(6, 5) + freeformTasksInZOrder = arrayListOf(6, 5), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -644,7 +638,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(5, 6)), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(7, 6, 5) + freeformTasksInZOrder = arrayListOf(7, 6, 5), ) verify(persistentRepository, times(2)) .addOrUpdateDesktop( @@ -652,10 +646,10 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(arrayOf(5, 7)), minimizedTasks = ArraySet(arrayOf(6)), - freeformTasksInZOrder = arrayListOf(7, 6, 5) + freeformTasksInZOrder = arrayListOf(7, 6, 5), ) } - } + } @Test fun addTask_taskIsUnminimized_noop() { @@ -694,7 +688,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(1) + freeformTasksInZOrder = arrayListOf(1), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -702,7 +696,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = ArrayList() + freeformTasksInZOrder = ArrayList(), ) } } @@ -731,7 +725,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(1) + freeformTasksInZOrder = arrayListOf(1), ) verify(persistentRepository) .addOrUpdateDesktop( @@ -739,7 +733,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = ArrayList() + freeformTasksInZOrder = ArrayList(), ) } } @@ -768,7 +762,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = arrayListOf(1) + freeformTasksInZOrder = arrayListOf(1), ) verify(persistentRepository, never()) .addOrUpdateDesktop( @@ -776,7 +770,7 @@ class DesktopRepositoryTest : ShellTestCase() { DEFAULT_DESKTOP_ID, visibleTasks = ArraySet(), minimizedTasks = ArraySet(), - freeformTasksInZOrder = ArrayList() + freeformTasksInZOrder = ArrayList(), ) } } @@ -928,7 +922,6 @@ class DesktopRepositoryTest : ShellTestCase() { assertThat(repo.isMinimizedTask(taskId = 2)).isFalse() } - @Test fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() { repo.minimizeTask(displayId = 10, taskId = 2) @@ -1056,6 +1049,7 @@ class DesktopRepositoryTest : ShellTestCase() { class TestListener : DesktopRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 + override fun onActiveTasksChanged(displayId: Int) { when (displayId) { DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++ @@ -1093,4 +1087,4 @@ class DesktopRepositoryTest : ShellTestCase() { private const val DEFAULT_USER_ID = 1000 private const val DEFAULT_DESKTOP_ID = 0 } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt index b4daa6637f83..19ab9113bc7a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt @@ -22,7 +22,6 @@ import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION -import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask @@ -45,167 +44,144 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class DesktopTaskChangeListenerTest : ShellTestCase() { - @JvmField @Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule val setFlagsRule = SetFlagsRule() - private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener + private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener - private val desktopUserRepositories = mock<DesktopUserRepositories>() - private val desktopRepository = mock<DesktopRepository>() + private val desktopUserRepositories = mock<DesktopUserRepositories>() + private val desktopRepository = mock<DesktopRepository>() - @Before - fun setUp() { - desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories) + @Before + fun setUp() { + desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories) - whenever(desktopUserRepositories.current).thenReturn(desktopRepository) - whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository) - } + whenever(desktopUserRepositories.current).thenReturn(desktopRepository) + whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository) + } - @Test - fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() { - val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(false) + @Test + fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() { + val task = createFullscreenTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false) - desktopTaskChangeListener.onTaskOpening(task) + desktopTaskChangeListener.onTaskOpening(task) - verify(desktopUserRepositories.current, never()) - .addTask(task.displayId, task.taskId, task.isVisible) - verify(desktopUserRepositories.current, never()) - .removeFreeformTask(task.displayId, task.taskId) - } + verify(desktopUserRepositories.current, never()) + .addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current, never()) + .removeFreeformTask(task.displayId, task.taskId) + } - @Test - fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() { - val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskOpening(task) - - verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) - } + @Test + fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() { + val task = createFullscreenTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) - @Test - fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(false) - - desktopTaskChangeListener.onTaskOpening(task) - - verify(desktopUserRepositories.current) - .addTask(task.displayId, task.taskId, task.isVisible) - } - - @Test - fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() { - val task = createFreeformTask().apply { isVisible = false } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskOpening(task) - - verify(desktopUserRepositories.current) - .addTask(task.displayId, task.taskId, task.isVisible) - } - - @Test - fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() { - val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskChanging(task) - - verify(desktopUserRepositories.current) - .removeFreeformTask(task.displayId, task.taskId) - } - - @Test - fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskChanging(task) - - verify(desktopUserRepositories.current) - .updateTask(task.displayId, task.taskId, task.isVisible) - } - - @Test - fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() { - val task = createFreeformTask().apply { isVisible = false } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskChanging(task) - - verify(desktopUserRepositories.current) - .updateTask(task.displayId, task.taskId, task.isVisible) - } - - @Test - fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() { - val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskMovingToFront(task) - - verify(desktopUserRepositories.current) - .removeFreeformTask(task.displayId, task.taskId) - } - - @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) - .thenReturn(false) - - desktopTaskChangeListener.onTaskClosing(task) - - verify(desktopUserRepositories.current) - .updateTask(task.displayId, task.taskId, isVisible = false) - verify(desktopUserRepositories.current) - .minimizeTask(task.displayId, task.taskId) - } - - @Test - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskClosing(task) - - verify(desktopUserRepositories.current, never()) - .minimizeTask(task.displayId, task.taskId) - verify(desktopUserRepositories.current) - .removeClosingTask(task.taskId) - verify(desktopUserRepositories.current) - .removeFreeformTask(task.displayId, task.taskId) - } - - @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) - .thenReturn(true) - whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) - .thenReturn(true) - - desktopTaskChangeListener.onTaskClosing(task) - - verify(desktopUserRepositories.current).removeClosingTask(task.taskId) - verify(desktopUserRepositories.current) - .removeFreeformTask(task.displayId, task.taskId) - } + desktopTaskChangeListener.onTaskOpening(task) + + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) + } + + @Test + fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false) + + desktopTaskChangeListener.onTaskOpening(task) + + verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible) + } + + @Test + fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() { + val task = createFreeformTask().apply { isVisible = false } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskOpening(task) + + verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible) + } + + @Test + fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() { + val task = createFullscreenTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskChanging(task) + + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) + } + + @Test + fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskChanging(task) + + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) + } + + @Test + fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() { + val task = createFreeformTask().apply { isVisible = false } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskChanging(task) + + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) + } + + @Test + fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() { + val task = createFullscreenTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskMovingToFront(task) + + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(false) + + desktopTaskChangeListener.onTaskClosing(task) + + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, isVisible = false) + verify(desktopUserRepositories.current).minimizeTask(task.displayId, task.taskId) + } + + @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskClosing(task) + + verify(desktopUserRepositories.current, never()).minimizeTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current).removeClosingTask(task.taskId) + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() { + val task = createFreeformTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskClosing(task) + + verify(desktopUserRepositories.current).removeClosingTask(task.taskId) + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 0b12d228a0c2..0eb88e368054 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -81,9 +81,9 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags -import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP +import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY import com.android.wm.shell.MockToken import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -96,7 +96,6 @@ import com.android.wm.shell.common.DisplayLayout 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.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -109,6 +108,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSplitScreenTask import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository @@ -137,8 +137,8 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import java.util.function.Consumer import java.util.Optional +import java.util.function.Consumer import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlin.test.assertIs @@ -167,8 +167,8 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.spy -import org.mockito.Mockito.verify import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor @@ -189,4415 +189,4737 @@ import org.mockito.quality.Strictness @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) class DesktopTasksControllerTest : ShellTestCase() { - @JvmField @Rule val setFlagsRule = SetFlagsRule() - - @Mock lateinit var testExecutor: ShellExecutor - @Mock lateinit var shellCommandHandler: ShellCommandHandler - @Mock lateinit var shellController: ShellController - @Mock lateinit var displayController: DisplayController - @Mock lateinit var displayLayout: DisplayLayout - @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer - @Mock lateinit var syncQueue: SyncTransactionQueue - @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer - @Mock lateinit var transitions: Transitions - @Mock lateinit var keyguardManager: KeyguardManager - @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator - @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler - @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler - @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler - @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler - @Mock - lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler - @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler - @Mock - lateinit var mMockDesktopImmersiveController: DesktopImmersiveController - @Mock lateinit var splitScreenController: SplitScreenController - @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler - @Mock lateinit var dragAndDropController: DragAndDropController - @Mock lateinit var multiInstanceHelper: MultiInstanceHelper - @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator - @Mock lateinit var recentTasksController: RecentTasksController - @Mock - private lateinit var mockInteractionJankMonitor: InteractionJankMonitor - @Mock private lateinit var mockSurface: SurfaceControl - @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener - @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter - @Mock private lateinit var mockHandler: Handler - @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger - @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger - @Mock lateinit var persistentRepository: DesktopPersistentRepository - @Mock lateinit var motionEvent: MotionEvent - @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer - @Mock private lateinit var mockToast: Toast - private lateinit var mockitoSession: StaticMockitoSession - @Mock - private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel - @Mock - private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration - @Mock private lateinit var resources: Resources - @Mock - lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener - @Mock private lateinit var userManager: UserManager - private lateinit var controller: DesktopTasksController - private lateinit var shellInit: ShellInit - private lateinit var taskRepository: DesktopRepository - private lateinit var userRepositories: DesktopUserRepositories - private lateinit var desktopTasksLimiter: DesktopTasksLimiter - private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener - private lateinit var testScope: CoroutineScope - - private val shellExecutor = TestShellExecutor() - - // Mock running tasks are registered here so we can get the list from mock shell task organizer - private val runningTasks = mutableListOf<RunningTaskInfo>() - - private val DISPLAY_DIMENSION_SHORT = 1600 - private val DISPLAY_DIMENSION_LONG = 2560 - private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) - private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085) - private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635) - private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275) - private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611) - private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275) - - @Before - fun setUp() { - Dispatchers.setMain(StandardTestDispatcher()) - mockitoSession = - mockitoSession() - .strictness(Strictness.LENIENT) - .spyStatic(DesktopModeStatus::class.java) - .spyStatic(Toast::class.java) - .startMocking() - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - - testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) - shellInit = spy(ShellInit(testExecutor)) - userRepositories = - DesktopUserRepositories( - context, - shellInit, - shellController, - persistentRepository, - repositoryInitializer, - testScope, - userManager) - desktopTasksLimiter = - DesktopTasksLimiter( + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var shellCommandHandler: ShellCommandHandler + @Mock lateinit var shellController: ShellController + @Mock lateinit var displayController: DisplayController + @Mock lateinit var displayLayout: DisplayLayout + @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock lateinit var syncQueue: SyncTransactionQueue + @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock lateinit var transitions: Transitions + @Mock lateinit var keyguardManager: KeyguardManager + @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator + @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler + @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler + @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler + @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler + @Mock + lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler + @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler + @Mock lateinit var mMockDesktopImmersiveController: DesktopImmersiveController + @Mock lateinit var splitScreenController: SplitScreenController + @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler + @Mock lateinit var dragAndDropController: DragAndDropController + @Mock lateinit var multiInstanceHelper: MultiInstanceHelper + @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator + @Mock lateinit var recentTasksController: RecentTasksController + @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + @Mock private lateinit var mockSurface: SurfaceControl + @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener + @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter + @Mock private lateinit var mockHandler: Handler + @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger + @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger + @Mock lateinit var persistentRepository: DesktopPersistentRepository + @Mock lateinit var motionEvent: MotionEvent + @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer + @Mock private lateinit var mockToast: Toast + private lateinit var mockitoSession: StaticMockitoSession + @Mock private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel + @Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration + @Mock private lateinit var resources: Resources + @Mock + lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener + @Mock private lateinit var userManager: UserManager + private lateinit var controller: DesktopTasksController + private lateinit var shellInit: ShellInit + private lateinit var taskRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories + private lateinit var desktopTasksLimiter: DesktopTasksLimiter + private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener + private lateinit var testScope: CoroutineScope + + private val shellExecutor = TestShellExecutor() + + // Mock running tasks are registered here so we can get the list from mock shell task organizer + private val runningTasks = mutableListOf<RunningTaskInfo>() + + private val DISPLAY_DIMENSION_SHORT = 1600 + private val DISPLAY_DIMENSION_LONG = 2560 + private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275) + private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085) + private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635) + private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275) + private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611) + private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275) + + @Before + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .spyStatic(Toast::class.java) + .startMocking() + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + + testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + shellInit = spy(ShellInit(testExecutor)) + userRepositories = + DesktopUserRepositories( + context, + shellInit, + shellController, + persistentRepository, + repositoryInitializer, + testScope, + userManager, + ) + desktopTasksLimiter = + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + MAX_TASK_LIMIT, + mockInteractionJankMonitor, + mContext, + mockHandler, + ) + + whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } + whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } + whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() } + whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } + whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }) + .thenReturn(Desktop.getDefaultInstance()) + doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) } + + val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + any<RunningTaskInfo>(), + any(), + ) + ) + .thenReturn(ExitResult.NoExit) + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + anyInt(), + anyOrNull(), + any(), + ) + ) + .thenReturn(ExitResult.NoExit) + + controller = createController() + controller.setSplitScreenController(splitScreenController) + controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter + controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener + + shellInit.init() + + val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java) + verify(recentsTransitionHandler).addTransitionStateListener(captor.capture()) + recentsTransitionStateListener = captor.value + + controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener + + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + taskRepository = userRepositories.current + } + + private fun createController(): DesktopTasksController { + return DesktopTasksController( + context, + shellInit, + shellCommandHandler, + shellController, + displayController, + shellTaskOrganizer, + syncQueue, + rootTaskDisplayAreaOrganizer, + dragAndDropController, transitions, + keyguardManager, + mReturnToDragStartAnimator, + desktopMixedTransitionHandler, + enterDesktopTransitionHandler, + exitDesktopTransitionHandler, + dragAndDropTransitionHandler, + toggleResizeDesktopTaskTransitionHandler, + dragToDesktopTransitionHandler, + mMockDesktopImmersiveController, userRepositories, - shellTaskOrganizer, - MAX_TASK_LIMIT, + recentsTransitionHandler, + multiInstanceHelper, + shellExecutor, + Optional.of(desktopTasksLimiter), + recentTasksController, mockInteractionJankMonitor, - mContext, - mockHandler) - - whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } - whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() } - whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) - whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(STABLE_BOUNDS) - } - whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( - Desktop.getDefaultInstance() - ) - doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) } - - val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>(), any())) - .thenReturn(ExitResult.NoExit) - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) - .thenReturn(ExitResult.NoExit) - - controller = createController() - controller.setSplitScreenController(splitScreenController) - controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter - controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener - - shellInit.init() - - val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java) - verify(recentsTransitionHandler).addTransitionStateListener(captor.capture()) - recentsTransitionStateListener = captor.value - - controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener - - assumeTrue(ENABLE_SHELL_TRANSITIONS) - - taskRepository = userRepositories.current - } - - private fun createController(): DesktopTasksController { - return DesktopTasksController( - context, - shellInit, - shellCommandHandler, - shellController, - displayController, - shellTaskOrganizer, - syncQueue, - rootTaskDisplayAreaOrganizer, - dragAndDropController, - transitions, - keyguardManager, - mReturnToDragStartAnimator, - desktopMixedTransitionHandler, - enterDesktopTransitionHandler, - exitDesktopTransitionHandler, - dragAndDropTransitionHandler, - toggleResizeDesktopTaskTransitionHandler, - dragToDesktopTransitionHandler, - mMockDesktopImmersiveController, - userRepositories, - recentsTransitionHandler, - multiInstanceHelper, - shellExecutor, - Optional.of(desktopTasksLimiter), - recentTasksController, - mockInteractionJankMonitor, - mockHandler, - desktopModeEventLogger, - desktopModeUiEventLogger, - desktopTilingDecorViewModel, - ) - } - - @After - fun tearDown() { - mockitoSession.finishMocking() - - runningTasks.clear() - testScope.cancel() - } - - @Test - fun instantiate_addInitCallback() { - verify(shellInit).addInitCallback(any(), any<DesktopTasksController>()) - } - - @Test - fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() { - setUpFreeformTask() - - assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse() - } - - @Test - fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() { - val task1 = setUpFreeformTask() - - val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize( - task1, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) + mockHandler, + desktopModeEventLogger, + desktopModeUiEventLogger, + desktopTilingDecorViewModel, + ) + } - verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task1, - STABLE_BOUNDS.width(), - STABLE_BOUNDS.height(), - displayController - ) - assertThat(argumentCaptor.value).isTrue() - } - - @Test - fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() { - val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } - setUpFreeformTask(bounds = stableBounds, active = true) - assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() - } - - @Test - fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() { - val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } - val task1 = setUpFreeformTask(bounds = stableBounds, active = true) - - val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize( - task1, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.RESTORE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, - InputMethod.TOUCH - ) - ) + @After + fun tearDown() { + mockitoSession.finishMocking() - verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - eq(ResizeTrigger.MAXIMIZE_BUTTON), - eq(InputMethod.TOUCH), - eq(task1), - anyOrNull(), - anyOrNull(), - eq(displayController), - anyOrNull() - ) - assertThat(argumentCaptor.value).isFalse() - } - - @Test - fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() { - val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } - setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)) - - assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() - } - - - @Test - fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() { - whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false) - clearInvocations(shellInit) - - createController() - - verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>()) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() { - val homeTask = setUpHomeTask() - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskHidden(task1) - markTaskHidden(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: home, task1, task2 - wct.assertReorderAt(index = 0, homeTask) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskHidden(task1) - markTaskHidden(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: wallpaper intent, task1, task2 - wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - fun isDesktopModeShowing_noTasks_returnsFalse() { - assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() - } - - @Test - fun isDesktopModeShowing_noTasksVisible_returnsFalse() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskHidden(task1) - markTaskHidden(task2) - - assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() - } - - @Test - fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskVisible(task1) - markTaskHidden(task2) - - assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() { - val homeTask = setUpHomeTask(SECOND_DISPLAY) - val task1 = setUpFreeformTask(SECOND_DISPLAY) - val task2 = setUpFreeformTask(SECOND_DISPLAY) - markTaskHidden(task1) - markTaskHidden(task2) - - controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: home, task1, task2 (no wallpaper intent) - wct.assertReorderAt(index = 0, homeTask) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() { - val homeTask = setUpHomeTask() - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskVisible(task1) - markTaskVisible(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: home, task1, task2 - wct.assertReorderAt(index = 0, homeTask) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() { - val homeTask = setUpHomeTask(SECOND_DISPLAY) - val task1 = setUpFreeformTask(SECOND_DISPLAY) - val task2 = setUpFreeformTask(SECOND_DISPLAY) - markTaskHidden(task1) - markTaskHidden(task2) - - controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: home, task1, task2 - wct.assertReorderAt(index = 0, homeTask) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskVisible(task1) - markTaskVisible(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: wallpaper intent, task1, task2 - wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() { - val homeTask = setUpHomeTask() - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskHidden(task1) - markTaskVisible(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: home, task1, task2 - wct.assertReorderAt(index = 0, homeTask) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - markTaskHidden(task1) - markTaskVisible(task2) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Expect order to be from bottom: wallpaper intent, task1, task2 - wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) - wct.assertReorderAt(index = 1, task1) - wct.assertReorderAt(index = 2, task2) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() { - val homeTask = setUpHomeTask() - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(1) - wct.assertReorderAt(index = 0, homeTask) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() { - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() { - val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) - val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) - setUpHomeTask(SECOND_DISPLAY) - val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) - markTaskHidden(taskDefaultDisplay) - markTaskHidden(taskSecondDisplay) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(2) - // Expect order to be from bottom: home, task - wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) - wct.assertReorderAt(index = 1, taskDefaultDisplay) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() { - val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) - val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) - setUpHomeTask(SECOND_DISPLAY) - val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) - markTaskHidden(taskDefaultDisplay) - markTaskHidden(taskSecondDisplay) - - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Move home to front - wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) - // Add desktop wallpaper activity - wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent) - // Move freeform task to front - wct.assertReorderAt(index = 2, taskDefaultDisplay) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() { - val homeTask = setUpHomeTask() - val freeformTask = setUpFreeformTask() - val minimizedTask = setUpFreeformTask() - - markTaskHidden(freeformTask) - markTaskHidden(minimizedTask) - taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(2) - // Reorder home and freeform task to top, don't reorder the minimized task - wct.assertReorderAt(index = 0, homeTask, toTop = true) - wct.assertReorderAt(index = 1, freeformTask, toTop = true) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() { - val homeTask = setUpHomeTask() - val freeformTask = setUpFreeformTask() - val minimizedTask = setUpFreeformTask() - - markTaskHidden(freeformTask) - markTaskHidden(minimizedTask) - taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - - val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) - assertThat(wct.hierarchyOps).hasSize(3) - // Move home to front - wct.assertReorderAt(index = 0, homeTask, toTop = true) - // Add desktop wallpaper activity - wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent) - // Reorder freeform task to top, don't reorder the minimized task - wct.assertReorderAt(index = 2, freeformTask, toTop = true) - } - - @Test - fun visibleTaskCount_noTasks_returnsZero() { - assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) - } - - @Test - fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() { - setUpHomeTask() - setUpFreeformTask().also(::markTaskVisible) - setUpFreeformTask().also(::markTaskVisible) - assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) - } - - @Test - fun visibleTaskCount_twoTasks_oneVisible_returnsOne() { - setUpHomeTask() - setUpFreeformTask().also(::markTaskVisible) - setUpFreeformTask().also(::markTaskHidden) - assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) - } - - @Test - fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { - setUpHomeTask() - setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible) - setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible) - assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) - } - - @Test - fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(gravity = Gravity.LEFT) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) - } - - @Test - fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(gravity = Gravity.RIGHT) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) - } - - @Test - fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(gravity = Gravity.TOP) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) - } - - @Test - fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun handleRequest_newFreeformTaskLaunch_cascadeApplied() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) - val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false) - - val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) - - assertNotNull(wct, "should handle request") - val finalBounds = findBoundsChange(wct, freeformTask) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.BottomRight) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) - val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) - - val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) - - assertNull(wct, "should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomRight() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.BottomRight) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopLeft() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.TopLeft) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomLeft() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.BottomLeft) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopRight() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.TopRight) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionResetsToCenter() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.Center) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - // Add freeform task with half display size snap bounds at left side. - setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.Center) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - // Add freeform task with half display size snap bounds at right side. - setUpFreeformTask(bounds = Rect( - stableBounds.right - 500, stableBounds.top, stableBounds.right, stableBounds.bottom)) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.Center) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - // Add maximised freeform task. - setUpFreeformTask(bounds = Rect(stableBounds)) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.Center) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_defaultToCenterIfFree() { - setUpLandscapeDisplay() - val stableBounds = Rect() - displayLayout.getStableBoundsForDesktopMode(stableBounds) - - val minTouchTarget = context.resources.getDimensionPixelSize( - R.dimen.freeform_required_visible_empty_space_in_header) - addFreeformTaskAtPosition(DesktopTaskPosition.Center, stableBounds, - Rect(0, 0, 1600, 1200), Point(0, minTouchTarget + 1)) - - val task = setUpFullscreenTask() - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - val finalBounds = findBoundsChange(wct, task) - assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) - .isEqualTo(DesktopTaskPosition.Center) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(enableUserFullscreenOverride = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, - shouldLetterbox = true, aspectRatioOverrideApplied = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { - setUpPortraitDisplay() - val task = setUpFullscreenTask(enableUserFullscreenOverride = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { - setUpPortraitDisplay() - val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { - setUpPortraitDisplay() - val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, - deviceOrientation = ORIENTATION_PORTRAIT, - shouldLetterbox = true, aspectRatioOverrideApplied = true) - val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) - - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) - } - - @Test - fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { - val task = setUpFullscreenTask() - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() { - val task = setUpFullscreenTask() - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - fun moveTaskToDesktop_nonExistentTask_doesNothing() { - controller.moveTaskToDesktop(999, transitionSource = UNKNOWN) - verifyEnterDesktopWCTNotExecuted() - verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted(anyInt()) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() { - val task = createTaskInfo(1) - whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) - - controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN) - - with(getLatestEnterDesktopWct()) { - assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) - } - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() { - val task = createTaskInfo(1) - whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) - - controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN) - - with(getLatestEnterDesktopWct()) { - // Add desktop wallpaper activity - assertPendingIntentAt(index = 0, desktopWallpaperIntent) - // Launch task - assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM) - } - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() { - val task = - setUpFullscreenTask().apply { - isActivityStackTransparent = true - isTopActivityNoDisplay = true - numActivities = 1 - } - - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() { - val task = - setUpFullscreenTask().apply { - isActivityStackTransparent = true - isTopActivityNoDisplay = false - numActivities = 1 - } - - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - verifyEnterDesktopWCTNotExecuted() - verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted( - FREEFORM_ANIMATION_DURATION - ) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() { - // Set task as systemUI package - val systemUIPackageName = context.resources.getString( - com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - val task = - setUpFullscreenTask().apply { - baseActivity = baseComponent - isTopActivityNoDisplay = false - } - - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - verifyEnterDesktopWCTNotExecuted() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() { - // Set task as systemUI package - val systemUIPackageName = context.resources.getString( - com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - val task = - setUpFullscreenTask().apply { - baseActivity = baseComponent - isTopActivityNoDisplay = true - } - - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() { - val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) - whenever( - transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()) - ).thenReturn(Binder()) - - val task = createTaskInfo(1) - whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) - controller.moveTaskToDesktop( - taskId = task.taskId, - transitionSource = UNKNOWN, - remoteTransition = RemoteTransition(spy(TestRemoteTransition()))) - - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) - } - - - @Test - fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() { - val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) - whenever( - transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()) - ).thenReturn(Binder()) - - controller.moveRunningTaskToDesktop( - task = setUpFullscreenTask(), - transitionSource = UNKNOWN, - remoteTransition = RemoteTransition(spy(TestRemoteTransition()))) - - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() { - val homeTask = setUpHomeTask() - val freeformTask = setUpFreeformTask() - val fullscreenTask = setUpFullscreenTask() - markTaskHidden(freeformTask) - - controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN) - - with(getLatestEnterDesktopWct()) { - // Operations should include home task, freeform task - assertThat(hierarchyOps).hasSize(3) - assertReorderSequence(homeTask, freeformTask, fullscreenTask) - assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - } - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() { - val freeformTask = setUpFreeformTask() - val fullscreenTask = setUpFullscreenTask() - markTaskHidden(freeformTask) - - controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN) - - with(getLatestEnterDesktopWct()) { - // Operations should include wallpaper intent, freeform task, fullscreen task - assertThat(hierarchyOps).hasSize(3) - assertPendingIntentAt(index = 0, desktopWallpaperIntent) - assertReorderAt(index = 1, freeformTask) - assertReorderAt(index = 2, fullscreenTask) - assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - } - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() { - setUpHomeTask(displayId = DEFAULT_DISPLAY) - val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) - markTaskHidden(freeformTaskDefault) - - val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY) - val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) - markTaskHidden(freeformTaskSecond) - - controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN) - - with(getLatestEnterDesktopWct()) { - // Check that hierarchy operations do not include tasks from second display - assertThat(hierarchyOps.map { it.container }).doesNotContain(homeTaskSecond.token.asBinder()) - assertThat(hierarchyOps.map { it.container }) - .doesNotContain(freeformTaskSecond.token.asBinder()) - } - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - } - - @Test - fun moveRunningTaskToDesktop_splitTaskExitsSplit() { - val task = setUpSplitScreenTask() - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - verify(splitScreenController) - .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) - } - - @Test - fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() { - val task = setUpFullscreenTask() - controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - verify(splitScreenController, never()) - .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - val newTask = setUpFullscreenTask() - val homeTask = setUpHomeTask() - - controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) - - val wct = getLatestEnterDesktopWct() - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home - wct.assertReorderAt(0, homeTask) - wct.assertReorderSequenceInRange( - range = 1..<(MAX_TASK_LIMIT + 1), - *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] - newTask) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - val newTask = setUpFullscreenTask() - val homeTask = setUpHomeTask() - - controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) - - val wct = getLatestEnterDesktopWct() - verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) - assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper - // Move home to front - wct.assertReorderAt(0, homeTask) - // Add desktop wallpaper activity - wct.assertPendingIntentAt(1, desktopWallpaperIntent) - // Bring freeform tasks to front - wct.assertReorderSequenceInRange( - range = 2..<(MAX_TASK_LIMIT + 2), - *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] - newTask) - } - - @Test - fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() { - val task = setUpFreeformTask() - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) - val wct = getLatestExitDesktopWct() - verify(desktopModeEnterExitTransitionListener, times(1)).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) - .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) - verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) - // Removes wallpaper activity when leaving desktop - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() { - val task = setUpFreeformTask() - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) - val wct = getLatestExitDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - } - - @Test - fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) - .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - - controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) - assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN) - verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - // Removes wallpaper activity when leaving desktop - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() { - val task1 = setUpFreeformTask() - // Setup task2 - setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) - .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - val task1Change = assertNotNull(wct.changes[task1.token.asBinder()]) - assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) - verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - // Does not remove wallpaper activity, as desktop still has a visible desktop task - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun moveToFullscreen_nonExistentTask_doesNothing() { - controller.moveToFullscreen(999, transitionSource = UNKNOWN) - verifyExitDesktopWCTNotExecuted() - } - - @Test - fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { - val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) - controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN) - - with(getLatestExitDesktopWct()) { - assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) - assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) - } - verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) - } - - @Test - fun moveTaskToFront_postsWctWithReorderOp() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - controller.moveTaskToFront(task1, remoteTransition = null) - - val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) - assertThat(wct.hierarchyOps).hasSize(1) - wct.assertReorderAt(index = 0, task1) - } - - @Test - fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() { - setUpHomeTask() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } - whenever(desktopMixedTransitionHandler.startLaunchTransition( - eq(TRANSIT_TO_FRONT), - any(), - eq(freeformTasks[0].taskId), - anyOrNull(), - anyOrNull(), - )).thenReturn(Binder()) - - controller.moveTaskToFront(freeformTasks[0], remoteTransition = null) - - val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) - assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize - wct.assertReorderAt(0, freeformTasks[0], toTop = true) - wct.assertReorderAt(1, freeformTasks[1], toTop = false) - } - - @Test - fun moveTaskToFront_remoteTransition_usesOneshotHandler() { - setUpHomeTask() - val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() } - val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) - whenever( - transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()) - ).thenReturn(Binder()) - - controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition())) - - assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) - } - - @Test - fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() { - setUpHomeTask() - val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() } - val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) - whenever( - transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()) - ).thenReturn(Binder()) - - controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition())) - - assertThat(transitionHandlerArgCaptor.value) - .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java) - } - - @Test - fun moveTaskToFront_backgroundTask_launchesTask() { - val task = createTaskInfo(1) - whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - - controller.moveTaskToFront(task.taskId, remoteTransition = null) - - val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) - assertThat(wct.hierarchyOps).hasSize(1) - wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) - } - - @Test - fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - val task = createTaskInfo(1001) - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) - whenever(desktopMixedTransitionHandler - .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull())) - .thenReturn(Binder()) - - controller.moveTaskToFront(task.taskId, remoteTransition = null) - - val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) - assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize - wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) - wct.assertReorderAt(1, freeformTasks[0], toTop = false) - } - - @Test - fun moveToNextDisplay_noOtherDisplays() { - whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) - val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - controller.moveToNextDisplay(task.taskId) - verifyWCTNotExecuted() - } - - @Test - fun moveToNextDisplay_moveFromFirstToSecondDisplay() { - // Set up two display ids - whenever(rootTaskDisplayAreaOrganizer.displayIds) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) - // Create a mock for the target display area: second display - val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) - .thenReturn(secondDisplayArea) - - val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - assertThat(hierarchyOps).hasSize(1) - assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) - assertThat(hierarchyOps[0].isReparent).isTrue() - assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder()) - assertThat(hierarchyOps[0].toTop).isTrue() - } - } - - @Test - fun moveToNextDisplay_moveFromSecondToFirstDisplay() { - // Set up two display ids - whenever(rootTaskDisplayAreaOrganizer.displayIds) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) - // Create a mock for the target display area: default display - val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) - .thenReturn(defaultDisplayArea) - - val task = setUpFreeformTask(displayId = SECOND_DISPLAY) - controller.moveToNextDisplay(task.taskId) - - with(getLatestWct(type = TRANSIT_CHANGE)) { - assertThat(hierarchyOps).hasSize(1) - assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) - assertThat(hierarchyOps[0].isReparent).isTrue() - assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder()) - assertThat(hierarchyOps[0].toTop).isTrue() - } - } - - @Test - @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY) - fun moveToNextDisplay_removeWallpaper() { - // Set up two display ids - whenever(rootTaskDisplayAreaOrganizer.displayIds) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) - // Create a mock for the target display area: second display - val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) - .thenReturn(secondDisplayArea) - // Add a task and a wallpaper - val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - controller.moveToNextDisplay(task.taskId) - - with(getLatestWct(type = TRANSIT_CHANGE)) { - val wallpaperChange = hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() } - assertThat(wallpaperChange).isNotNull() - assertThat(wallpaperChange!!.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) - } - } - - @Test - fun getTaskWindowingMode() { - val fullscreenTask = setUpFullscreenTask() - val freeformTask = setUpFreeformTask() - - assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId)) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - assertThat(controller.getTaskWindowingMode(freeformTask.taskId)) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - fun onDesktopWindowClose_noActiveTasks() { - val task = setUpFreeformTask(active = false) - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() { - val task = setUpFreeformTask() - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) - // Adds remove wallpaper operation - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun onDesktopWindowClose_singleActiveTask_isClosing() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId) - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun onDesktopWindowClose_singleActiveTask_isMinimized() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun onDesktopWindowClose_multipleActiveTasks() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) - // Doesn't modify transaction - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId) - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) - // Adds remove wallpaper operation - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) - - val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) - // Adds remove wallpaper operation - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() { - val task = setUpFreeformTask(active = false) - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - controller.minimizeTask(task) - - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - captor.value.hierarchyOps.none { hop -> - hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() - } - } - - @Test - fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() { - val task = setUpPipTask(autoEnterEnabled = true) - val handler = mock(TransitionHandler::class.java) - whenever(freeformTaskTransitionStarter.startPipTransition(any())) - .thenReturn(Binder()) - whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) - .thenReturn(android.util.Pair(handler, WindowContainerTransaction()) + runningTasks.clear() + testScope.cancel() + } + + @Test + fun instantiate_addInitCallback() { + verify(shellInit).addInitCallback(any(), any<DesktopTasksController>()) + } + + @Test + fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() { + setUpFreeformTask() + + assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse() + } + + @Test + fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() { + val task1 = setUpFreeformTask() + + val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) + controller.toggleDesktopTaskSize( + task1, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + + verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task1, + STABLE_BOUNDS.width(), + STABLE_BOUNDS.height(), + displayController, + ) + assertThat(argumentCaptor.value).isTrue() + } + + @Test + fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() { + val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } + setUpFreeformTask(bounds = stableBounds, active = true) + assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() + } + + @Test + fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() { + val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } + val task1 = setUpFreeformTask(bounds = stableBounds, active = true) + + val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) + controller.toggleDesktopTaskSize( + task1, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH, + ), + ) + + verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture()) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + eq(ResizeTrigger.MAXIMIZE_BUTTON), + eq(InputMethod.TOUCH), + eq(task1), + anyOrNull(), + anyOrNull(), + eq(displayController), + anyOrNull(), + ) + assertThat(argumentCaptor.value).isFalse() + } + + @Test + fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() { + val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } + setUpFreeformTask( + bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom) + ) + + assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue() + } + + @Test + fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() { + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false) + clearInvocations(shellInit) + + createController() + + verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() { + val homeTask = setUpHomeTask() + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskHidden(task1) + markTaskHidden(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskHidden(task1) + markTaskHidden(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: wallpaper intent, task1, task2 + wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + fun isDesktopModeShowing_noTasks_returnsFalse() { + assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + fun isDesktopModeShowing_noTasksVisible_returnsFalse() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskHidden(task1) + markTaskHidden(task2) + + assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskVisible(task1) + markTaskHidden(task2) + + assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() { + val homeTask = setUpHomeTask(SECOND_DISPLAY) + val task1 = setUpFreeformTask(SECOND_DISPLAY) + val task2 = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(task1) + markTaskHidden(task2) + + controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 (no wallpaper intent) + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() { + val homeTask = setUpHomeTask() + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskVisible(task1) + markTaskVisible(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() { + val homeTask = setUpHomeTask(SECOND_DISPLAY) + val task1 = setUpFreeformTask(SECOND_DISPLAY) + val task2 = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(task1) + markTaskHidden(task2) + + controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskVisible(task1) + markTaskVisible(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: wallpaper intent, task1, task2 + wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() { + val homeTask = setUpHomeTask() + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskHidden(task1) + markTaskVisible(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + markTaskHidden(task1) + markTaskVisible(task2) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: wallpaper intent, task1, task2 + wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() { + val homeTask = setUpHomeTask() + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertReorderAt(index = 0, homeTask) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() { + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() { + val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) + val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) + setUpHomeTask(SECOND_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(taskDefaultDisplay) + markTaskHidden(taskSecondDisplay) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(2) + // Expect order to be from bottom: home, task + wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) + wct.assertReorderAt(index = 1, taskDefaultDisplay) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() { + val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) + val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) + setUpHomeTask(SECOND_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY) + markTaskHidden(taskDefaultDisplay) + markTaskHidden(taskSecondDisplay) + + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Move home to front + wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) + // Add desktop wallpaper activity + wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent) + // Move freeform task to front + wct.assertReorderAt(index = 2, taskDefaultDisplay) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val minimizedTask = setUpFreeformTask() + + markTaskHidden(freeformTask) + markTaskHidden(minimizedTask) + taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(2) + // Reorder home and freeform task to top, don't reorder the minimized task + wct.assertReorderAt(index = 0, homeTask, toTop = true) + wct.assertReorderAt(index = 1, freeformTask, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val minimizedTask = setUpFreeformTask() + + markTaskHidden(freeformTask) + markTaskHidden(minimizedTask) + taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(3) + // Move home to front + wct.assertReorderAt(index = 0, homeTask, toTop = true) + // Add desktop wallpaper activity + wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent) + // Reorder freeform task to top, don't reorder the minimized task + wct.assertReorderAt(index = 2, freeformTask, toTop = true) + } + + @Test + fun visibleTaskCount_noTasks_returnsZero() { + assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) + } + + @Test + fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() { + setUpHomeTask() + setUpFreeformTask().also(::markTaskVisible) + setUpFreeformTask().also(::markTaskVisible) + assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2) + } + + @Test + fun visibleTaskCount_twoTasks_oneVisible_returnsOne() { + setUpHomeTask() + setUpFreeformTask().also(::markTaskVisible) + setUpFreeformTask().also(::markTaskHidden) + assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1) + } + + @Test + fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + setUpHomeTask() + setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible) + setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible) + assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1) + } + + @Test + fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(gravity = Gravity.LEFT) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(finalBounds).isEqualTo(Rect()) + } + + @Test + fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(gravity = Gravity.RIGHT) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(finalBounds).isEqualTo(Rect()) + } + + @Test + fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(gravity = Gravity.TOP) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(finalBounds).isEqualTo(Rect()) + } + + @Test + fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(finalBounds).isEqualTo(Rect()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun handleRequest_newFreeformTaskLaunch_cascadeApplied() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false) + + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + val finalBounds = findBoundsChange(wct, freeformTask) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.BottomRight) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNull(wct, "should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_positionBottomRight() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.BottomRight) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_positionTopLeft() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.TopLeft) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_positionBottomLeft() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.BottomLeft) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_positionTopRight() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.TopRight) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_positionResetsToCenter() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.Center) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + // Add freeform task with half display size snap bounds at left side. + setUpFreeformTask( + bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom) + ) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.Center) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + // Add freeform task with half display size snap bounds at right side. + setUpFreeformTask( + bounds = + Rect( + stableBounds.right - 500, + stableBounds.top, + stableBounds.right, + stableBounds.bottom, + ) + ) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.Center) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + // Add maximised freeform task. + setUpFreeformTask(bounds = Rect(stableBounds)) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.Center) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) + fun addMoveToDesktopChanges_defaultToCenterIfFree() { + setUpLandscapeDisplay() + val stableBounds = Rect() + displayLayout.getStableBoundsForDesktopMode(stableBounds) + + val minTouchTarget = + context.resources.getDimensionPixelSize( + R.dimen.freeform_required_visible_empty_space_in_header + ) + addFreeformTaskAtPosition( + DesktopTaskPosition.Center, + stableBounds, + Rect(0, 0, 1600, 1200), + Point(0, minTouchTarget + 1), + ) + + val task = setUpFullscreenTask() + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + val finalBounds = findBoundsChange(wct, task) + assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) + .isEqualTo(DesktopTaskPosition.Center) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(enableUserFullscreenOverride = true) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { + setUpLandscapeDisplay() + val task = + setUpFullscreenTask( + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true, + aspectRatioOverrideApplied = true, + ) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { + setUpPortraitDisplay() + val task = setUpFullscreenTask(enableUserFullscreenOverride = true) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { + setUpPortraitDisplay() + val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { + setUpPortraitDisplay() + val task = + setUpFullscreenTask( + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, + deviceOrientation = ORIENTATION_PORTRAIT, + shouldLetterbox = true, + aspectRatioOverrideApplied = true, + ) + val wct = WindowContainerTransaction() + controller.addMoveToDesktopChanges(wct, task) + + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { + val task = setUpFullscreenTask() + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() { + val task = setUpFullscreenTask() + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + fun moveTaskToDesktop_nonExistentTask_doesNothing() { + controller.moveTaskToDesktop(999, transitionSource = UNKNOWN) + verifyEnterDesktopWCTNotExecuted() + verify(desktopModeEnterExitTransitionListener, times(0)) + .onEnterDesktopModeTransitionStarted(anyInt()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() { + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + + controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() { + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + + controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + // Add desktop wallpaper activity + assertPendingIntentAt(index = 0, desktopWallpaperIntent) + // Launch task + assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM) + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() { + val task = + setUpFullscreenTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = true + numActivities = 1 + } + + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() { + val task = + setUpFullscreenTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + } + + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + verifyEnterDesktopWCTNotExecuted() + verify(desktopModeEnterExitTransitionListener, times(0)) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() { + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + verifyEnterDesktopWCTNotExecuted() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() { + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() { + val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) + whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())) + .thenReturn(Binder()) + + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + controller.moveTaskToDesktop( + taskId = task.taskId, + transitionSource = UNKNOWN, + remoteTransition = RemoteTransition(spy(TestRemoteTransition())), + ) + + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) + } + + @Test + fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() { + val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) + whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())) + .thenReturn(Binder()) + + controller.moveRunningTaskToDesktop( + task = setUpFullscreenTask(), + transitionSource = UNKNOWN, + remoteTransition = RemoteTransition(spy(TestRemoteTransition())), + ) + + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val fullscreenTask = setUpFullscreenTask() + markTaskHidden(freeformTask) + + controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + // Operations should include home task, freeform task + assertThat(hierarchyOps).hasSize(3) + assertReorderSequence(homeTask, freeformTask, fullscreenTask) + assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() { + val freeformTask = setUpFreeformTask() + val fullscreenTask = setUpFullscreenTask() + markTaskHidden(freeformTask) + + controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + // Operations should include wallpaper intent, freeform task, fullscreen task + assertThat(hierarchyOps).hasSize(3) + assertPendingIntentAt(index = 0, desktopWallpaperIntent) + assertReorderAt(index = 1, freeformTask) + assertReorderAt(index = 2, fullscreenTask) + assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() { + setUpHomeTask(displayId = DEFAULT_DISPLAY) + val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + markTaskHidden(freeformTaskDefault) + + val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY) + val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) + markTaskHidden(freeformTaskSecond) + + controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + // Check that hierarchy operations do not include tasks from second display + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(homeTaskSecond.token.asBinder()) + assertThat(hierarchyOps.map { it.container }) + .doesNotContain(freeformTaskSecond.token.asBinder()) + } + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + } + + @Test + fun moveRunningTaskToDesktop_splitTaskExitsSplit() { + val task = setUpSplitScreenTask() + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + verify(splitScreenController) + .prepareExitSplitScreen( + any(), + anyInt(), + eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE), + ) + } + + @Test + fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() { + val task = setUpFullscreenTask() + controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN) + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + verify(splitScreenController, never()) + .prepareExitSplitScreen( + any(), + anyInt(), + eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE), + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + val newTask = setUpFullscreenTask() + val homeTask = setUpHomeTask() + + controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home + wct.assertReorderAt(0, homeTask) + wct.assertReorderSequenceInRange( + range = 1..<(MAX_TASK_LIMIT + 1), + *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] + newTask, + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + val newTask = setUpFullscreenTask() + val homeTask = setUpHomeTask() + + controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + verify(desktopModeEnterExitTransitionListener) + .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper + // Move home to front + wct.assertReorderAt(0, homeTask) + // Add desktop wallpaper activity + wct.assertPendingIntentAt(1, desktopWallpaperIntent) + // Bring freeform tasks to front + wct.assertReorderSequenceInRange( + range = 2..<(MAX_TASK_LIMIT + 2), + *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] + newTask, + ) + } + + @Test + fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() { + val task = setUpFreeformTask() + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) + val wct = getLatestExitDesktopWct() + verify(desktopModeEnterExitTransitionListener, times(1)) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .configuration + .windowConfiguration + .windowingMode = WINDOWING_MODE_FULLSCREEN + + controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) + verify(desktopModeEnterExitTransitionListener) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) + // Removes wallpaper activity when leaving desktop + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() { + val task = setUpFreeformTask() + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) + val wct = getLatestExitDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + verify(desktopModeEnterExitTransitionListener) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + } + + @Test + fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .configuration + .windowConfiguration + .windowingMode = WINDOWING_MODE_FREEFORM + + controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val taskChange = assertNotNull(wct.changes[task.token.asBinder()]) + assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN) + verify(desktopModeEnterExitTransitionListener) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + // Removes wallpaper activity when leaving desktop + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() { + val task1 = setUpFreeformTask() + // Setup task2 + setUpFreeformTask() + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .configuration + .windowConfiguration + .windowingMode = WINDOWING_MODE_FULLSCREEN + + controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val task1Change = assertNotNull(wct.changes[task1.token.asBinder()]) + assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) + verify(desktopModeEnterExitTransitionListener) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + // Does not remove wallpaper activity, as desktop still has a visible desktop task + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun moveToFullscreen_nonExistentTask_doesNothing() { + controller.moveToFullscreen(999, transitionSource = UNKNOWN) + verifyExitDesktopWCTNotExecuted() + } + + @Test + fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { + val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) + controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN) + + with(getLatestExitDesktopWct()) { + assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) + assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) + } + verify(desktopModeEnterExitTransitionListener) + .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + } + + @Test + fun moveTaskToFront_postsWctWithReorderOp() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + controller.moveTaskToFront(task1, remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertReorderAt(index = 0, task1) + } + + @Test + fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() { + setUpHomeTask() + val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(freeformTasks[0].taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(freeformTasks[0], remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize + wct.assertReorderAt(0, freeformTasks[0], toTop = true) + wct.assertReorderAt(1, freeformTasks[1], toTop = false) + } + + @Test + fun moveTaskToFront_remoteTransition_usesOneshotHandler() { + setUpHomeTask() + val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() } + val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) + whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())) + .thenReturn(Binder()) + + controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition())) + + assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value) + } + + @Test + fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() { + setUpHomeTask() + val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() } + val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java) + whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())) + .thenReturn(Binder()) + + controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition())) + + assertThat(transitionHandlerArgCaptor.value) + .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java) + } + + @Test + fun moveTaskToFront_backgroundTask_launchesTask() { + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + + controller.moveTaskToFront(task.taskId, remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } + + @Test + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + val task = createTaskInfo(1001) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + eq(task.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(task.taskId, remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize + wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + wct.assertReorderAt(1, freeformTasks[0], toTop = false) + } + + @Test + fun moveToNextDisplay_noOtherDisplays() { + whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + controller.moveToNextDisplay(task.taskId) + verifyWCTNotExecuted() + } + + @Test + fun moveToNextDisplay_moveFromFirstToSecondDisplay() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + controller.moveToNextDisplay(task.taskId) + with(getLatestWct(type = TRANSIT_CHANGE)) { + assertThat(hierarchyOps).hasSize(1) + assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) + assertThat(hierarchyOps[0].isReparent).isTrue() + assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder()) + assertThat(hierarchyOps[0].toTop).isTrue() + } + } + + @Test + fun moveToNextDisplay_moveFromSecondToFirstDisplay() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: default display + val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultDisplayArea) + + val task = setUpFreeformTask(displayId = SECOND_DISPLAY) + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + assertThat(hierarchyOps).hasSize(1) + assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) + assertThat(hierarchyOps[0].isReparent).isTrue() + assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder()) + assertThat(hierarchyOps[0].toTop).isTrue() + } + } + + @Test + @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY) + fun moveToNextDisplay_removeWallpaper() { + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + // Add a task and a wallpaper + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + controller.moveToNextDisplay(task.taskId) + + with(getLatestWct(type = TRANSIT_CHANGE)) { + val wallpaperChange = + hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() } + assertThat(wallpaperChange).isNotNull() + assertThat(wallpaperChange!!.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + } + } + + @Test + fun getTaskWindowingMode() { + val fullscreenTask = setUpFullscreenTask() + val freeformTask = setUpFreeformTask() + + assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId)) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(controller.getTaskWindowingMode(freeformTask.taskId)) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun onDesktopWindowClose_noActiveTasks() { + val task = setUpFreeformTask(active = false) + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() { + val task = setUpFreeformTask() + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowClose_singleActiveTask_isClosing() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_singleActiveTask_isMinimized() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_multipleActiveTasks() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() { + val task = setUpFreeformTask(active = false) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test + fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() { + val task = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder()) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startPipTransition(any()) + verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) + } + + @Test + fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() { + val task = setUpPipTask(autoEnterEnabled = false) + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(Binder()) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) + verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) + } + + @Test + fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { + val task = setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK } + } + + @Test + fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() { + val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + // The only active task is being minimized. + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + // Adds remove wallpaper operation + captor.value.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() { + val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) + + // The only active task is already minimized. + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test + fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() { + val task1 = setUpFreeformTask(active = true) + setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + controller.minimizeTask(task1) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test + fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() { + val task1 = setUpFreeformTask(active = true) + val task2 = setUpFreeformTask(active = true) + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + // task1 is the only visible task as task2 is minimized. + controller.minimizeTask(task1) + // Adds remove wallpaper operation + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) + // Adds remove wallpaper operation + captor.value.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowMinimize_triesToExitImmersive() { + val task = setUpFreeformTask() + val transition = Binder() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + + controller.minimizeTask(task) + + verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) + } + + @Test + fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() { + val task = setUpFreeformTask() + val transition = Binder() + val runOnTransit = RunOnStartTransitionCallback() + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(transition) + whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) + .thenReturn( + ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit) + ) + + controller.minimizeTask(task) + + assertThat(runOnTransit.invocations).isEqualTo(1) + assertThat(runOnTransit.lastInvoked).isEqualTo(transition) + } + + @Test + fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + + assertThat(wct.hierarchyOps).hasSize(1) + } + + @Test + fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + + // There are 5 hops that are happening in this case: + // 1. Moving the fullscreen task to top as we add moveToDesktop() changes + // 2. Bringing home task to front + // 3. Pending intent for the wallpaper + // 4. Bringing the existing freeform task to top + // 5. Bringing the fullscreen task back at the top + assertThat(wct.hierarchyOps).hasSize(5) + wct.assertReorderAt(1, homeTask, toTop = true) + wct.assertReorderAt(4, fullscreenTask, toTop = true) + } + + @Test + fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + // Make sure we only reorder the new task to top (we don't reorder the old task to bottom) + assertThat(wct?.hierarchyOps?.size).isEqualTo(1) + wct!!.assertReorderAt(0, fullscreenTask, toTop = true) + } + + @Test + fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + // Make sure we reorder the new task to top, and the back task to the bottom + assertThat(wct!!.hierarchyOps.size).isEqualTo(2) + wct.assertReorderAt(0, fullscreenTask, toTop = true) + wct.assertReorderAt(1, freeformTasks[0], toTop = false) + } + + @Test + fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + // Make sure we reorder the new task to top, and the back task to the bottom + assertThat(wct!!.hierarchyOps.size).isEqualTo(9) + wct.assertReorderAt(0, fullscreenTask, toTop = true) + wct.assertReorderAt(8, freeformTasks[0], toTop = false) + } + + @Test + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() { + val minimizedTask = setUpFreeformTask() + taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId) + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val homeTask = setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertThat(wct!!.hierarchyOps.size).isEqualTo(10) + wct.assertReorderAt(0, fullscreenTask, toTop = true) + // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized + // task is under the home task. + wct.assertReorderAt(1, homeTask, toTop = true) + wct.assertReorderAt(9, freeformTasks[0], toTop = false) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + assertThat(wct.hierarchyOps).hasSize(3) + // There are 3 hops that are happening in this case: + // 1. Moving the fullscreen task to top as we add moveToDesktop() changes + // 2. Pending intent for the wallpaper + // 3. Bringing the fullscreen task back at the top + wct.assertPendingIntentAt(1, desktopWallpaperIntent) + wct.assertReorderAt(2, fullscreenTask, toTop = true) + } + + @Test + fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertThat(wct).isNull() + } + + @Test + fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val fullscreenTask = createFullscreenTask() + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + fun handleRequest_fullscreenTask_noOtherTasks_returnNull() { + val fullscreenTask = createFullscreenTask() + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { + val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = + controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) + assertThat(result).isNull() + } + + @Test + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { + val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + freeformTasks.forEach { markTaskVisible(it) } + val newFreeformTask = createFreeformTask() + + val wct = + controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN)) + + assertThat(wct?.hierarchyOps?.size).isEqualTo(1) + wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom + } + + @Test + fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. + assertNotNull(wct, "should handle request") + assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertFalse(wct.anyWindowingModeChange(freeformTask.token)) + } + + @Test + fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + + val freeformTask = setUpFreeformTask() + markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { + val freeformTask1 = setUpFreeformTask() + val freeformTask2 = createFreeformTask() + + markTaskHidden(freeformTask1) + val result = + controller.handleRequest( + Binder(), + createTransition(freeformTask2, type = TRANSIT_TO_FRONT), + ) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + result.assertReorderAt(1, freeformTask2, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() { + val freeformTask1 = setUpFreeformTask() + val freeformTask2 = createFreeformTask() + + markTaskHidden(freeformTask1) + val result = + controller.handleRequest( + Binder(), + createTransition(freeformTask2, type = TRANSIT_TO_FRONT), + ) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(3) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring active desktop tasks to front + result.assertReorderAt(1, freeformTask1, toTop = true) + // Bring new task to front + result.assertReorderAt(2, freeformTask2, toTop = true) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { + val task = createFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, task, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() { + val task = createFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring new task to front + result.assertReorderAt(1, task, toTop = true) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() { + val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) + // Second display task + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, taskDefaultDisplay, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() { + val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) + // Second display task + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring new task to front + result.assertReorderAt(1, taskDefaultDisplay, toTop = true) + } + + @Test + fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() { + whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false) + + val freeformTask1 = setUpFreeformTask() + markTaskVisible(freeformTask1) + + val freeformTask2 = createFreeformTask() + val result = + controller.handleRequest( + freeformTask2.token.asBinder(), + createTransition(freeformTask2), + ) + assertFalse(result.anyDensityConfigChange(freeformTask2.token)) + } + + @Test + fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() { + whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true) + + val freeformTask1 = setUpFreeformTask() + markTaskVisible(freeformTask1) + + val freeformTask2 = createFreeformTask() + val result = + controller.handleRequest( + freeformTask2.token.asBinder(), + createTransition(freeformTask2), + ) + assertTrue(result.anyDensityConfigChange(freeformTask2.token)) + } + + @Test + fun handleRequest_freeformTask_keyguardLocked_returnNull() { + whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNull(result, "Should NOT handle request") + } + + @Test + fun handleRequest_notOpenOrToFrontTransition_returnNull() { + val task = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build() + val transition = createTransition(task = task, type = TRANSIT_CLOSE) + val result = controller.handleRequest(Binder(), transition) + assertThat(result).isNull() + } + + @Test + fun handleRequest_noTriggerTask_returnNull() { + assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull() + } + + @Test + fun handleRequest_triggerTaskNotStandard_returnNull() { + val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build() + assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() + } + + @Test + fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() { + val task = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .build() + assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() + } + + @Test + fun handleRequest_recentsAnimationRunning_returnNull() { + // Set up a visible freeform task so a fullscreen task should be converted to freeform + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + // Mark recents animation running + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) + + // Open a fullscreen task, check that it does not result in a WCT with changes to it + val fullscreenTask = createFullscreenTask() + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() { + // Set up a visible freeform task + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + // Mark recents animation running + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) + + // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + val task = + setUpFullscreenTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = true + numActivities = 1 + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + val task = + setUpFreeformTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = false + numActivities = 1 + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFreeformTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val task = + setUpFullscreenTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { + val task = setUpFreeformTask() + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() { + val task = setUpFreeformTask() + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() { + val task = setUpFreeformTask() + markTaskHidden(task) + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() { + val task = setUpFreeformTask() + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { + val task = setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleTasks_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, ) + fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + // Task is being minimized so mark it as not visible. + taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false) + val result = + controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { + val task = setUpFreeformTask() + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() { + val task = setUpFreeformTask() + + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { + val task = setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + val result = + controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + taskRepository.wallpaperActivityToken = MockToken().token() + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = + controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + // Should create remove wallpaper transaction + assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + // Task is being minimized so mark it as not visible. + taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false) + val result = + controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } - controller.minimizeTask(task) - - verify(freeformTaskTransitionStarter).startPipTransition(any()) - verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) - } - - @Test - fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() { - val task = setUpPipTask(autoEnterEnabled = false) - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(Binder()) - - controller.minimizeTask(task) - - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) - verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) - } - - @Test - fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { - val task = setUpFreeformTask(active = true) - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - - controller.minimizeTask(task) - - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - captor.value.hierarchyOps.none { hop -> - hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK - } - } - - @Test - fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() { - val task = setUpFreeformTask() - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - // The only active task is being minimized. - controller.minimizeTask(task) - - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - // Adds remove wallpaper operation - captor.value.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() { - val task = setUpFreeformTask() - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) - - // The only active task is already minimized. - controller.minimizeTask(task) - - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - captor.value.hierarchyOps.none { hop -> - hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() - } - } - - @Test - fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() { - val task1 = setUpFreeformTask(active = true) - setUpFreeformTask(active = true) - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - - controller.minimizeTask(task1) - - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - captor.value.hierarchyOps.none { hop -> - hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() - } - } - - @Test - fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() { - val task1 = setUpFreeformTask(active = true) - val task2 = setUpFreeformTask(active = true) - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - val wallpaperToken = MockToken().token() - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) - - // task1 is the only visible task as task2 is minimized. - controller.minimizeTask(task1) - // Adds remove wallpaper operation - val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) - // Adds remove wallpaper operation - captor.value.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun onDesktopWindowMinimize_triesToExitImmersive() { - val task = setUpFreeformTask() - val transition = Binder() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - - controller.minimizeTask(task) - - verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) - } - - @Test - fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() { - val task = setUpFreeformTask() - val transition = Binder() - val runOnTransit = RunOnStartTransitionCallback() - whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) - .thenReturn(transition) - whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = task.taskId, - runOnTransitionStart = runOnTransit, - )) - - controller.minimizeTask(task) - - assertThat(runOnTransit.invocations).isEqualTo(1) - assertThat(runOnTransit.lastInvoked).isEqualTo(transition) - } - - @Test - fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { - val homeTask = setUpHomeTask() - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - val fullscreenTask = createFullscreenTask() - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - assertNotNull(wct, "should handle request") - assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - - assertThat(wct.hierarchyOps).hasSize(1) - } - - @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() { - val homeTask = setUpHomeTask() - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - val fullscreenTask = createFullscreenTask() - fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - assertNotNull(wct, "should handle request") - assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - - // There are 5 hops that are happening in this case: - // 1. Moving the fullscreen task to top as we add moveToDesktop() changes - // 2. Bringing home task to front - // 3. Pending intent for the wallpaper - // 4. Bringing the existing freeform task to top - // 5. Bringing the fullscreen task back at the top - assertThat(wct.hierarchyOps).hasSize(5) - wct.assertReorderAt(1, homeTask, toTop = true) - wct.assertReorderAt(4, fullscreenTask, toTop = true) - } - - @Test - fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() { - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - val fullscreenTask = createFullscreenTask() - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - // Make sure we only reorder the new task to top (we don't reorder the old task to bottom) - assertThat(wct?.hierarchyOps?.size).isEqualTo(1) - wct!!.assertReorderAt(0, fullscreenTask, toTop = true) - } - - @Test - fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - freeformTasks.forEach { markTaskVisible(it) } - val fullscreenTask = createFullscreenTask() - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - // Make sure we reorder the new task to top, and the back task to the bottom - assertThat(wct!!.hierarchyOps.size).isEqualTo(2) - wct.assertReorderAt(0, fullscreenTask, toTop = true) - wct.assertReorderAt(1, freeformTasks[0], toTop = false) - } - - @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - freeformTasks.forEach { markTaskVisible(it) } - val fullscreenTask = createFullscreenTask() - fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - // Make sure we reorder the new task to top, and the back task to the bottom - assertThat(wct!!.hierarchyOps.size).isEqualTo(9) - wct.assertReorderAt(0, fullscreenTask, toTop = true) - wct.assertReorderAt(8, freeformTasks[0], toTop = false) - } - - @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() { - val minimizedTask = setUpFreeformTask() - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId) - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - freeformTasks.forEach { markTaskVisible(it) } - val homeTask = setUpHomeTask() - val fullscreenTask = createFullscreenTask() - fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) - - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - assertThat(wct!!.hierarchyOps.size).isEqualTo(10) - wct.assertReorderAt(0, fullscreenTask, toTop = true) - // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized - // task is under the home task. - wct.assertReorderAt(1, homeTask, toTop = true) - wct.assertReorderAt(9, freeformTasks[0], toTop = false) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() { - whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - - val fullscreenTask = createFullscreenTask() - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - assertNotNull(wct, "should handle request") - assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - assertThat(wct.hierarchyOps).hasSize(3) - // There are 3 hops that are happening in this case: - // 1. Moving the fullscreen task to top as we add moveToDesktop() changes - // 2. Pending intent for the wallpaper - // 3. Bringing the fullscreen task back at the top - wct.assertPendingIntentAt(1, desktopWallpaperIntent) - wct.assertReorderAt(2, fullscreenTask, toTop = true) - } - - @Test - fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() { - whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - val fullscreenTask = createFullscreenTask() - val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - - assertThat(wct).isNull() - } - - @Test - fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { - val freeformTask = setUpFreeformTask() - markTaskHidden(freeformTask) - val fullscreenTask = createFullscreenTask() - assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() - } - - @Test - fun handleRequest_fullscreenTask_noOtherTasks_returnNull() { - val fullscreenTask = createFullscreenTask() - assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() - } - - @Test - fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { - val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) - createFreeformTask(displayId = SECOND_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) - assertThat(result).isNull() - } - - @Test - fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } - freeformTasks.forEach { markTaskVisible(it) } - val newFreeformTask = createFreeformTask() - - val wct = controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN)) - - assertThat(wct?.hierarchyOps?.size).isEqualTo(1) - wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom - } - - @Test - fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { - val freeformTask = setUpFreeformTask() - markTaskHidden(freeformTask) - - val wct = - controller.handleRequest(Binder(), createTransition(freeformTask)) - - // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. - assertNotNull(wct, "should handle request") - assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() { - whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - - val freeformTask = setUpFreeformTask() - markTaskHidden(freeformTask) - val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) - - assertNotNull(wct, "should handle request") - assertFalse(wct.anyWindowingModeChange(freeformTask.token)) - } - - @Test - fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() { - whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - val freeformTask = setUpFreeformTask() - markTaskHidden(freeformTask) - val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) - - assertNotNull(wct, "should handle request") - assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { - val freeformTask1 = setUpFreeformTask() - val freeformTask2 = createFreeformTask() - - markTaskHidden(freeformTask1) - val result = - controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(2) - result.assertReorderAt(1, freeformTask2, toTop = true) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() { - val freeformTask1 = setUpFreeformTask() - val freeformTask2 = createFreeformTask() - - markTaskHidden(freeformTask1) - val result = - controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(3) - // Add desktop wallpaper activity - result.assertPendingIntentAt(0, desktopWallpaperIntent) - // Bring active desktop tasks to front - result.assertReorderAt(1, freeformTask1, toTop = true) - // Bring new task to front - result.assertReorderAt(2, freeformTask2, toTop = true) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { - val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(1) - result.assertReorderAt(0, task, toTop = true) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() { - val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(2) - // Add desktop wallpaper activity - result.assertPendingIntentAt(0, desktopWallpaperIntent) - // Bring new task to front - result.assertReorderAt(1, task, toTop = true) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() { - val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) - // Second display task - createFreeformTask(displayId = SECOND_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(1) - result.assertReorderAt(0, taskDefaultDisplay, toTop = true) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() { - val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) - // Second display task - createFreeformTask(displayId = SECOND_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) - - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(2) - // Add desktop wallpaper activity - result.assertPendingIntentAt(0, desktopWallpaperIntent) - // Bring new task to front - result.assertReorderAt(1, taskDefaultDisplay, toTop = true) - } - - @Test - fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() { - whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false) - - val freeformTask1 = setUpFreeformTask() - markTaskVisible(freeformTask1) - - val freeformTask2 = createFreeformTask() - val result = - controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2)) - assertFalse(result.anyDensityConfigChange(freeformTask2.token)) - } - - @Test - fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() { - whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true) - - val freeformTask1 = setUpFreeformTask() - markTaskVisible(freeformTask1) - - val freeformTask2 = createFreeformTask() - val result = - controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2)) - assertTrue(result.anyDensityConfigChange(freeformTask2.token)) - } - - @Test - fun handleRequest_freeformTask_keyguardLocked_returnNull() { - whenever(keyguardManager.isKeyguardLocked).thenReturn(true) - val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - - assertNull(result, "Should NOT handle request") - } - - @Test - fun handleRequest_notOpenOrToFrontTransition_returnNull() { - val task = - TestRunningTaskInfoBuilder() - .setActivityType(ACTIVITY_TYPE_STANDARD) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN) - .build() - val transition = createTransition(task = task, type = TRANSIT_CLOSE) - val result = controller.handleRequest(Binder(), transition) - assertThat(result).isNull() - } - - @Test - fun handleRequest_noTriggerTask_returnNull() { - assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull() - } - - @Test - fun handleRequest_triggerTaskNotStandard_returnNull() { - val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build() - assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() - } - - @Test - fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() { - val task = - TestRunningTaskInfoBuilder() - .setActivityType(ACTIVITY_TYPE_STANDARD) - .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) - .build() - assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() - } - - @Test - fun handleRequest_recentsAnimationRunning_returnNull() { - // Set up a visible freeform task so a fullscreen task should be converted to freeform - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - // Mark recents animation running - recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) - - // Open a fullscreen task, check that it does not result in a WCT with changes to it - val fullscreenTask = createFullscreenTask() - assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() - } - - @Test - fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() { - // Set up a visible freeform task - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - // Mark recents animation running - recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) - - // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - val task = - setUpFullscreenTask().apply { - isActivityStackTransparent = true - isTopActivityNoDisplay = true - numActivities = 1 - } - - val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + @Test + fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() { + val task1 = setUpFullscreenTask() + val task2 = setUpFullscreenTask() + val task3 = setUpFullscreenTask() + + task1.isFocused = true + task2.isFocused = false + task3.isFocused = false + + controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task1.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() { - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - val task = - setUpFreeformTask().apply { - isActivityStackTransparent = true - isTopActivityNoDisplay = false - numActivities = 1 - } - - val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + } + + @Test + fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() { + val task1 = setUpSplitScreenTask() + val task2 = setUpFullscreenTask() + val task3 = setUpFullscreenTask() + val task4 = setUpSplitScreenTask() + + task1.isFocused = true + task2.isFocused = false + task3.isFocused = false + task4.isFocused = true + + task4.parentTaskId = task1.taskId + + controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task4.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController) + .prepareExitSplitScreen( + any(), + anyInt(), + eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE), + ) + } + + @Test + fun moveFocusedTaskToFullscreen() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + + controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + assertThat(wct.changes[task2.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() { - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - // Set task as systemUI package - val systemUIPackageName = context.resources.getString( - com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - val task = - setUpFreeformTask().apply { - baseActivity = baseComponent - isTopActivityNoDisplay = false - } - - val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + } + + @Test + fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId) + taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false) + + controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val taskChange = assertNotNull(wct.changes[task2.token.asBinder()]) + assertThat(taskChange.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + taskRepository.wallpaperActivityToken = wallpaperToken + controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val taskChange = assertNotNull(wct.changes[task2.token.asBinder()]) + assertThat(taskChange.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN - } + // Does not remove wallpaper activity, as desktop still has visible desktop tasks + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeDesktop_multipleTasks_removesAll() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + controller.removeDesktop(displayId = DEFAULT_DISPLAY) + + val wct = getLatestWct(TRANSIT_CLOSE) + assertThat(wct.hierarchyOps).hasSize(3) + wct.assertRemoveAt(index = 0, task1.token) + wct.assertRemoveAt(index = 1, task2.token) + wct.assertRemoveAt(index = 2, task3.token) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null) + + controller.removeDesktop(displayId = DEFAULT_DISPLAY) + + val wct = getLatestWct(TRANSIT_CLOSE) + assertThat(wct.hierarchyOps).hasSize(2) + wct.assertRemoveAt(index = 0, task1.token) + wct.assertRemoveAt(index = 1, task2.token) + verify(recentTasksController).removeBackgroundTask(task3.taskId) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { - val freeformTask = setUpFreeformTask() - markTaskVisible(freeformTask) - - // Set task as systemUI package - val systemUIPackageName = context.resources.getString( - com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - val task = - setUpFullscreenTask().apply { - baseActivity = baseComponent - isTopActivityNoDisplay = true - } - - val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - ) - fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() { - val task = setUpFreeformTask() - markTaskHidden(task) - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { - val task = setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleTasks_doesNotHandle() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - ) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - // Task is being minimized so mark it as not visible. - taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false) - val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() { - val task = setUpFreeformTask() - - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - assertNull(result, "Should not handle request") - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() { - val task = setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() { - val task = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - assertNull(result, "Should not handle request") - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,) - fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() { - val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) - val wallpaperToken = MockToken().token() - - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) - // Task is being minimized so mark it as not visible. - taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false) - val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK)) - - assertNull(result, "Should not handle request") - } - - @Test - fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() { - val task1 = setUpFullscreenTask() - val task2 = setUpFullscreenTask() - val task3 = setUpFullscreenTask() - - task1.isFocused = true - task2.isFocused = false - task3.isFocused = false - - controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task1.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - } - - @Test - fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() { - val task1 = setUpSplitScreenTask() - val task2 = setUpFullscreenTask() - val task3 = setUpFullscreenTask() - val task4 = setUpSplitScreenTask() - - task1.isFocused = true - task2.isFocused = false - task3.isFocused = false - task4.isFocused = true - - task4.parentTaskId = task1.taskId - - controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - - val wct = getLatestEnterDesktopWct() - assertThat(wct.changes[task4.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - verify(splitScreenController) - .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)) - } - - @Test - fun moveFocusedTaskToFullscreen() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - - controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - assertThat(wct.changes[task2.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN - } - - @Test - fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId) - taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false) - - controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - val taskChange = assertNotNull(wct.changes[task2.token.asBinder()]) - assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN - wct.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - taskRepository.wallpaperActivityToken = wallpaperToken - controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) - - val wct = getLatestExitDesktopWct() - val taskChange = assertNotNull(wct.changes[task2.token.asBinder()]) - assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN - // Does not remove wallpaper activity, as desktop still has visible desktop tasks - assertThat(wct.hierarchyOps).isEmpty() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun removeDesktop_multipleTasks_removesAll() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) - - controller.removeDesktop(displayId = DEFAULT_DISPLAY) - - val wct = getLatestWct(TRANSIT_CLOSE) - assertThat(wct.hierarchyOps).hasSize(3) - wct.assertRemoveAt(index = 0, task1.token) - wct.assertRemoveAt(index = 1, task2.token) - wct.assertRemoveAt(index = 2, task3.token) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) - whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null) - - controller.removeDesktop(displayId = DEFAULT_DISPLAY) - - val wct = getLatestWct(TRANSIT_CLOSE) - assertThat(wct.hierarchyOps).hasSize(2) - wct.assertRemoveAt(index = 0, task1.token) - wct.assertRemoveAt(index = 1, task2.token) - verify(recentTasksController).removeBackgroundTask(task3.taskId) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = setUpFullscreenTask() - setUpLandscapeDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) - setUpLandscapeDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) - setUpLandscapeDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) - setUpLandscapeDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask( - isResizable = false, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT, - shouldLetterbox = true) - setUpLandscapeDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask( - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask( - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, - shouldLetterbox = true) - setUpPortraitDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask( - isResizable = false, - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { - val spyController = spy(controller) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) - - val task = - setUpFullscreenTask( - isResizable = false, - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, - shouldLetterbox = true) - setUpPortraitDisplay() - - spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface) - val wct = getLatestDragToDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) - } - - @Test - fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { - val task = setUpFreeformTask() - val spyController = spy(controller) - val mockSurface = mock(SurfaceControl::class.java) - val mockDisplayLayout = mock(DisplayLayout::class.java) - whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000)) - - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) - spyController.onDragPositioningEnd( - task, - mockSurface, - Point(100, -100), /* position */ - PointF(200f, -200f), /* inputCoordinate */ - Rect(100, -100, 500, 1000), /* currentDragBounds */ - Rect(0, 50, 2000, 2000), /* validDragArea */ - Rect() /* dragStartBounds */, - motionEvent, - desktopWindowDecoration, + val task = setUpFullscreenTask() + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true, + ) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + isResizable = false, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, + ) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + isResizable = false, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true, + ) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + ) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, + shouldLetterbox = true, + ) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + ) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = + setUpFullscreenTask( + isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, + shouldLetterbox = true, + ) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { + val task = setUpFreeformTask() + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000)) + + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) + spyController.onDragPositioningEnd( + task, + mockSurface, + Point(100, -100), /* position */ + PointF(200f, -200f), /* inputCoordinate */ + Rect(100, -100, 500, 1000), /* currentDragBounds */ + Rect(0, 50, 2000, 2000), /* validDragArea */ + Rect() /* dragStartBounds */, + motionEvent, + desktopWindowDecoration, ) - val rectAfterEnd = Rect(100, 50, 500, 1150) - verify(transitions) - .startTransition( - eq(TRANSIT_CHANGE), - Mockito.argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - change.configuration.windowConfiguration.bounds == rectAfterEnd - } - }, - eq(null)) - } - - @Test - fun onDesktopDragEnd_noIndicator_updatesTaskBounds() { - val task = setUpFreeformTask() - val spyController = spy(controller) - val mockSurface = mock(SurfaceControl::class.java) - val mockDisplayLayout = mock(DisplayLayout::class.java) - whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) - - val currentDragBounds = Rect(100, 200, 500, 1000) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) - - spyController.onDragPositioningEnd( - task, - mockSurface, - Point(100, 200), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - currentDragBounds, /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, - motionEvent, - desktopWindowDecoration, - ) - - - verify(transitions) - .startTransition( - eq(TRANSIT_CHANGE), - Mockito.argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - change.configuration.windowConfiguration.bounds == currentDragBounds - } - }, - eq(null)) - } - - @Test - fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { - val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) - val spyController = spy(controller) - val mockSurface = mock(SurfaceControl::class.java) - val mockDisplayLayout = mock(DisplayLayout::class.java) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(STABLE_BOUNDS) - } - whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) - - // Drag move the task to the top edge - spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) - spyController.onDragPositioningEnd( - task, - mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - Rect(100, 50, 500, 1000), /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, - motionEvent, - desktopWindowDecoration) - - // Assert the task exits desktop mode - val wct = getLatestExitDesktopWct() - assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } - - @Test - fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() { - val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) - val spyController = spy(controller) - val mockSurface = mock(SurfaceControl::class.java) - val mockDisplayLayout = mock(DisplayLayout::class.java) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(STABLE_BOUNDS) - } - whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) - - // Drag move the task to the top edge - val currentDragBounds = Rect(100, 50, 500, 1000) - spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) - spyController.onDragPositioningEnd( - task, - mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - currentDragBounds, - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, - motionEvent, - desktopWindowDecoration) - - // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) - assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) - // Assert event is properly logged - verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( - ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, - InputMethod.UNKNOWN_INPUT_METHOD, - task, - task.configuration.windowConfiguration.bounds.width(), - task.configuration.windowConfiguration.bounds.height(), - displayController - ) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, - InputMethod.UNKNOWN_INPUT_METHOD, - task, - STABLE_BOUNDS.width(), - STABLE_BOUNDS.height(), - displayController - ) - } - - @Test - fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() { - val task = setUpFreeformTask(bounds = STABLE_BOUNDS) - val spyController = spy(controller) - val mockSurface = mock(SurfaceControl::class.java) - val mockDisplayLayout = mock(DisplayLayout::class.java) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) - whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) - whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(STABLE_BOUNDS) - } - whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) - .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) - - // Drag move the task to the top edge - val currentDragBounds = Rect(100, 50, 500, 1000) - spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) - spyController.onDragPositioningEnd( - task, - mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - currentDragBounds, /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, - motionEvent, - desktopWindowDecoration) - - // Assert that task is NOT updated via WCT - verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) - // Assert that task leash is updated via Surface Animations - verify(mReturnToDragStartAnimator).start( - eq(task.taskId), - eq(mockSurface), - eq(currentDragBounds), - eq(STABLE_BOUNDS), - anyOrNull(), - ) - // Assert no event is logged - verify(desktopModeEventLogger, never()).logTaskResizingStarted( - any(), any(), any(), any(), any(), any(), any() - ) - verify(desktopModeEventLogger, never()).logTaskResizingEnded( - any(), any(), any(), any(), any(), any(), any() - ) - } - - @Test - fun enterSplit_freeformTaskIsMovedToSplit() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - - controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) - - verify(splitScreenController) - .requestEnterSplitSelect( - eq(task2), - any(), - eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), - eq(task2.configuration.windowConfiguration.bounds)) - } - - @Test - fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - taskRepository.wallpaperActivityToken = wallpaperToken - taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId) - taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false) - - controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) - - val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(splitScreenController) - .requestEnterSplitSelect( - eq(task2), - wctArgument.capture(), - eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), - eq(task2.configuration.windowConfiguration.bounds)) - // Removes wallpaper activity when leaving desktop - wctArgument.value.assertRemoveAt(index = 0, wallpaperToken) - } - - @Test - fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() { - val task1 = setUpFreeformTask() - val task2 = setUpFreeformTask() - val task3 = setUpFreeformTask() - val wallpaperToken = MockToken().token() - - task1.isFocused = false - task2.isFocused = true - task3.isFocused = false - taskRepository.wallpaperActivityToken = wallpaperToken - - controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) - - val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(splitScreenController) - .requestEnterSplitSelect( - eq(task2), - wctArgument.capture(), - eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), - eq(task2.configuration.windowConfiguration.bounds)) - // Does not remove wallpaper activity, as desktop still has visible desktop tasks - assertThat(wctArgument.value.hierarchyOps).isEmpty() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun newWindow_fromFullscreenOpensInSplit() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask() - val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) - runOpenNewWindow(task) - verify(splitScreenController) - .startIntent(any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED) - ) - assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) - .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun newWindow_fromSplitOpensInSplit() { - setUpLandscapeDisplay() - val task = setUpSplitScreenTask() - val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) - runOpenNewWindow(task) - verify(splitScreenController) - .startIntent( - any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED) - ) - assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) - .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun newWindow_fromFreeformAddsNewWindow() { - setUpLandscapeDisplay() - val task = setUpFreeformTask() - val wctCaptor = argumentCaptor<WindowContainerTransaction>() - val transition = Binder() - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) - .thenReturn(ExitResult.NoExit) - whenever(desktopMixedTransitionHandler - .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull())) - .thenReturn(transition) - - runOpenNewWindow(task) - - 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(), any())) - .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) - task.isFocused = true - runningTasks.add(task) - controller.openNewWindow(task) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFullscreenOpensInSplit() { - setUpLandscapeDisplay() - val task = setUpFullscreenTask() - val taskToRequest = setUpFreeformTask() - val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) - runOpenInstance(task, taskToRequest.taskId) - verify(splitScreenController) - .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) - assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) - .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromSplitOpensInSplit() { - setUpLandscapeDisplay() - val task = setUpSplitScreenTask() - val taskToRequest = setUpFreeformTask() - val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) - runOpenInstance(task, taskToRequest.taskId) - verify(splitScreenController) - .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) - assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) - .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeformAddsNewWindow() { - setUpLandscapeDisplay() - val task = setUpFreeformTask() - val taskToRequest = setUpFreeformTask() - runOpenInstance(task, taskToRequest.taskId) - verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(), - anyOrNull(), anyOrNull()) - val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) - assertThat(wct.hierarchyOps).hasSize(1) - wct.assertReorderAt(index = 0, taskToRequest) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeform_minimizesIfNeeded() { - setUpLandscapeDisplay() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } - val oldestTask = freeformTasks.first() - val newestTask = freeformTasks.last() - - val transition = Binder() - val wctCaptor = argumentCaptor<WindowContainerTransaction>() - whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), wctCaptor.capture(), - anyInt(), anyOrNull(), anyOrNull() - )) - .thenReturn(transition) - - runOpenInstance(newestTask, freeformTasks[1].taskId) - - val wct = wctCaptor.firstValue - assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize - wct.assertReorderAt(0, freeformTasks[1], toTop = true) - wct.assertReorderAt(1, oldestTask, toTop = false) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { - setUpLandscapeDisplay() - val freeformTask = setUpFreeformTask() - val immersiveTask = setUpFreeformTask() - taskRepository.setTaskInFullImmersiveState( - displayId = immersiveTask.displayId, - taskId = immersiveTask.taskId, - immersive = true - ) - val runOnStartTransit = RunOnStartTransitionCallback() - val transition = Binder() - whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(), - anyOrNull(), anyOrNull() - )) - .thenReturn(transition) - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable( - any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = immersiveTask.taskId, - runOnTransitionStart = runOnStartTransit, - )) - - runOpenInstance(immersiveTask, freeformTask.taskId) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()) - runOnStartTransit.assertOnlyInvocation(transition) - } - - private fun runOpenInstance( - callingTask: RunningTaskInfo, - requestedTaskId: Int - ) { - markTaskVisible(callingTask) - callingTask.baseActivity = mock(ComponentName::class.java) - callingTask.isFocused = true - runningTasks.add(callingTask) - controller.openInstance(callingTask, requestedTaskId) - } - - @Test - fun toggleBounds_togglesToStableBounds() { - val bounds = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) + val rectAfterEnd = Rect(100, 50, 500, 1150) + verify(transitions) + .startTransition( + eq(TRANSIT_CHANGE), + Mockito.argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + change.configuration.windowConfiguration.bounds == rectAfterEnd + } + }, + eq(null), + ) + } - // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - STABLE_BOUNDS.width(), - STABLE_BOUNDS.height(), - displayController - ) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) - fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() { - val bounds = Rect(100, 100, 300, 300) - val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { - topActivityInfo = ActivityInfo().apply { - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE - configuration.windowConfiguration.appBounds = bounds - } - isResizeable = true - } - - val currentDragBounds = Rect(0, 100, 200, 300) - val expectedBounds = Rect( - STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom - ) + @Test + fun onDesktopDragEnd_noIndicator_updatesTaskBounds() { + val task = setUpFreeformTask() + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + + val currentDragBounds = Rect(100, 200, 500, 1000) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR) + + spyController.onDragPositioningEnd( + task, + mockSurface, + Point(100, 200), /* position */ + PointF(200f, 300f), /* inputCoordinate */ + currentDragBounds, /* currentDragBounds */ + Rect(0, 50, 2000, 2000) /* validDragArea */, + Rect() /* dragStartBounds */, + motionEvent, + desktopWindowDecoration, + ) - controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, - ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, desktopWindowDecoration) - // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) - assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.SNAP_LEFT_MENU, - InputMethod.TOUCH, - task, - expectedBounds.width(), - expectedBounds.height(), - displayController - ) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) - fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() { - // Set up task to already be in snapped-left bounds - val bounds = Rect( - STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom - ) - val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { - topActivityInfo = ActivityInfo().apply { - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE - configuration.windowConfiguration.appBounds = bounds - } - isResizeable = true - } - - // Attempt to snap left again - val currentDragBounds = Rect(bounds).apply { offset(-100, 0) } - controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT, - ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, desktopWindowDecoration) - // Assert that task is NOT updated via WCT - verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) - - // Assert that task leash is updated via Surface Animations - verify(mReturnToDragStartAnimator).start( - eq(task.taskId), - eq(mockSurface), - eq(currentDragBounds), - eq(bounds), - anyOrNull(), - ) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.SNAP_LEFT_MENU, - InputMethod.TOUCH, - task, - bounds.width(), - bounds.height(), - displayController - ) - } + verify(transitions) + .startTransition( + eq(TRANSIT_CHANGE), + Mockito.argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + change.configuration.windowConfiguration.bounds == currentDragBounds + } + }, + eq(null), + ) + } - @Test - @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, Flags.FLAG_ENABLE_TILE_RESIZING) - fun handleSnapResizingTaskOnDrag_nonResizable_snapsToHalfScreen() { - val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { - isResizeable = false + @Test + fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() { + val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } + whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) + + // Drag move the task to the top edge + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + spyController.onDragPositioningEnd( + task, + mockSurface, + Point(100, 50), /* position */ + PointF(200f, 300f), /* inputCoordinate */ + Rect(100, 50, 500, 1000), /* currentDragBounds */ + Rect(0, 50, 2000, 2000) /* validDragArea */, + Rect() /* dragStartBounds */, + motionEvent, + desktopWindowDecoration, + ) + + // Assert the task exits desktop mode + val wct = getLatestExitDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) } - val preDragBounds = Rect(100, 100, 400, 500) - val currentDragBounds = Rect(0, 100, 300, 500) - val expectedBounds = - Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom) - controller.handleSnapResizingTaskOnDrag( + @Test + fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() { + val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100)) + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } + whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) + + // Drag move the task to the top edge + val currentDragBounds = Rect(100, 50, 500, 1000) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + spyController.onDragPositioningEnd( + task, + mockSurface, + Point(100, 50), /* position */ + PointF(200f, 300f), /* inputCoordinate */ + currentDragBounds, + Rect(0, 50, 2000, 2000) /* validDragArea */, + Rect() /* dragStartBounds */, + motionEvent, + desktopWindowDecoration, + ) - task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent, - desktopWindowDecoration - ) - val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) - assertThat(findBoundsChange(wct, task)).isEqualTo( - expectedBounds - ) - verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( - ResizeTrigger.DRAG_LEFT, - InputMethod.UNKNOWN_INPUT_METHOD, - task, - preDragBounds.width(), - preDragBounds.height(), - displayController - ) - } - - @Test - @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) - fun handleSnapResizingTaskOnDrag_nonResizable_startsRepositionAnimation() { - val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { - isResizeable = false - } - val preDragBounds = Rect(100, 100, 400, 500) - val currentDragBounds = Rect(0, 100, 300, 500) - - controller.handleSnapResizingTaskOnDrag( - task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent, - desktopWindowDecoration) - verify(mReturnToDragStartAnimator).start( - eq(task.taskId), - eq(mockSurface), - eq(currentDragBounds), - eq(preDragBounds), - any(), - ) - verify(desktopModeEventLogger, never()).logTaskResizingStarted( - any(), - any(), - any(), - any(), - any(), - any(), - any() - ) - } - - @Test - @EnableFlags( - Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING - ) - fun handleInstantSnapResizingTask_nonResizable_animatorNotStartedAndShowsToast() { - val taskBounds = Rect(0, 0, 200, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { - isResizeable = false - } - - controller.handleInstantSnapResizingTask( - task, - SnapPosition.LEFT, - ResizeTrigger.SNAP_LEFT_MENU, - InputMethod.MOUSE, - desktopWindowDecoration - ) + // Assert bounds set to stable bounds + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) + assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) + // Assert event is properly logged + verify(desktopModeEventLogger, times(1)) + .logTaskResizingStarted( + ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, + InputMethod.UNKNOWN_INPUT_METHOD, + task, + task.configuration.windowConfiguration.bounds.width(), + task.configuration.windowConfiguration.bounds.height(), + displayController, + ) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, + InputMethod.UNKNOWN_INPUT_METHOD, + task, + STABLE_BOUNDS.width(), + STABLE_BOUNDS.height(), + displayController, + ) + } - // Assert that task is NOT updated via WCT - verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) - verify(mockToast).show() - } - - @Test - @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) - @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) - fun handleInstantSnapResizingTask_resizable_snapsToHalfScreenAndNotShowToast() { - val taskBounds = Rect(0, 0, 200, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { - isResizeable = true - } - val expectedBounds = Rect( - STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom - ) + @Test + fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() { + val task = setUpFreeformTask(bounds = STABLE_BOUNDS) + val spyController = spy(controller) + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(STABLE_BOUNDS) + } + whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) + + // Drag move the task to the top edge + val currentDragBounds = Rect(100, 50, 500, 1000) + spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) + spyController.onDragPositioningEnd( + task, + mockSurface, + Point(100, 50), /* position */ + PointF(200f, 300f), /* inputCoordinate */ + currentDragBounds, /* currentDragBounds */ + Rect(0, 50, 2000, 2000) /* validDragArea */, + Rect() /* dragStartBounds */, + motionEvent, + desktopWindowDecoration, + ) - controller.handleInstantSnapResizingTask( - task, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.MOUSE, - desktopWindowDecoration - ) + // Assert that task is NOT updated via WCT + verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) + // Assert that task leash is updated via Surface Animations + verify(mReturnToDragStartAnimator) + .start( + eq(task.taskId), + eq(mockSurface), + eq(currentDragBounds), + eq(STABLE_BOUNDS), + anyOrNull(), + ) + // Assert no event is logged + verify(desktopModeEventLogger, never()) + .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any()) + verify(desktopModeEventLogger, never()) + .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any()) + } - // Assert bounds set to half of the stable bounds - val wct = getLatestToggleResizeDesktopTaskWct(taskBounds) - assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) - verify(mockToast, never()).show() - verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( - ResizeTrigger.SNAP_LEFT_MENU, - InputMethod.MOUSE, - task, - taskBounds.width(), - taskBounds.height(), - displayController - ) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.SNAP_LEFT_MENU, - InputMethod.MOUSE, - task, - expectedBounds.width(), - expectedBounds.height(), - displayController - ) - } - - @Test - fun toggleBounds_togglesToCalculatedBoundsForNonResizable() { - val bounds = Rect(0, 0, 200, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { - topActivityInfo = ActivityInfo().apply { - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE - configuration.windowConfiguration.appBounds = bounds - } - appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width() - appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height() - isResizeable = false - } - - // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds - val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750) - - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) + @Test + fun enterSplit_freeformTaskIsMovedToSplit() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + + controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) + + verify(splitScreenController) + .requestEnterSplitSelect( + eq(task2), + any(), + eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), + eq(task2.configuration.windowConfiguration.bounds), + ) + } - // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - expectedBounds.width(), - expectedBounds.height(), - displayController - ) - } - - @Test - fun toggleBounds_lastBoundsBeforeMaximizeSaved() { - val bounds = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) + @Test + fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId) + taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false) + + controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) + + val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(splitScreenController) + .requestEnterSplitSelect( + eq(task2), + wctArgument.capture(), + eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), + eq(task2.configuration.windowConfiguration.bounds), + ) + // Removes wallpaper activity when leaving desktop + wctArgument.value.assertRemoveAt(index = 0, wallpaperToken) + } - assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds) - verify(desktopModeEventLogger, never()).logTaskResizingEnded( - any(), any(), any(), any(), - any(), any(), any() - ) - } - - @Test - fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() { - val boundsBeforeMaximize = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) - - // Maximize - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) - task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) - - // Restore - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.RESTORE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, - InputMethod.TOUCH - ) - ) + @Test + fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + taskRepository.wallpaperActivityToken = wallpaperToken + + controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false) + + val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(splitScreenController) + .requestEnterSplitSelect( + eq(task2), + wctArgument.capture(), + eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT), + eq(task2.configuration.windowConfiguration.bounds), + ) + // Does not remove wallpaper activity, as desktop still has visible desktop tasks + assertThat(wctArgument.value.hierarchyOps).isEmpty() + } - // Assert bounds set to last bounds before maximize - val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - boundsBeforeMaximize.width(), - boundsBeforeMaximize.height(), - displayController - ) - } - - @Test - fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() { - val boundsBeforeMaximize = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { - isResizeable = false - } - - // Maximize - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) - task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left, - boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom) - - // Restore - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.RESTORE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, - InputMethod.TOUCH - ) - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromFullscreenOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenNewWindow(task) + verify(splitScreenController) + .startIntent( + any(), + anyInt(), + any(), + any(), + optionsCaptor.capture(), + anyOrNull(), + eq(true), + eq(SPLIT_INDEX_UNDEFINED), + ) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } - // Assert bounds set to last bounds before maximize - val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - boundsBeforeMaximize.width(), - boundsBeforeMaximize.height(), - displayController - ) - } - - @Test - fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() { - val boundsBeforeMaximize = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { - isResizeable = false - } - - // Maximize - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) - task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left, - STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom) - - // Restore - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.RESTORE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, - InputMethod.TOUCH - ) - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromSplitOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpSplitScreenTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenNewWindow(task) + verify(splitScreenController) + .startIntent( + any(), + anyInt(), + any(), + any(), + optionsCaptor.capture(), + anyOrNull(), + eq(true), + eq(SPLIT_INDEX_UNDEFINED), + ) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } - // Assert bounds set to last bounds before maximize - val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - boundsBeforeMaximize.width(), - boundsBeforeMaximize.height(), - displayController - ) - } - - @Test - fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() { - val boundsBeforeMaximize = Rect(0, 0, 100, 100) - val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) - - // Maximize - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.MAXIMIZE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, - InputMethod.TOUCH - ) - ) - task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) - - // Restore - controller.toggleDesktopTaskSize( - task, - ToggleTaskSizeInteraction( - ToggleTaskSizeInteraction.Direction.RESTORE, - ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, - InputMethod.TOUCH - ) - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun newWindow_fromFreeformAddsNewWindow() { + setUpLandscapeDisplay() + val task = setUpFreeformTask() + val wctCaptor = argumentCaptor<WindowContainerTransaction>() + val transition = Binder() + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + anyInt(), + anyOrNull(), + any(), + ) + ) + .thenReturn(ExitResult.NoExit) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + runOpenNewWindow(task) + + verify(desktopMixedTransitionHandler) + .startLaunchTransition( + anyInt(), + wctCaptor.capture(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + assertThat( + ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions) + .launchWindowingMode + ) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } - // Assert last bounds before maximize removed after use - assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull() - verify(desktopModeEventLogger, times(1)).logTaskResizingEnded( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task, - boundsBeforeMaximize.width(), - boundsBeforeMaximize.height(), - displayController - ) - } - - @Test - fun onUnhandledDrag_newFreeformIntent() { - testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, - PointF(1200f, 700f), - Rect(240, 700, 2160, 1900)) - } - - @Test - fun onUnhandledDrag_newFreeformIntentSplitLeft() { - testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR, - PointF(50f, 700f), - Rect(0, 0, 500, 1000)) - } - - @Test - fun onUnhandledDrag_newFreeformIntentSplitRight() { - testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR, - PointF(2500f, 700f), - Rect(500, 0, 1000, 1000)) - } - - @Test - fun onUnhandledDrag_newFullscreenIntent() { - testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, - PointF(1200f, 50f), - Rect()) - } - - @Test - fun shellController_registersUserChangeListener() { - verify(shellController, times(2)).addUserChangeListener(any()) - } - - @Test - @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() { - val task = setUpFreeformTask(DEFAULT_DISPLAY) - taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) - - task.requestedVisibleTypes = WindowInsets.Type.statusBars() - controller.onTaskInfoChanged(task) - - verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) - } - - @Test - @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() { - val task = setUpFreeformTask(DEFAULT_DISPLAY) - taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false) - - task.requestedVisibleTypes = WindowInsets.Type.statusBars() - controller.onTaskInfoChanged(task) - - verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) - } - - @Test - @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() { - val task = setUpFreeformTask(DEFAULT_DISPLAY) - taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) - recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED) - - task.requestedVisibleTypes = WindowInsets.Type.statusBars() - controller.onTaskInfoChanged(task) - - verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) - } - - @Test - fun moveTaskToDesktop_background_attemptsImmersiveExit() { - val task = setUpFreeformTask(background = true) - val wct = WindowContainerTransaction() - val runOnStartTransit = RunOnStartTransitionCallback() - val transition = Binder() - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = 5, - runOnTransitionStart = runOnStartTransit, - )) - whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) - - controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) - runOnStartTransit.assertOnlyInvocation(transition) - } - - @Test - fun moveTaskToDesktop_foreground_attemptsImmersiveExit() { - val task = setUpFreeformTask(background = false) - val wct = WindowContainerTransaction() - val runOnStartTransit = RunOnStartTransitionCallback() - val transition = Binder() - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = 5, - runOnTransitionStart = runOnStartTransit, - )) - whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) - - controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) - runOnStartTransit.assertOnlyInvocation(transition) - } - - @Test - fun moveTaskToFront_background_attemptsImmersiveExit() { - val task = setUpFreeformTask(background = true) - val runOnStartTransit = RunOnStartTransitionCallback() - val transition = Binder() - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = 5, - runOnTransitionStart = runOnStartTransit, - )) - whenever(desktopMixedTransitionHandler - .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull())) - .thenReturn(transition) - - controller.moveTaskToFront(task.taskId, remoteTransition = null) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) - runOnStartTransit.assertOnlyInvocation(transition) - } - - @Test - fun moveTaskToFront_foreground_attemptsImmersiveExit() { - val task = setUpFreeformTask(background = false) - val runOnStartTransit = RunOnStartTransitionCallback() - val transition = Binder() - whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) - .thenReturn( - ExitResult.Exit( - exitingTask = 5, - runOnTransitionStart = runOnStartTransit, - )) - whenever(desktopMixedTransitionHandler - .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull())) - .thenReturn(transition) - - controller.moveTaskToFront(task.taskId, remoteTransition = null) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) - runOnStartTransit.assertOnlyInvocation(transition) - } - - @Test - fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() { - markTaskVisible(setUpFreeformTask()) - val task = setUpFreeformTask() - markTaskVisible(task) - val binder = Binder() - - controller.handleRequest(binder, createTransition(task)) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) - } - - @Test - fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() { - setUpFreeformTask() - val task = setUpFullscreenTask() - val binder = Binder() - - controller.handleRequest(binder, createTransition(task)) - - verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { - val triggerTask = setUpFullscreenTask(displayId = 5) - taskRepository.setTaskInFullImmersiveState( - displayId = triggerTask.displayId, - taskId = triggerTask.taskId, - immersive = true - ) + @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(), + any(), + ) + ) + .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart)) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + any(), + anyOrNull(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + runOpenNewWindow(task) + + runOnStart.assertOnlyInvocation(transition) + } - assertThat(controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - )).isFalse() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5) - taskRepository.setTaskInFullImmersiveState( - displayId = triggerTask.displayId, - taskId = triggerTask.taskId, - immersive = true - ) + private fun runOpenNewWindow(task: RunningTaskInfo) { + markTaskVisible(task) + task.baseActivity = mock(ComponentName::class.java) + task.isFocused = true + runningTasks.add(task) + controller.openNewWindow(task) + } - assertThat(controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null) - )).isFalse() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5) - taskRepository.setTaskInFullImmersiveState( - displayId = triggerTask.displayId, - taskId = triggerTask.taskId, - immersive = false - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFullscreenOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpFullscreenTask() + val taskToRequest = setUpFreeformTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenInstance(task, taskToRequest.taskId) + verify(splitScreenController) + .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } - assertThat(controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - )).isFalse() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() { - // At least one freeform task to be in a desktop. - val existingTask = setUpFreeformTask(displayId = 5) - val triggerTask = setUpFullscreenTask(displayId = 5) - assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() - taskRepository.setTaskInFullImmersiveState( - displayId = existingTask.displayId, - taskId = existingTask.taskId, - immersive = true - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromSplitOpensInSplit() { + setUpLandscapeDisplay() + val task = setUpSplitScreenTask() + val taskToRequest = setUpFreeformTask() + val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java) + runOpenInstance(task, taskToRequest.taskId) + verify(splitScreenController) + .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull()) + assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) + .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) + } - assertThat( - controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - ) - ).isTrue() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { - val triggerTask = setUpFullscreenTask(displayId = 5) - assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() - - assertThat(controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - )).isFalse() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() { - // At least one freeform task to be in a desktop. - val existingTask = setUpFreeformTask(displayId = 5) - val triggerTask = setUpFreeformTask(displayId = 5, active = false) - assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() - taskRepository.setTaskInFullImmersiveState( - displayId = existingTask.displayId, - taskId = existingTask.taskId, - immersive = true - ) + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeformAddsNewWindow() { + setUpLandscapeDisplay() + val task = setUpFreeformTask() + val taskToRequest = setUpFreeformTask() + runOpenInstance(task, taskToRequest.taskId) + verify(desktopMixedTransitionHandler) + .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertReorderAt(index = 0, taskToRequest) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeform_minimizesIfNeeded() { + setUpLandscapeDisplay() + val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val oldestTask = freeformTasks.first() + val newestTask = freeformTasks.last() + + val transition = Binder() + val wctCaptor = argumentCaptor<WindowContainerTransaction>() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + wctCaptor.capture(), + anyInt(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + runOpenInstance(newestTask, freeformTasks[1].taskId) + + val wct = wctCaptor.firstValue + assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize + wct.assertReorderAt(0, freeformTasks[1], toTop = true) + wct.assertReorderAt(1, oldestTask, toTop = false) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { + setUpLandscapeDisplay() + val freeformTask = setUpFreeformTask() + val immersiveTask = setUpFreeformTask() + taskRepository.setTaskInFullImmersiveState( + displayId = immersiveTask.displayId, + taskId = immersiveTask.taskId, + immersive = true, + ) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + any(), + anyInt(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + eq(DEFAULT_DISPLAY), + eq(freeformTask.taskId), + any(), + ) + ) + .thenReturn( + ExitResult.Exit( + exitingTask = immersiveTask.taskId, + runOnTransitionStart = runOnStartTransit, + ) + ) + + runOpenInstance(immersiveTask, freeformTask.taskId) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable( + any(), + eq(immersiveTask.displayId), + eq(freeformTask.taskId), + any(), + ) + runOnStartTransit.assertOnlyInvocation(transition) + } + + private fun runOpenInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) { + markTaskVisible(callingTask) + callingTask.baseActivity = mock(ComponentName::class.java) + callingTask.isFocused = true + runningTasks.add(callingTask) + controller.openInstance(callingTask, requestedTaskId) + } + + @Test + fun toggleBounds_togglesToStableBounds() { + val bounds = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) + + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + + // Assert bounds set to stable bounds + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + STABLE_BOUNDS.width(), + STABLE_BOUNDS.height(), + displayController, + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) + fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() { + val bounds = Rect(100, 100, 300, 300) + val task = + setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { + topActivityInfo = + ActivityInfo().apply { + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE + configuration.windowConfiguration.appBounds = bounds + } + isResizeable = true + } + + val currentDragBounds = Rect(0, 100, 200, 300) + val expectedBounds = + Rect( + STABLE_BOUNDS.left, + STABLE_BOUNDS.top, + STABLE_BOUNDS.right / 2, + STABLE_BOUNDS.bottom, + ) + + controller.snapToHalfScreen( + task, + mockSurface, + currentDragBounds, + SnapPosition.LEFT, + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.TOUCH, + desktopWindowDecoration, + ) + // Assert bounds set to stable bounds + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) + assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.TOUCH, + task, + expectedBounds.width(), + expectedBounds.height(), + displayController, + ) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) + fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() { + // Set up task to already be in snapped-left bounds + val bounds = + Rect( + STABLE_BOUNDS.left, + STABLE_BOUNDS.top, + STABLE_BOUNDS.right / 2, + STABLE_BOUNDS.bottom, + ) + val task = + setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { + topActivityInfo = + ActivityInfo().apply { + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE + configuration.windowConfiguration.appBounds = bounds + } + isResizeable = true + } + + // Attempt to snap left again + val currentDragBounds = Rect(bounds).apply { offset(-100, 0) } + controller.snapToHalfScreen( + task, + mockSurface, + currentDragBounds, + SnapPosition.LEFT, + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.TOUCH, + desktopWindowDecoration, + ) + // Assert that task is NOT updated via WCT + verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) + + // Assert that task leash is updated via Surface Animations + verify(mReturnToDragStartAnimator) + .start(eq(task.taskId), eq(mockSurface), eq(currentDragBounds), eq(bounds), anyOrNull()) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.TOUCH, + task, + bounds.width(), + bounds.height(), + displayController, + ) + } - assertThat( - controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - ) - ).isTrue() - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) - fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5, active = false) - assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() - - assertThat(controller.shouldPlayDesktopAnimation( - TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) - )).isFalse() - } - - private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { - var invocations = 0 - private set - var lastInvoked: IBinder? = null - private set - - override fun invoke(transition: IBinder) { - invocations++ - lastInvoked = transition - } - } - - private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) { - assertThat(invocations).isEqualTo(1) - assertThat(lastInvoked).isEqualTo(transition) - } - - /** - * Assert that an unhandled drag event launches a PendingIntent with the - * windowing mode and bounds we are expecting. - */ - private fun testOnUnhandledDrag( - indicatorType: DesktopModeVisualIndicator.IndicatorType, - inputCoordinate: PointF, - expectedBounds: Rect - ) { - setUpLandscapeDisplay() - val task = setUpFreeformTask() - markTaskVisible(task) - task.isFocused = true - val runningTasks = ArrayList<RunningTaskInfo>() - runningTasks.add(task) - val spyController = spy(controller) - val mockPendingIntent = mock(PendingIntent::class.java) - val mockDragEvent = mock(DragEvent::class.java) - val mockCallback = mock(Consumer::class.java) - val b = SurfaceControl.Builder() - b.setName("test surface") - val dragSurface = b.build() - whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks) - whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) - whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) - whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) - whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true) - whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) - doReturn(indicatorType) - .whenever(spyController).updateVisualIndicator( - eq(task), - anyOrNull(), - anyOrNull(), - anyOrNull(), - eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT) - ) - - spyController.onUnhandledDrag( - mockPendingIntent, - mockDragEvent, - mockCallback as Consumer<Boolean> + @Test + @DisableFlags( + Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, + Flags.FLAG_ENABLE_TILE_RESIZING, ) - val arg: ArgumentCaptor<WindowContainerTransaction> = - ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - var expectedWindowingMode: Int - if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) { - expectedWindowingMode = WINDOWING_MODE_FULLSCREEN - // Fullscreen launches currently use default transitions - verify(transitions).startTransition(any(), capture(arg), anyOrNull()) - } else { - expectedWindowingMode = WINDOWING_MODE_FREEFORM - // All other launches use a special handler. - verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg)) - } - assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions) - .launchWindowingMode).isEqualTo(expectedWindowingMode) - assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions) - .launchBounds).isEqualTo(expectedBounds) - } - - private val desktopWallpaperIntent: Intent - get() = Intent(context, DesktopWallpaperActivity::class.java) - - private fun addFreeformTaskAtPosition( - pos: DesktopTaskPosition, - stableBounds: Rect, - bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS, - offsetPos: Point = Point(0, 0) - ): RunningTaskInfo { - val offset = pos.getTopLeftCoordinates(stableBounds, bounds) - val prevTaskBounds = Rect(bounds) - prevTaskBounds.offsetTo(offset.x + offsetPos.x, offset.y + offsetPos.y) - return setUpFreeformTask(bounds = prevTaskBounds) - } - - private fun setUpFreeformTask( - displayId: Int = DEFAULT_DISPLAY, - bounds: Rect? = null, - active: Boolean = true, - background: Boolean = false, - ): RunningTaskInfo { - val task = createFreeformTask(displayId, bounds) - val activityInfo = ActivityInfo() - task.topActivityInfo = activityInfo - if (background) { - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) - whenever(recentTasksController.findTaskInBackground(task.taskId)) - .thenReturn(createTaskInfo(task.taskId)) - } else { - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - } - taskRepository.addTask(displayId, task.taskId, isVisible = active) - if (!background) { - runningTasks.add(task) - } - return task - } - - private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo { - return setUpFreeformTask().apply { - pictureInPictureParams = PictureInPictureParams.Builder() - .setAutoEnterEnabled(autoEnterEnabled) - .build() - } - } - - private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { - val task = createHomeTask(displayId) - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - runningTasks.add(task) - return task - } - - private fun setUpFullscreenTask( - displayId: Int = DEFAULT_DISPLAY, - isResizable: Boolean = true, - windowingMode: Int = WINDOWING_MODE_FULLSCREEN, - deviceOrientation: Int = ORIENTATION_LANDSCAPE, - screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, - shouldLetterbox: Boolean = false, - gravity: Int = Gravity.NO_GRAVITY, - enableUserFullscreenOverride: Boolean = false, - enableSystemFullscreenOverride: Boolean = false, - aspectRatioOverrideApplied: Boolean = false - ): RunningTaskInfo { - val task = createFullscreenTask(displayId) - val activityInfo = ActivityInfo() - activityInfo.screenOrientation = screenOrientation - activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0) - with(task) { - topActivityInfo = activityInfo - isResizeable = isResizable - configuration.orientation = deviceOrientation - configuration.windowConfiguration.windowingMode = windowingMode - appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride - appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride - - if (deviceOrientation == ORIENTATION_LANDSCAPE) { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT - } else { - configuration.windowConfiguration.appBounds = - Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) - appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT - appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG - } - - if (shouldLetterbox) { - appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied) - if (deviceOrientation == ORIENTATION_LANDSCAPE && - screenOrientation == SCREEN_ORIENTATION_PORTRAIT) { - // Letterbox to portrait size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1200 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1600 - } else if (deviceOrientation == ORIENTATION_PORTRAIT && - screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) { - // Letterbox to landscape size - appCompatTaskInfo.setTopActivityLetterboxed(true) - appCompatTaskInfo.topActivityLetterboxAppWidth = 1600 - appCompatTaskInfo.topActivityLetterboxAppHeight = 1200 + fun handleSnapResizingTaskOnDrag_nonResizable_snapsToHalfScreen() { + val task = + setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false } + val preDragBounds = Rect(100, 100, 400, 500) + val currentDragBounds = Rect(0, 100, 300, 500) + val expectedBounds = + Rect( + STABLE_BOUNDS.left, + STABLE_BOUNDS.top, + STABLE_BOUNDS.right / 2, + STABLE_BOUNDS.bottom, + ) + + controller.handleSnapResizingTaskOnDrag( + task, + SnapPosition.LEFT, + mockSurface, + currentDragBounds, + preDragBounds, + motionEvent, + desktopWindowDecoration, + ) + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) + assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingStarted( + ResizeTrigger.DRAG_LEFT, + InputMethod.UNKNOWN_INPUT_METHOD, + task, + preDragBounds.width(), + preDragBounds.height(), + displayController, + ) + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun handleSnapResizingTaskOnDrag_nonResizable_startsRepositionAnimation() { + val task = + setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false } + val preDragBounds = Rect(100, 100, 400, 500) + val currentDragBounds = Rect(0, 100, 300, 500) + + controller.handleSnapResizingTaskOnDrag( + task, + SnapPosition.LEFT, + mockSurface, + currentDragBounds, + preDragBounds, + motionEvent, + desktopWindowDecoration, + ) + verify(mReturnToDragStartAnimator) + .start( + eq(task.taskId), + eq(mockSurface), + eq(currentDragBounds), + eq(preDragBounds), + any(), + ) + verify(desktopModeEventLogger, never()) + .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + fun handleInstantSnapResizingTask_nonResizable_animatorNotStartedAndShowsToast() { + val taskBounds = Rect(0, 0, 200, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = false } + + controller.handleInstantSnapResizingTask( + task, + SnapPosition.LEFT, + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.MOUSE, + desktopWindowDecoration, + ) + + // Assert that task is NOT updated via WCT + verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any()) + verify(mockToast).show() + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING) + @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING) + fun handleInstantSnapResizingTask_resizable_snapsToHalfScreenAndNotShowToast() { + val taskBounds = Rect(0, 0, 200, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = true } + val expectedBounds = + Rect( + STABLE_BOUNDS.left, + STABLE_BOUNDS.top, + STABLE_BOUNDS.right / 2, + STABLE_BOUNDS.bottom, + ) + + controller.handleInstantSnapResizingTask( + task, + SnapPosition.LEFT, + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.MOUSE, + desktopWindowDecoration, + ) + + // Assert bounds set to half of the stable bounds + val wct = getLatestToggleResizeDesktopTaskWct(taskBounds) + assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) + verify(mockToast, never()).show() + verify(desktopModeEventLogger, times(1)) + .logTaskResizingStarted( + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.MOUSE, + task, + taskBounds.width(), + taskBounds.height(), + displayController, + ) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.SNAP_LEFT_MENU, + InputMethod.MOUSE, + task, + expectedBounds.width(), + expectedBounds.height(), + displayController, + ) + } + + @Test + fun toggleBounds_togglesToCalculatedBoundsForNonResizable() { + val bounds = Rect(0, 0, 200, 100) + val task = + setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { + topActivityInfo = + ActivityInfo().apply { + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE + configuration.windowConfiguration.appBounds = bounds + } + appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width() + appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height() + isResizeable = false + } + + // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds + val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750) + + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + + // Assert bounds set to stable bounds + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + expectedBounds.width(), + expectedBounds.height(), + displayController, + ) + } + + @Test + fun toggleBounds_lastBoundsBeforeMaximizeSaved() { + val bounds = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) + + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + + assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds) + verify(desktopModeEventLogger, never()) + .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any()) + } + + @Test + fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) + + // Maximize + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) + + // Restore + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH, + ), + ) + + // Assert bounds set to last bounds before maximize + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + boundsBeforeMaximize.width(), + boundsBeforeMaximize.height(), + displayController, + ) + } + + @Test + fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = + setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false } + + // Maximize + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + task.configuration.windowConfiguration.bounds.set( + STABLE_BOUNDS.left, + boundsBeforeMaximize.top, + STABLE_BOUNDS.right, + boundsBeforeMaximize.bottom, + ) + + // Restore + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH, + ), + ) + + // Assert bounds set to last bounds before maximize + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + boundsBeforeMaximize.width(), + boundsBeforeMaximize.height(), + displayController, + ) + } + + @Test + fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = + setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false } + + // Maximize + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + task.configuration.windowConfiguration.bounds.set( + boundsBeforeMaximize.left, + STABLE_BOUNDS.top, + boundsBeforeMaximize.right, + STABLE_BOUNDS.bottom, + ) + + // Restore + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH, + ), + ) + + // Assert bounds set to last bounds before maximize + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + boundsBeforeMaximize.width(), + boundsBeforeMaximize.height(), + displayController, + ) + } + + @Test + fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) + + // Maximize + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH, + ), + ) + task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS) + + // Restore + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.RESTORE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE, + InputMethod.TOUCH, + ), + ) + + // Assert last bounds before maximize removed after use + assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull() + verify(desktopModeEventLogger, times(1)) + .logTaskResizingEnded( + ResizeTrigger.MAXIMIZE_BUTTON, + InputMethod.TOUCH, + task, + boundsBeforeMaximize.width(), + boundsBeforeMaximize.height(), + displayController, + ) + } + + @Test + fun onUnhandledDrag_newFreeformIntent() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, + PointF(1200f, 700f), + Rect(240, 700, 2160, 1900), + ) + } + + @Test + fun onUnhandledDrag_newFreeformIntentSplitLeft() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR, + PointF(50f, 700f), + Rect(0, 0, 500, 1000), + ) + } + + @Test + fun onUnhandledDrag_newFreeformIntentSplitRight() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR, + PointF(2500f, 700f), + Rect(500, 0, 1000, 1000), + ) + } + + @Test + fun onUnhandledDrag_newFullscreenIntent() { + testOnUnhandledDrag( + DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR, + PointF(1200f, 50f), + Rect(), + ) + } + + @Test + fun shellController_registersUserChangeListener() { + verify(shellController, times(2)).addUserChangeListener(any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) + } + + @Test + fun moveTaskToDesktop_background_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = true) + val wct = WindowContainerTransaction() + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + eq(wct), + eq(task.displayId), + eq(task.taskId), + any(), + ) + ) + .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)) + whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) + + controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToDesktop_foreground_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = false) + val wct = WindowContainerTransaction() + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + eq(wct), + eq(task.displayId), + eq(task.taskId), + any(), + ) + ) + .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)) + whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition) + + controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToFront_background_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = true) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + eq(task.displayId), + eq(task.taskId), + any(), + ) + ) + .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + any(), + any(), + anyInt(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + controller.moveTaskToFront(task.taskId, remoteTransition = null) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun moveTaskToFront_foreground_attemptsImmersiveExit() { + val task = setUpFreeformTask(background = false) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever( + mMockDesktopImmersiveController.exitImmersiveIfApplicable( + any(), + eq(task.displayId), + eq(task.taskId), + any(), + ) + ) + .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + any(), + any(), + eq(task.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + controller.moveTaskToFront(task.taskId, remoteTransition = null) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) + runOnStartTransit.assertOnlyInvocation(transition) + } + + @Test + fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() { + markTaskVisible(setUpFreeformTask()) + val task = setUpFreeformTask() + markTaskVisible(task) + val binder = Binder() + + controller.handleRequest(binder, createTransition(task)) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) + } + + @Test + fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() { + setUpFreeformTask() + val task = setUpFullscreenTask() + val binder = Binder() + + controller.handleRequest(binder, createTransition(task)) + + verify(mMockDesktopImmersiveController) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { + val triggerTask = setUpFullscreenTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = true, + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = true, + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null) + ) + ) + .isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5) + taskRepository.setTaskInFullImmersiveState( + displayId = triggerTask.displayId, + taskId = triggerTask.taskId, + immersive = false, + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() { + // At least one freeform task to be in a desktop. + val existingTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFullscreenTask(displayId = 5) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() + taskRepository.setTaskInFullImmersiveState( + displayId = existingTask.displayId, + taskId = existingTask.taskId, + immersive = true, + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { + val triggerTask = setUpFullscreenTask(displayId = 5) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() { + // At least one freeform task to be in a desktop. + val existingTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFreeformTask(displayId = 5, active = false) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() + taskRepository.setTaskInFullImmersiveState( + displayId = existingTask.displayId, + taskId = existingTask.taskId, + immersive = true, + ) + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { + val triggerTask = setUpFreeformTask(displayId = 5, active = false) + assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() + + assertThat( + controller.shouldPlayDesktopAnimation( + TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null) + ) + ) + .isFalse() + } + + private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { + var invocations = 0 + private set + + var lastInvoked: IBinder? = null + private set + + override fun invoke(transition: IBinder) { + invocations++ + lastInvoked = transition } - } - } - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - runningTasks.add(task) - return task - } - - private fun setUpLandscapeDisplay() { - whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) - whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) - val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_LONG, - DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT - ) - whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(stableBounds) } - } - private fun setUpPortraitDisplay() { - whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) - whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) - val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_SHORT, - DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT - ) - whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> - (i.arguments.first() as Rect).set(stableBounds) - } - } - - private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { - val task = createSplitScreenTask(displayId) - whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) - whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - runningTasks.add(task) - return task - } - - private fun markTaskVisible(task: RunningTaskInfo) { - taskRepository.updateTask(task.displayId, task.taskId, isVisible = true) - } - - private fun markTaskHidden(task: RunningTaskInfo) { - taskRepository.updateTask(task.displayId, task.taskId, isVisible = false) - } - - private fun getLatestWct( - @WindowManager.TransitionType type: Int = TRANSIT_OPEN, - handlerClass: Class<out TransitionHandler>? = null - ): WindowContainerTransaction { - val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - if (handlerClass == null) { - verify(transitions).startTransition(eq(type), arg.capture(), isNull()) - } else { - verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass)) - } - return arg.value - } - - private fun getLatestToggleResizeDesktopTaskWct( - currentBounds: Rect? = null - ): WindowContainerTransaction { - val arg: ArgumentCaptor<WindowContainerTransaction> = - ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce()) - .startTransition(capture(arg), eq(currentBounds)) - return arg.value - } - - private fun getLatestDesktopMixedTaskWct( - @WindowManager.TransitionType type: Int = TRANSIT_OPEN, - ): WindowContainerTransaction { - val arg: ArgumentCaptor<WindowContainerTransaction> = - ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(desktopMixedTransitionHandler) - .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull()) - return arg.value - } - - private fun getLatestEnterDesktopWct(): WindowContainerTransaction { - val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) - return arg.value - } - - private fun getLatestDragToDesktopWct(): WindowContainerTransaction { - val arg: ArgumentCaptor<WindowContainerTransaction> = - ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg)) - return arg.value - } - - private fun getLatestExitDesktopWct(): WindowContainerTransaction { - val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) - verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any()) - return arg.value - } - - private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = - wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds - - private fun verifyWCTNotExecuted() { - verify(transitions, never()).startTransition(anyInt(), any(), isNull()) - } - - private fun verifyExitDesktopWCTNotExecuted() { - verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any()) - } - - private fun verifyEnterDesktopWCTNotExecuted() { - verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any()) - } - - private fun createTransition( - task: RunningTaskInfo?, - @WindowManager.TransitionType type: Int = TRANSIT_OPEN - ): TransitionRequestInfo { - return TransitionRequestInfo(type, task, null /* remoteTransition */) - } - - private companion object { - const val SECOND_DISPLAY = 2 - val STABLE_BOUNDS = Rect(0, 0, 1000, 1000) - const val MAX_TASK_LIMIT = 6 - private const val TASKBAR_FRAME_HEIGHT = 200 - } + private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) { + assertThat(invocations).isEqualTo(1) + assertThat(lastInvoked).isEqualTo(transition) + } + + /** + * Assert that an unhandled drag event launches a PendingIntent with the windowing mode and + * bounds we are expecting. + */ + private fun testOnUnhandledDrag( + indicatorType: DesktopModeVisualIndicator.IndicatorType, + inputCoordinate: PointF, + expectedBounds: Rect, + ) { + setUpLandscapeDisplay() + val task = setUpFreeformTask() + markTaskVisible(task) + task.isFocused = true + val runningTasks = ArrayList<RunningTaskInfo>() + runningTasks.add(task) + val spyController = spy(controller) + val mockPendingIntent = mock(PendingIntent::class.java) + val mockDragEvent = mock(DragEvent::class.java) + val mockCallback = mock(Consumer::class.java) + val b = SurfaceControl.Builder() + b.setName("test surface") + val dragSurface = b.build() + whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks) + whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) + whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) + whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) + whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + doReturn(indicatorType) + .whenever(spyController) + .updateVisualIndicator( + eq(task), + anyOrNull(), + anyOrNull(), + anyOrNull(), + eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT), + ) + + spyController.onUnhandledDrag( + mockPendingIntent, + mockDragEvent, + mockCallback as Consumer<Boolean>, + ) + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + var expectedWindowingMode: Int + if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) { + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN + // Fullscreen launches currently use default transitions + verify(transitions).startTransition(any(), capture(arg), anyOrNull()) + } else { + expectedWindowingMode = WINDOWING_MODE_FREEFORM + // All other launches use a special handler. + verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg)) + } + assertThat( + ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions) + .launchWindowingMode + ) + .isEqualTo(expectedWindowingMode) + assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds) + .isEqualTo(expectedBounds) + } + + private val desktopWallpaperIntent: Intent + get() = Intent(context, DesktopWallpaperActivity::class.java) + + private fun addFreeformTaskAtPosition( + pos: DesktopTaskPosition, + stableBounds: Rect, + bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS, + offsetPos: Point = Point(0, 0), + ): RunningTaskInfo { + val offset = pos.getTopLeftCoordinates(stableBounds, bounds) + val prevTaskBounds = Rect(bounds) + prevTaskBounds.offsetTo(offset.x + offsetPos.x, offset.y + offsetPos.y) + return setUpFreeformTask(bounds = prevTaskBounds) + } + + private fun setUpFreeformTask( + displayId: Int = DEFAULT_DISPLAY, + bounds: Rect? = null, + active: Boolean = true, + background: Boolean = false, + ): RunningTaskInfo { + val task = createFreeformTask(displayId, bounds) + val activityInfo = ActivityInfo() + task.topActivityInfo = activityInfo + if (background) { + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(task.taskId)) + .thenReturn(createTaskInfo(task.taskId)) + } else { + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + } + taskRepository.addTask(displayId, task.taskId, isVisible = active) + if (!background) { + runningTasks.add(task) + } + return task + } + + private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo { + return setUpFreeformTask().apply { + pictureInPictureParams = + PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() + } + } + + private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createHomeTask(displayId) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + runningTasks.add(task) + return task + } + + private fun setUpFullscreenTask( + displayId: Int = DEFAULT_DISPLAY, + isResizable: Boolean = true, + windowingMode: Int = WINDOWING_MODE_FULLSCREEN, + deviceOrientation: Int = ORIENTATION_LANDSCAPE, + screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, + shouldLetterbox: Boolean = false, + gravity: Int = Gravity.NO_GRAVITY, + enableUserFullscreenOverride: Boolean = false, + enableSystemFullscreenOverride: Boolean = false, + aspectRatioOverrideApplied: Boolean = false, + ): RunningTaskInfo { + val task = createFullscreenTask(displayId) + val activityInfo = ActivityInfo() + activityInfo.screenOrientation = screenOrientation + activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0) + with(task) { + topActivityInfo = activityInfo + isResizeable = isResizable + configuration.orientation = deviceOrientation + configuration.windowConfiguration.windowingMode = windowingMode + appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride + appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride + + if (deviceOrientation == ORIENTATION_LANDSCAPE) { + configuration.windowConfiguration.appBounds = + Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) + appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG + appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT + } else { + configuration.windowConfiguration.appBounds = + Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) + appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT + appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG + } + + if (shouldLetterbox) { + appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied) + if ( + deviceOrientation == ORIENTATION_LANDSCAPE && + screenOrientation == SCREEN_ORIENTATION_PORTRAIT + ) { + // Letterbox to portrait size + appCompatTaskInfo.setTopActivityLetterboxed(true) + appCompatTaskInfo.topActivityLetterboxAppWidth = 1200 + appCompatTaskInfo.topActivityLetterboxAppHeight = 1600 + } else if ( + deviceOrientation == ORIENTATION_PORTRAIT && + screenOrientation == SCREEN_ORIENTATION_LANDSCAPE + ) { + // Letterbox to landscape size + appCompatTaskInfo.setTopActivityLetterboxed(true) + appCompatTaskInfo.topActivityLetterboxAppWidth = 1600 + appCompatTaskInfo.topActivityLetterboxAppHeight = 1200 + } + } + } + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + runningTasks.add(task) + return task + } + + private fun setUpLandscapeDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) + val stableBounds = + Rect( + 0, + 0, + DISPLAY_DIMENSION_LONG, + DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT, + ) + whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + } + + private fun setUpPortraitDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) + val stableBounds = + Rect( + 0, + 0, + DISPLAY_DIMENSION_SHORT, + DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT, + ) + whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + } + + private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createSplitScreenTask(displayId) + whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + runningTasks.add(task) + return task + } + + private fun markTaskVisible(task: RunningTaskInfo) { + taskRepository.updateTask(task.displayId, task.taskId, isVisible = true) + } + + private fun markTaskHidden(task: RunningTaskInfo) { + taskRepository.updateTask(task.displayId, task.taskId, isVisible = false) + } + + private fun getLatestWct( + @WindowManager.TransitionType type: Int = TRANSIT_OPEN, + handlerClass: Class<out TransitionHandler>? = null, + ): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (handlerClass == null) { + verify(transitions).startTransition(eq(type), arg.capture(), isNull()) + } else { + verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass)) + } + return arg.value + } + + private fun getLatestToggleResizeDesktopTaskWct( + currentBounds: Rect? = null + ): WindowContainerTransaction { + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce()) + .startTransition(capture(arg), eq(currentBounds)) + return arg.value + } + + private fun getLatestDesktopMixedTaskWct( + @WindowManager.TransitionType type: Int = TRANSIT_OPEN + ): WindowContainerTransaction { + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(desktopMixedTransitionHandler) + .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull()) + return arg.value + } + + private fun getLatestEnterDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any()) + return arg.value + } + + private fun getLatestDragToDesktopWct(): WindowContainerTransaction { + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg)) + return arg.value + } + + private fun getLatestExitDesktopWct(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any()) + return arg.value + } + + private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = + wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds + + private fun verifyWCTNotExecuted() { + verify(transitions, never()).startTransition(anyInt(), any(), isNull()) + } + + private fun verifyExitDesktopWCTNotExecuted() { + verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any()) + } + + private fun verifyEnterDesktopWCTNotExecuted() { + verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any()) + } + + private fun createTransition( + task: RunningTaskInfo?, + @WindowManager.TransitionType type: Int = TRANSIT_OPEN, + ): TransitionRequestInfo { + return TransitionRequestInfo(type, task, null /* remoteTransition */) + } + + private companion object { + const val SECOND_DISPLAY = 2 + val STABLE_BOUNDS = Rect(0, 0, 1000, 1000) + const val MAX_TASK_LIMIT = 6 + private const val TASKBAR_FRAME_HEIGHT = 200 + } } private fun WindowContainerTransaction.assertIndexInBounds(index: Int) { - assertWithMessage("WCT does not have a hierarchy operation at index $index") - .that(hierarchyOps.size) - .isGreaterThan(index) + assertWithMessage("WCT does not have a hierarchy operation at index $index") + .that(hierarchyOps.size) + .isGreaterThan(index) } private fun WindowContainerTransaction.assertReorderAt( index: Int, task: RunningTaskInfo, - toTop: Boolean? = null + toTop: Boolean? = null, ) { - assertIndexInBounds(index) - val op = hierarchyOps[index] - assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER) - assertThat(op.container).isEqualTo(task.token.asBinder()) - toTop?.let { assertThat(op.toTop).isEqualTo(it) } + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER) + assertThat(op.container).isEqualTo(task.token.asBinder()) + toTop?.let { assertThat(op.toTop).isEqualTo(it) } } private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) { - for (i in tasks.indices) { - assertReorderAt(i, tasks[i]) - } + for (i in tasks.indices) { + assertReorderAt(i, tasks[i]) + } } /** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */ private fun WindowContainerTransaction.assertReorderSequenceInRange( - range: IntRange, - vararg tasks: RunningTaskInfo + range: IntRange, + vararg tasks: RunningTaskInfo, ) { - assertThat(hierarchyOps.slice(range).map { it.type to it.container }) - .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() }) - .inOrder() + assertThat(hierarchyOps.slice(range).map { it.type to it.container }) + .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() }) + .inOrder() } 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()) + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) } private fun WindowContainerTransaction.assertNoRemoveAt(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()) + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) } private fun WindowContainerTransaction.hasRemoveAt(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()) + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(op.container).isEqualTo(token.asBinder()) } private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) { - assertIndexInBounds(index) - val op = hierarchyOps[index] - assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT) - assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT) + assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) } private fun WindowContainerTransaction.assertLaunchTaskAt( index: Int, taskId: Int, - windowingMode: Int + windowingMode: Int, ) { - val keyLaunchWindowingMode = "android.activity.windowingMode" - - assertIndexInBounds(index) - val op = hierarchyOps[index] - assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK) - assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId) - assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED)) - .isEqualTo(windowingMode) + val keyLaunchWindowingMode = "android.activity.windowingMode" + + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK) + assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId) + assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED)) + .isEqualTo(windowingMode) } private fun WindowContainerTransaction?.anyDensityConfigChange( token: WindowContainerToken ): Boolean { - return this?.changes?.any { change -> - change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) - } ?: false + return this?.changes?.any { change -> + change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) + } ?: false } private fun WindowContainerTransaction?.anyWindowingModeChange( - token: WindowContainerToken + token: WindowContainerToken ): Boolean { -return this?.changes?.any { change -> - change.key == token.asBinder() && change.value.windowingMode >= 0 -} ?: false + return this?.changes?.any { change -> + change.key == token.asBinder() && change.value.windowingMode >= 0 + } ?: false } private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { - taskId = id - token = WindowContainerToken(mock(IWindowContainerToken::class.java)) + taskId = id + token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 1e4d108a9cda..e6f1fcf7f14f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -42,12 +42,12 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions @@ -85,9 +85,7 @@ import org.mockito.quality.Strictness @ExperimentalCoroutinesApi class DesktopTasksLimiterTest : ShellTestCase() { - @JvmField - @Rule - val setFlagsRule = SetFlagsRule() + @JvmField @Rule val setFlagsRule = SetFlagsRule() @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @Mock lateinit var transitions: Transitions @@ -108,9 +106,12 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Before fun setUp() { - mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT) - .spyStatic(DesktopModeStatus::class.java).startMocking() - doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) } + mockitoSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } shellInit = spy(ShellInit(testExecutor)) Dispatchers.setMain(StandardTestDispatcher()) testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) @@ -123,12 +124,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { persistentRepository, repositoryInitializer, testScope, - userManager + userManager, ) desktopTaskRepo = userRepositories.current desktopTasksLimiter = - DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT, - interactionJankMonitor, mContext, handler) + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + MAX_TASK_LIMIT, + interactionJankMonitor, + mContext, + handler, + ) } @After @@ -140,16 +148,30 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0, - interactionJankMonitor, mContext, handler) + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + 0, + interactionJankMonitor, + mContext, + handler, + ) } } @Test fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5, - interactionJankMonitor, mContext, handler) + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + -5, + interactionJankMonitor, + mContext, + handler, + ) } } @@ -168,11 +190,14 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.getTransitionObserver().onTransitionReady( + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( Binder() /* transition */, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -184,13 +209,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) desktopTasksLimiter.addPendingMinimizeChange( - pendingTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + pendingTransition, + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) - desktopTasksLimiter.getTransitionObserver().onTransitionReady( - taskTransition /* transition */, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + taskTransition /* transition */, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -201,13 +232,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskVisible(task) desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + transition, + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) - desktopTasksLimiter.getTransitionObserver().onTransitionReady( + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).build(), StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -218,13 +255,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + transition, + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) - desktopTasksLimiter.getTransitionObserver().onTransitionReady( + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).build(), StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -234,13 +277,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + transition, + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) - desktopTasksLimiter.getTransitionObserver().onTransitionReady( + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -251,22 +300,29 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) - - val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply { - mode = TRANSIT_TO_BACK - taskInfo = task - setStartAbsBounds(bounds) - } - desktopTasksLimiter.getTransitionObserver().onTransitionReady( transition, - TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) + + val change = + TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply { + mode = TRANSIT_TO_BACK + taskInfo = task + setStartAbsBounds(bounds) + } + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + transition, + TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() - assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo( - bounds) + assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)) + .isEqualTo(bounds) } @Test @@ -275,15 +331,22 @@ class DesktopTasksLimiterTest : ShellTestCase() { val newTransition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) - desktopTasksLimiter.getTransitionObserver().onTransitionMerged( - mergedTransition, newTransition) - - desktopTasksLimiter.getTransitionObserver().onTransitionReady( - newTransition, - TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + mergedTransition, + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) + desktopTasksLimiter + .getTransitionObserver() + .onTransitionMerged(mergedTransition, newTransition) + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + newTransition, + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() } @@ -297,7 +360,9 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( - DEFAULT_DISPLAY, wct) + DEFAULT_DISPLAY, + wct, + ) assertThat(wct.isEmpty).isTrue() } @@ -307,7 +372,9 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() { val wct = WindowContainerTransaction() desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( - DEFAULT_DISPLAY, wct) + DEFAULT_DISPLAY, + wct, + ) assertThat(wct.isEmpty).isTrue() } @@ -322,7 +389,9 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( - DEFAULT_DISPLAY, wct) + DEFAULT_DISPLAY, + wct, + ) assertThat(wct.hierarchyOps).hasSize(2) assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) @@ -351,10 +420,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = - desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, - wct = wct, - newFrontTaskId = setUpFreeformTask().taskId) + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + displayId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) assertThat(minimizedTaskId).isNull() assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added @@ -367,10 +437,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = - desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, - wct = wct, - newFrontTaskId = setUpFreeformTask().taskId) + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + displayId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId) assertThat(wct.hierarchyOps.size).isEqualTo(1) @@ -385,10 +456,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = - desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = 0, - wct = wct, - newFrontTaskId = setUpFreeformTask().taskId) + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + displayId = 0, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) assertThat(minimizedTaskId).isNull() assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added @@ -398,8 +470,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun getTaskToMinimize_tasksWithinLimit_returnsNull() { val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } - val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( - visibleOrderedTasks = tasks.map { it.taskId }) + val minimizedTask = + desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId }) assertThat(minimizedTask).isNull() } @@ -408,8 +480,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() { val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() } - val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( - visibleOrderedTasks = tasks.map { it.taskId }) + val minimizedTask = + desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId }) // first == front, last == back assertThat(minimizedTask).isEqualTo(tasks.last().taskId) @@ -418,12 +490,19 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() { desktopTasksLimiter = - DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2, - interactionJankMonitor, mContext, handler) + DesktopTasksLimiter( + transitions, + userRepositories, + shellTaskOrganizer, + MAX_TASK_LIMIT2, + interactionJankMonitor, + mContext, + handler, + ) val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() } - val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( - visibleOrderedTasks = tasks.map { it.taskId }) + val minimizedTask = + desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId }) // first == front, last == back assertThat(minimizedTask).isEqualTo(tasks.last().taskId) @@ -433,9 +512,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() { val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } - val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( + val minimizedTask = + desktopTasksLimiter.getTaskIdToMinimize( visibleOrderedTasks = tasks.map { it.taskId }, - newTaskIdInFront = setUpFreeformTask().taskId) + newTaskIdInFront = setUpFreeformTask().taskId, + ) // first == front, last == back assertThat(minimizedTask).isEqualTo(tasks.last().taskId) @@ -444,10 +525,12 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() { val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } - val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( - visibleOrderedTasks = tasks.map { it.taskId }, - newTaskIdInFront = null, - launchingNewIntent = true) + val minimizedTask = + desktopTasksLimiter.getTaskIdToMinimize( + visibleOrderedTasks = tasks.map { it.taskId }, + newTaskIdInFront = null, + launchingNewIntent = true, + ) // first == front, last == back assertThat(minimizedTask).isEqualTo(tasks.last().taskId) @@ -459,25 +542,28 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) - - desktopTasksLimiter.getTransitionObserver().onTransitionReady( transition, - TransitionInfoBuilder(TRANSIT_OPEN).build(), - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).build(), + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - verify(interactionJankMonitor).begin( - any(), - eq(mContext), - eq(handler), - eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) + verify(interactionJankMonitor) + .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - desktopTasksLimiter.getTransitionObserver().onTransitionFinished( - transition, - /* aborted = */ false) + desktopTasksLimiter + .getTransitionObserver() + .onTransitionFinished(transition, /* aborted= */ false) verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) } @@ -488,26 +574,28 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) - - desktopTasksLimiter.getTransitionObserver().onTransitionReady( transition, - TransitionInfoBuilder(TRANSIT_OPEN).build(), - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + transition, + TransitionInfoBuilder(TRANSIT_OPEN).build(), + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) - verify(interactionJankMonitor).begin( - any(), - eq(mContext), - eq(handler), - eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW), - ) + verify(interactionJankMonitor) + .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - desktopTasksLimiter.getTransitionObserver().onTransitionFinished( - transition, - /* aborted = */ true) + desktopTasksLimiter + .getTransitionObserver() + .onTransitionFinished(transition, /* aborted= */ true) verify(interactionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) } @@ -519,25 +607,28 @@ class DesktopTasksLimiterTest : ShellTestCase() { val newTransition = Binder() val task = setUpFreeformTask() desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId) - - desktopTasksLimiter.getTransitionObserver().onTransitionReady( mergedTransition, - TransitionInfoBuilder(TRANSIT_OPEN).build(), - StubTransaction() /* startTransaction */, - StubTransaction() /* finishTransaction */) + displayId = DEFAULT_DISPLAY, + taskId = task.taskId, + ) + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + mergedTransition, + TransitionInfoBuilder(TRANSIT_OPEN).build(), + StubTransaction() /* startTransaction */, + StubTransaction(), /* finishTransaction */ + ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition) - verify(interactionJankMonitor).begin( - any(), - eq(mContext), - eq(handler), - eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) + verify(interactionJankMonitor) + .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) - desktopTasksLimiter.getTransitionObserver().onTransitionMerged( - mergedTransition, - newTransition) + desktopTasksLimiter + .getTransitionObserver() + .onTransitionMerged(mergedTransition, newTransition) verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) } @@ -550,19 +641,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { } private fun markTaskVisible(task: RunningTaskInfo) { - desktopTaskRepo.updateTask( - task.displayId, - task.taskId, - isVisible = true - ) + desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true) } private fun markTaskHidden(task: RunningTaskInfo) { - desktopTaskRepo.updateTask( - task.displayId, - task.taskId, - isVisible = false - ) + desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = false) } private companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index b31a3f5fa642..c9623bcd5c16 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -99,7 +99,7 @@ class DesktopTasksTransitionObserverTest { shellTaskOrganizer, mixedHandler, backAnimationController, - shellInit + shellInit, ) } @@ -139,7 +139,10 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).minimizeTask(task.displayId, task.taskId) val pendingTransition = DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( - transition, task.taskId, isLastTask = false) + transition, + task.taskId, + isLastTask = false, + ) verify(mixedHandler).addPendingMixedTransition(pendingTransition) } @@ -162,7 +165,10 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).minimizeTask(task.displayId, task.taskId) val pendingTransition = DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( - transition, task.taskId, isLastTask = true) + transition, + task.taskId, + isLastTask = true, + ) verify(mixedHandler).addPendingMixedTransition(pendingTransition) } @@ -251,7 +257,8 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = task flags = flags - }) + } + ) if (withWallpaper) { addChange( Change(mock(), mock()).apply { @@ -259,14 +266,15 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = createWallpaperTaskInfo() flags = flags - }) + } + ) } } } private fun createOpenChangeTransition( task: RunningTaskInfo?, - type: Int = TRANSIT_OPEN + type: Int = TRANSIT_OPEN, ): TransitionInfo { return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply { addChange( @@ -275,7 +283,8 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = task flags = flags - }) + } + ) } } @@ -287,13 +296,14 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = task flags = flags - }) + } + ) } } private fun getLatestWct( @WindowManager.TransitionType type: Int = TRANSIT_OPEN, - handlerClass: Class<out Transitions.TransitionHandler>? = null + handlerClass: Class<out Transitions.TransitionHandler>? = null, ): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (handlerClass == null) { @@ -330,8 +340,6 @@ class DesktopTasksTransitionObserverTest { RunningTaskInfo().apply { token = mock<WindowContainerToken>() baseIntent = - Intent().apply { - component = DesktopWallpaperActivity.wallpaperActivityComponent - } + Intent().apply { component = DesktopWallpaperActivity.wallpaperActivityComponent } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt index a2e939d86adb..b9e307fa5973 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt @@ -82,20 +82,20 @@ class DesktopUserRepositoriesTest : ShellTestCase() { datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - val profiles: MutableList<UserInfo> = mutableListOf( - UserInfo(USER_ID_1, "User 1", 0), - UserInfo(PROFILE_ID_2, "Profile 2", 0)) + val profiles: MutableList<UserInfo> = + mutableListOf(UserInfo(USER_ID_1, "User 1", 0), UserInfo(PROFILE_ID_2, "Profile 2", 0)) whenever(userManager.getProfiles(USER_ID_1)).thenReturn(profiles) - userRepositories = DesktopUserRepositories( - context, - shellInit, - shellController, - persistentRepository, - repositoryInitializer, - datastoreScope, - userManager - ) + userRepositories = + DesktopUserRepositories( + context, + shellInit, + shellController, + persistentRepository, + repositoryInitializer, + datastoreScope, + userManager, + ) } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 13528b947609..e4eff9f1d592 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -61,9 +61,7 @@ import org.mockito.quality.Strictness @RunWithLooper @RunWith(AndroidTestingRunner::class) class DragToDesktopTransitionHandlerTest : ShellTestCase() { - @JvmField - @Rule - val mAnimatorTestRule = AnimatorTestRule(this) + @JvmField @Rule val mAnimatorTestRule = AnimatorTestRule(this) @Mock private lateinit var transitions: Transitions @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @@ -123,11 +121,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task + draggedTask = task, ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) verify(dragAnimator).startAnimation() @@ -137,13 +135,13 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() { performEarlyCancel( defaultHandler, - DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL, ) verify(transitions) .startTransition( eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), - eq(defaultHandler) + eq(defaultHandler), ) } @@ -151,7 +149,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() { performEarlyCancel( defaultHandler, - DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT, ) verify(splitScreenController) .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any()) @@ -161,7 +159,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() { performEarlyCancel( defaultHandler, - DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT, ) verify(splitScreenController) .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()) @@ -214,7 +212,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { .startTransition( eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP), any(), - eq(defaultHandler) + eq(defaultHandler), ) } @@ -277,14 +275,18 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { val startToken = startDrag(defaultHandler) // Then user cancelled after it had already started. - val cancelToken = cancelDragToDesktopTransition( - defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + val cancelToken = + cancelDragToDesktopTransition( + defaultHandler, + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL, + ) defaultHandler.mergeAnimation( cancelToken, TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0), mock<SurfaceControl.Transaction>(), startToken, - mock<Transitions.TransitionFinishCallback>()) + mock<Transitions.TransitionFinishCallback>(), + ) // Cancel animation should run since it had already started. verify(dragAnimator).cancelAnimator() @@ -296,8 +298,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { val startToken = startDrag(defaultHandler) // Then user cancelled after it had already started. - val cancelToken = cancelDragToDesktopTransition( - defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + val cancelToken = + cancelDragToDesktopTransition( + defaultHandler, + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL, + ) defaultHandler.onTransitionConsumed(cancelToken, aborted = true, null) // Cancel animation should run since it had already started. @@ -360,7 +365,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { .startTransition( eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), - eq(defaultHandler) + eq(defaultHandler), ) } @@ -374,7 +379,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { .startTransition( eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), - eq(defaultHandler) + eq(defaultHandler), ) } @@ -390,7 +395,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task), t = transaction, mergeTarget = mock(), - finishCallback = finishCallback + finishCallback = finishCallback, ) // Should NOT have any transaction changes @@ -414,11 +419,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, - draggedTask = task + draggedTask = task, ), t = mergedStartTransaction, mergeTarget = startTransition, - finishCallback = finishCallback + finishCallback = finishCallback, ) // Should show dragged task layer in start and finish transaction @@ -446,11 +451,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, - draggedTask = task + draggedTask = task, ), t = mergedStartTransaction, mergeTarget = startTransition, - finishCallback = finishCallback + finishCallback = finishCallback, ) // Should show dragged task layer in start and finish transaction @@ -475,7 +480,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { assertEquals( "Expects to return system properties stored value", /* expected= */ value, - /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name) + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name), ) } @@ -491,7 +496,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { assertEquals( "Expects to return scaled system properties stored value", /* expected= */ value / scale, - /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale) + /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale), ) } @@ -508,8 +513,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { /* expected= */ defaultValue, /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue( name, - default = defaultValue - ) + default = defaultValue, + ), ) } @@ -530,8 +535,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue( name, default = defaultValue, - scale = scale - ) + scale = scale, + ), ) } @@ -542,8 +547,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { defaultHandler.onTransitionConsumed(transition, aborted = true, mock()) verify(mockInteractionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)) - verify(mockInteractionJankMonitor, times(0)).cancel( - eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)) + verify(mockInteractionJankMonitor, times(0)) + .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)) } @Test @@ -554,13 +559,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { defaultHandler.onTaskResizeAnimationListener = mock() defaultHandler.mergeAnimation( transition = endTransition, - info = createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, - draggedTask = task - ), + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, + draggedTask = task, + ), t = mock<SurfaceControl.Transaction>(), mergeTarget = startTransition, - finishCallback = mock<Transitions.TransitionFinishCallback>() + finishCallback = mock<Transitions.TransitionFinishCallback>(), ) defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock()) @@ -574,7 +580,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { private fun startDrag( handler: DragToDesktopTransitionHandler, task: RunningTaskInfo = createTask(), - finishTransaction: SurfaceControl.Transaction = mock() + finishTransaction: SurfaceControl.Transaction = mock(), ): IBinder { whenever(dragAnimator.position).thenReturn(PointF()) // Simulate transition is started and is ready to animate. @@ -584,11 +590,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task + draggedTask = task, ), startTransaction = mock(), finishTransaction = finishTransaction, - finishCallback = {} + finishCallback = {}, ) return transition } @@ -596,14 +602,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { private fun startDragToDesktopTransition( handler: DragToDesktopTransitionHandler, task: RunningTaskInfo, - dragAnimator: MoveToDesktopAnimator + dragAnimator: MoveToDesktopAnimator, ): IBinder { val token = mock<IBinder>() whenever( transitions.startTransition( eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP), any(), - eq(handler) + eq(handler), ) ) .thenReturn(token) @@ -613,13 +619,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { private fun cancelDragToDesktopTransition( handler: DragToDesktopTransitionHandler, - cancelState: DragToDesktopTransitionHandler.CancelState): IBinder { + cancelState: DragToDesktopTransitionHandler.CancelState, + ): IBinder { val token = mock<IBinder>() whenever( transitions.startTransition( eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), - eq(handler) + eq(handler), ) ) .thenReturn(token) @@ -630,7 +637,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { private fun performEarlyCancel( handler: DragToDesktopTransitionHandler, - cancelState: DragToDesktopTransitionHandler.CancelState + cancelState: DragToDesktopTransitionHandler.CancelState, ) { val task = createTask() // Simulate transition is started and is ready to animate. @@ -643,11 +650,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { info = createTransitionInfo( type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task + draggedTask = task, ), startTransaction = mock(), finishTransaction = mock(), - finishCallback = {} + finishCallback = {}, ) // Don't even animate the "drag" since it was already cancelled. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt index 38c6ed90241c..e10253992bb5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt @@ -31,67 +31,71 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class WindowDecorCaptionHandleRepositoryTest { - private lateinit var captionHandleRepository: WindowDecorCaptionHandleRepository + private lateinit var captionHandleRepository: WindowDecorCaptionHandleRepository - @Before - fun setUp() { - captionHandleRepository = WindowDecorCaptionHandleRepository() - } + @Before + fun setUp() { + captionHandleRepository = WindowDecorCaptionHandleRepository() + } - @Test - fun initialState_noAction_returnsNoCaption() { - // Check the initial value of `captionStateFlow`. - assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption) - } + @Test + fun initialState_noAction_returnsNoCaption() { + // Check the initial value of `captionStateFlow`. + assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption) + } - @Test - fun notifyCaptionChange_toAppHandleVisible_updatesStateWithCorrectData() { - val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME) - val appHandleCaptionState = - CaptionState.AppHandle( - runningTaskInfo = taskInfo, - isHandleMenuExpanded = false, - globalAppHandleBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3), - isCapturedLinkAvailable = false) + @Test + fun notifyCaptionChange_toAppHandleVisible_updatesStateWithCorrectData() { + val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME) + val appHandleCaptionState = + CaptionState.AppHandle( + runningTaskInfo = taskInfo, + isHandleMenuExpanded = false, + globalAppHandleBounds = + Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3), + isCapturedLinkAvailable = false, + ) - captionHandleRepository.notifyCaptionChanged(appHandleCaptionState) + captionHandleRepository.notifyCaptionChanged(appHandleCaptionState) - assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHandleCaptionState) - } + assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHandleCaptionState) + } - @Test - fun notifyCaptionChange_toAppChipVisible_updatesStateWithCorrectData() { - val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME) - val appHeaderCaptionState = - CaptionState.AppHeader( - runningTaskInfo = taskInfo, - isHeaderMenuExpanded = true, - globalAppChipBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3), - isCapturedLinkAvailable = false) + @Test + fun notifyCaptionChange_toAppChipVisible_updatesStateWithCorrectData() { + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME) + val appHeaderCaptionState = + CaptionState.AppHeader( + runningTaskInfo = taskInfo, + isHeaderMenuExpanded = true, + globalAppChipBounds = + Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3), + isCapturedLinkAvailable = false, + ) - captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState) + captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState) - assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHeaderCaptionState) - } + assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHeaderCaptionState) + } - @Test - fun notifyCaptionChange_toNoCaption_updatesState() { - captionHandleRepository.notifyCaptionChanged(CaptionState.NoCaption) + @Test + fun notifyCaptionChange_toNoCaption_updatesState() { + captionHandleRepository.notifyCaptionChanged(CaptionState.NoCaption) - assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption) - } + assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption) + } - private fun createTaskInfo( - deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED, - runningTaskPackageName: String = LAUNCHER_PACKAGE_NAME - ): RunningTaskInfo = - RunningTaskInfo().apply { - configuration.windowConfiguration.apply { windowingMode = deviceWindowingMode } - topActivityInfo?.apply { packageName = runningTaskPackageName } - } + private fun createTaskInfo( + deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED, + runningTaskPackageName: String = LAUNCHER_PACKAGE_NAME, + ): RunningTaskInfo = + RunningTaskInfo().apply { + configuration.windowConfiguration.apply { windowingMode = deviceWindowingMode } + topActivityInfo?.apply { packageName = runningTaskPackageName } + } - private companion object { - const val GMAIL_PACKAGE_NAME = "com.google.android.gm" - const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" - } + private companion object { + const val GMAIL_PACKAGE_NAME = "com.google.android.gm" + const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt index c33005e7cfcc..1569f9dc9b10 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt @@ -25,10 +25,10 @@ import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask -import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder @@ -44,8 +44,8 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever /** - * Tests for {@link SystemModalsTransitionHandler} - * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest + * Tests for {@link SystemModalsTransitionHandler} Usage: atest + * WMShellUnitTests:SystemModalsTransitionHandlerTest */ @SmallTest @RunWith(AndroidTestingRunner::class) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt index 9c00c0cee8b1..5475032f35a9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt @@ -69,431 +69,448 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) class AppHandleEducationControllerTest : ShellTestCase() { - @JvmField - @Rule - val extendedMockitoRule = - ExtendedMockitoRule.Builder(this) - .mockStatic(DesktopModeStatus::class.java) - .mockStatic(SystemProperties::class.java) - .build()!! - @JvmField @Rule val setFlagsRule = SetFlagsRule() - - private lateinit var educationController: AppHandleEducationController - private lateinit var testableContext: TestableContext - private val testScope = TestScope() - private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto()) - private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption) - private val educationConfigCaptor = - argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>() - @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter - @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository - @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository - @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - Dispatchers.setMain(StandardTestDispatcher(testScope.testScheduler)) - testableContext = TestableContext(mContext) - whenever(mockDataStoreRepository.dataStoreFlow).thenReturn(testDataStoreFlow) - whenever(mockCaptionHandleRepository.captionStateFlow).thenReturn(testCaptionStateFlow) - whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) - - educationController = - AppHandleEducationController( - testableContext, - mockEducationFilter, - mockDataStoreRepository, - mockCaptionHandleRepository, - mockTooltipController, - testScope.backgroundScope, - Dispatchers.Main) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleVisible_shouldCallShowEducationTooltip() = - testScope.runTest { - // App handle is visible. Should show education tooltip. + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeStatus::class.java) + .mockStatic(SystemProperties::class.java) + .build()!! + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + private lateinit var educationController: AppHandleEducationController + private lateinit var testableContext: TestableContext + private val testScope = TestScope() + private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto()) + private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption) + private val educationConfigCaptor = + argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>() + @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter + @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository + @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository + @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + Dispatchers.setMain(StandardTestDispatcher(testScope.testScheduler)) + testableContext = TestableContext(mContext) + whenever(mockDataStoreRepository.dataStoreFlow).thenReturn(testDataStoreFlow) + whenever(mockCaptionHandleRepository.captionStateFlow).thenReturn(testCaptionStateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + + educationController = + AppHandleEducationController( + testableContext, + mockEducationFilter, + mockDataStoreRepository, + mockCaptionHandleRepository, + mockTooltipController, + testScope.backgroundScope, + Dispatchers.Main, + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleVisible_shouldCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_flagDisabled_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle visible but education aconfig flag disabled, should not show education + // tooltip. + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible but [shouldShowAppHandleEducation] api returns false, should + // not + // show education tooltip. + setShouldShowAppHandleEducation(false) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is not visible, should not show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle is not visible. + testCaptionStateFlow.value = CaptionState.NoCaption + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleHintViewedAlready_shouldNotCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible but app handle hint has been viewed before, + // should not show education tooltip. + // Mark app handle hint viewed. + testDataStoreFlow.value = + createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() = + testScope.runTest { + // App handle is visible but app handle hint has been viewed before. + // But as we are overriding prerequisite conditions, we should show app + // handle tooltip. + // Mark app handle hint viewed. + testDataStoreFlow.value = + createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) + val systemPropertiesKey = + "persist.desktop_windowing_app_handle_education_override_conditions" + whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) + .thenReturn(true) + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() = + testScope.runTest { + setShouldShowAppHandleEducation(false) + + // Simulate app handle visible and expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for some time before verifying + waitForBufferDelay() + + verify(mockDataStoreRepository, times(1)) + .updateAppHandleHintUsedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockDataStoreRepository, times(1)) + .updateAppHandleHintViewedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded. Should show second + // education + // tooltip. + showAndDismissFirstTooltip() + + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called twice, once for each tooltip. + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded after timeout. Should not + // show + // second education tooltip. + showAndDismissFirstTooltip() + + // Wait for timeout to occur, after this timeout we should not listen for further + // triggers + // anymore. + advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) + runCurrent() + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called once, just for the first tooltip. + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded twice. Should show second + // education tooltip only once. + showAndDismissFirstTooltip() + + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + // Simulate app handle being expanded twice. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + waitForBufferDelay() + + // [showEducationTooltip] should not be called thrice, even if app handle was expanded + // twice. Should be called twice, once for each tooltip. + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() = + testScope.runTest { + // After first tooltip is dismissed, app handle is not expanded. Should not show second + // education tooltip. + showAndDismissFirstTooltip() + + // Simulate app handle visible but not expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) + // Wait for next tooltip to showup. + waitForBufferDelay() + + // [showEducationTooltip] should be called once, just for the first tooltip. + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible. Should show third + // education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible after timeout. Should + // not + // show third education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Wait for timeout to occur, after this timeout we should not listen for further + // triggers + // anymore. + advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) + runCurrent() + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible twice. Should show + // third + // education tooltip only once. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + testCaptionStateFlow.value = createAppHeaderState() + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() = + testScope.runTest { + // After first two tooltips are dismissed, app header is visible but expanded. Should + // not + // show third education tooltip. + showAndDismissFirstTooltip() + showAndDismissSecondTooltip() + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() = + testScope.runTest { + // App handle is visible. Should show education tooltip. + setShouldShowAppHandleEducation(true) + val mockOpenHandleMenuCallback: (Int) -> Unit = mock() + val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() + educationController.setAppHandleEducationTooltipCallbacks( + mockOpenHandleMenuCallback, + mockToDesktopModeCallback, + ) + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onEducationClickAction.invoke() + + verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + @Ignore("b/371527084: revisit testcase after refactoring original logic") + fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() = + testScope.runTest { + // After first tooltip is dismissed, app handle is expanded. Should show second + // education + // tooltip. + showAndDismissFirstTooltip() + val mockOpenHandleMenuCallback: (Int) -> Unit = mock() + val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() + educationController.setAppHandleEducationTooltipCallbacks( + mockOpenHandleMenuCallback, + mockToDesktopModeCallback, + ) + // Simulate app handle expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + // Wait for next tooltip to showup. + waitForBufferDelay() + + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onEducationClickAction.invoke() + + verify(mockToDesktopModeCallback, times(1)).invoke(any(), any()) + } + + private suspend fun TestScope.showAndDismissFirstTooltip() { setShouldShowAppHandleEducation(true) - - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_flagDisabled_shouldNotCallShowEducationTooltip() = - testScope.runTest { - // App handle visible but education aconfig flag disabled, should not show education - // tooltip. - whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) - setShouldShowAppHandleEducation(true) - - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, never()).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() = - testScope.runTest { - // App handle is visible but [shouldShowAppHandleEducation] api returns false, should not - // show education tooltip. - setShouldShowAppHandleEducation(false) - - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, never()).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() = - testScope.runTest { - // App handle is not visible, should not show education tooltip. - setShouldShowAppHandleEducation(true) - - // Simulate app handle is not visible. - testCaptionStateFlow.value = CaptionState.NoCaption - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, never()).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleHintViewedAlready_shouldNotCallShowEducationTooltip() = - testScope.runTest { - // App handle is visible but app handle hint has been viewed before, - // should not show education tooltip. - // Mark app handle hint viewed. - testDataStoreFlow.value = - createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) - setShouldShowAppHandleEducation(true) - - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, never()).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() = - testScope.runTest { - // App handle is visible but app handle hint has been viewed before. - // But as we are overriding prerequisite conditions, we should show app - // handle tooltip. - // Mark app handle hint viewed. - testDataStoreFlow.value = - createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) - val systemPropertiesKey = - "persist.desktop_windowing_app_handle_education_override_conditions" - whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) - .thenReturn(true) - setShouldShowAppHandleEducation(true) - // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) // Wait for first tooltip to showup. waitForBufferDelay() - - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() = - testScope.runTest { + // [shouldShowAppHandleEducation] should return false as education has been viewed + // before. setShouldShowAppHandleEducation(false) + // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. + captureAndInvokeOnDismissAction() + } - // Simulate app handle visible and expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for some time before verifying - waitForBufferDelay() - - verify(mockDataStoreRepository, times(1)).updateAppHandleHintUsedTimestampMillis(eq(true)) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() = - testScope.runTest { - // App handle is visible. Should show education tooltip. - setShouldShowAppHandleEducation(true) - - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockDataStoreRepository, times(1)).updateAppHandleHintViewedTimestampMillis(eq(true)) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded. Should show second education - // tooltip. - showAndDismissFirstTooltip() - - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - - // [showEducationTooltip] should be called twice, once for each tooltip. - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded after timeout. Should not show - // second education tooltip. - showAndDismissFirstTooltip() - - // Wait for timeout to occur, after this timeout we should not listen for further triggers - // anymore. - advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) - runCurrent() - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - - // [showEducationTooltip] should be called once, just for the first tooltip. - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded twice. Should show second - // education tooltip only once. - showAndDismissFirstTooltip() - - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - // Simulate app handle being expanded twice. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - waitForBufferDelay() - - // [showEducationTooltip] should not be called thrice, even if app handle was expanded - // twice. Should be called twice, once for each tooltip. - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() = - testScope.runTest { - // After first tooltip is dismissed, app handle is not expanded. Should not show second - // education tooltip. - showAndDismissFirstTooltip() - - // Simulate app handle visible but not expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for next tooltip to showup. - waitForBufferDelay() - - // [showEducationTooltip] should be called once, just for the first tooltip. - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible. Should show third - // education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible after timeout. Should not - // show third education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Wait for timeout to occur, after this timeout we should not listen for further triggers - // anymore. - advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) - runCurrent() - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible twice. Should show third - // education tooltip only once. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible but expanded. Should not - // show third education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() = - testScope.runTest { - // App handle is visible. Should show education tooltip. - setShouldShowAppHandleEducation(true) - val mockOpenHandleMenuCallback: (Int) -> Unit = mock() - val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() - educationController.setAppHandleEducationTooltipCallbacks( - mockOpenHandleMenuCallback, mockToDesktopModeCallback) - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, atLeastOnce()) - .showEducationTooltip(educationConfigCaptor.capture(), any()) - educationConfigCaptor.lastValue.onEducationClickAction.invoke() - - verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded. Should show second education - // tooltip. - showAndDismissFirstTooltip() - val mockOpenHandleMenuCallback: (Int) -> Unit = mock() - val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() - educationController.setAppHandleEducationTooltipCallbacks( - mockOpenHandleMenuCallback, mockToDesktopModeCallback) + private fun TestScope.showAndDismissSecondTooltip() { // Simulate app handle expanded. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) // Wait for next tooltip to showup. waitForBufferDelay() + // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. + captureAndInvokeOnDismissAction() + } + private fun captureAndInvokeOnDismissAction() { verify(mockTooltipController, atLeastOnce()) .showEducationTooltip(educationConfigCaptor.capture(), any()) - educationConfigCaptor.lastValue.onEducationClickAction.invoke() - - verify(mockToDesktopModeCallback, times(1)).invoke(any(), any()) - } - - private suspend fun TestScope.showAndDismissFirstTooltip() { - setShouldShowAppHandleEducation(true) - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for first tooltip to showup. - waitForBufferDelay() - // [shouldShowAppHandleEducation] should return false as education has been viewed - // before. - setShouldShowAppHandleEducation(false) - // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. - captureAndInvokeOnDismissAction() - } - - private fun TestScope.showAndDismissSecondTooltip() { - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. - captureAndInvokeOnDismissAction() - } - - private fun captureAndInvokeOnDismissAction() { - verify(mockTooltipController, atLeastOnce()) - .showEducationTooltip(educationConfigCaptor.capture(), any()) - educationConfigCaptor.lastValue.onDismissAction.invoke() - } - - private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) = - whenever(mockEducationFilter.shouldShowAppHandleEducation(any())) - .thenReturn(shouldShowAppHandleEducation) - - /** - * Class under test waits for some time before showing education, simulate advance time before - * verifying or moving forward - */ - private fun TestScope.waitForBufferDelay() { - advanceTimeBy(APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS) - runCurrent() - } - - private companion object { - val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long = APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L - val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long = - APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L - } + educationConfigCaptor.lastValue.onDismissAction.invoke() + } + + private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) = + whenever(mockEducationFilter.shouldShowAppHandleEducation(any())) + .thenReturn(shouldShowAppHandleEducation) + + /** + * Class under test waits for some time before showing education, simulate advance time before + * verifying or moving forward + */ + private fun TestScope.waitForBufferDelay() { + advanceTimeBy(APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS) + runCurrent() + } + + private companion object { + val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long = + APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L + val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long = + APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt index 963890d1caa4..4db883d13551 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt @@ -49,85 +49,89 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi class AppHandleEducationDatastoreRepositoryTest { - private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext - private lateinit var testDatastore: DataStore<WindowingEducationProto> - private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository - private lateinit var datastoreScope: CoroutineScope - - @Before - fun setUp() { - Dispatchers.setMain(StandardTestDispatcher()) - datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) - testDatastore = - DataStoreFactory.create( - serializer = - AppHandleEducationDatastoreRepository.Companion.WindowingEducationProtoSerializer, - scope = datastoreScope) { - testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE) + private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext + private lateinit var testDatastore: DataStore<WindowingEducationProto> + private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository + private lateinit var datastoreScope: CoroutineScope + + @Before + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + testDatastore = + DataStoreFactory.create( + serializer = + AppHandleEducationDatastoreRepository.Companion + .WindowingEducationProtoSerializer, + scope = datastoreScope, + ) { + testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE) } - datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore) - } - - @After - fun tearDown() { - File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore") - .deleteRecursively() - - datastoreScope.cancel() - } - - @Test - fun getWindowingEducationProto_returnsCorrectProto() = - runTest(StandardTestDispatcher()) { - val windowingEducationProto = - createWindowingEducationProto( - appHandleHintViewedTimestampMillis = 123L, - appHandleHintUsedTimestampMillis = 124L, - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), - appUsageStatsLastUpdateTimestampMillis = 125L) - testDatastore.updateData { windowingEducationProto } - - val resultProto = datastoreRepository.windowingEducationProto() - - assertThat(resultProto).isEqualTo(windowingEducationProto) - } - - @Test - fun updateAppUsageStats_updatesDatastoreProto() = - runTest(StandardTestDispatcher()) { - val appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 3) - val appUsageStatsLastUpdateTimestamp = Duration.ofMillis(123L) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = appUsageStats, - appUsageStatsLastUpdateTimestampMillis = - appUsageStatsLastUpdateTimestamp.toMillis()) - - datastoreRepository.updateAppUsageStats(appUsageStats, appUsageStatsLastUpdateTimestamp) - - val result = testDatastore.data.first() - assertThat(result).isEqualTo(windowingEducationProto) - } - - @Test - fun updateAppHandleHintViewedTimestampMillis_updatesDatastoreProto() = - runTest(StandardTestDispatcher()) { - datastoreRepository.updateAppHandleHintViewedTimestampMillis(true) - - val result = testDatastore.data.first().hasAppHandleHintViewedTimestampMillis() - assertThat(result).isEqualTo(true) - } - - @Test - fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() = - runTest(StandardTestDispatcher()) { - datastoreRepository.updateAppHandleHintUsedTimestampMillis(true) - - val result = testDatastore.data.first().hasAppHandleHintUsedTimestampMillis() - assertThat(result).isEqualTo(true) - } - - companion object { - private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" - } + datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore) + } + + @After + fun tearDown() { + File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore") + .deleteRecursively() + + datastoreScope.cancel() + } + + @Test + fun getWindowingEducationProto_returnsCorrectProto() = + runTest(StandardTestDispatcher()) { + val windowingEducationProto = + createWindowingEducationProto( + appHandleHintViewedTimestampMillis = 123L, + appHandleHintUsedTimestampMillis = 124L, + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), + appUsageStatsLastUpdateTimestampMillis = 125L, + ) + testDatastore.updateData { windowingEducationProto } + + val resultProto = datastoreRepository.windowingEducationProto() + + assertThat(resultProto).isEqualTo(windowingEducationProto) + } + + @Test + fun updateAppUsageStats_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + val appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 3) + val appUsageStatsLastUpdateTimestamp = Duration.ofMillis(123L) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = appUsageStats, + appUsageStatsLastUpdateTimestampMillis = + appUsageStatsLastUpdateTimestamp.toMillis(), + ) + + datastoreRepository.updateAppUsageStats(appUsageStats, appUsageStatsLastUpdateTimestamp) + + val result = testDatastore.data.first() + assertThat(result).isEqualTo(windowingEducationProto) + } + + @Test + fun updateAppHandleHintViewedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateAppHandleHintViewedTimestampMillis(true) + + val result = testDatastore.data.first().hasAppHandleHintViewedTimestampMillis() + assertThat(result).isEqualTo(true) + } + + @Test + fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateAppHandleHintUsedTimestampMillis(true) + + val result = testDatastore.data.first().hasAppHandleHintUsedTimestampMillis() + assertThat(result).isEqualTo(true) + } + + companion object { + private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt index e5edd69155b5..2fc36efb1a41 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt @@ -53,189 +53,221 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) @kotlinx.coroutines.ExperimentalCoroutinesApi class AppHandleEducationFilterTest : ShellTestCase() { - @JvmField - @Rule - val extendedMockitoRule = - ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!! - @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository - @Mock private lateinit var mockUsageStatsManager: UsageStatsManager - private lateinit var educationFilter: AppHandleEducationFilter - private lateinit var testableResources: TestableResources - private lateinit var testableContext: TestableContext - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - testableContext = TestableContext(mContext) - testableResources = - testableContext.orCreateTestableResources.apply { - addOverride( - R.array.desktop_windowing_app_handle_education_allowlist_apps, - arrayOf(GMAIL_PACKAGE_NAME)) - addOverride(R.integer.desktop_windowing_education_required_time_since_setup_seconds, 0) - addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) - addOverride( - R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, MAX_VALUE) - addOverride(R.integer.desktop_windowing_education_app_launch_interval_seconds, 100) - } - testableContext.addMockSystemService(Context.USAGE_STATS_SERVICE, mockUsageStatsManager) - educationFilter = AppHandleEducationFilter(testableContext, datastoreRepository) - } - - @Test - fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest { - // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation - // should return true - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isTrue() - } - - @Test - fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest { - // Pass Youtube as current focus app, it is not in allowlist hence #shouldShowAppHandleEducation - // should return false - testableResources.addOverride( - R.array.desktop_windowing_app_handle_education_allowlist_apps, arrayOf(GMAIL_PACKAGE_NAME)) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(YOUTUBE_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - val captionState = - createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME)) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(captionState) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest { - // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation should - // return false - testableResources.addOverride( - R.integer.desktop_windowing_education_required_time_since_setup_seconds, MAX_VALUE) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest { - // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return false - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appHandleHintViewedTimestampMillis = 123L, - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest { - // App handle hint has been used before, hence #shouldShowAppHandleEducation should return false - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appHandleHintUsedTimestampMillis = 123L, - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest { - // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence - // #shouldShowAppHandleEducation should return false - testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest { - // UsageStats caching interval is set to 0ms, that means caching should happen very frequently - testableResources.addOverride( - R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, 0) - // The DataStore currently holds a proto object where Gmail's app launch count is recorded as 4. - // This value exceeds the minimum required count of 3. - testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = 0) - // The mocked UsageStatsManager is configured to return a launch count of 2 for Gmail. - // This value is below the minimum required count of 3. - `when`(mockUsageStatsManager.queryAndAggregateUsageStats(anyLong(), anyLong())) - .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 })) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - // Result should be false as queried usage stats should be considered to determine the result - // instead of cached stats - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest { - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - // Simulate app handle menu is expanded - val captionState = createAppHandleState(isHandleMenuExpanded = true) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(captionState) - - // We should not show app handle education if app menu is expanded - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest { - // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence - // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite - // conditions, #shouldShowAppHandleEducation should return true. - testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) - val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions" - whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())).thenReturn(true) - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isTrue() - } + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!! + @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository + @Mock private lateinit var mockUsageStatsManager: UsageStatsManager + private lateinit var educationFilter: AppHandleEducationFilter + private lateinit var testableResources: TestableResources + private lateinit var testableContext: TestableContext + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + testableContext = TestableContext(mContext) + testableResources = + testableContext.orCreateTestableResources.apply { + addOverride( + R.array.desktop_windowing_app_handle_education_allowlist_apps, + arrayOf(GMAIL_PACKAGE_NAME), + ) + addOverride( + R.integer.desktop_windowing_education_required_time_since_setup_seconds, + 0, + ) + addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) + addOverride( + R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, + MAX_VALUE, + ) + addOverride(R.integer.desktop_windowing_education_app_launch_interval_seconds, 100) + } + testableContext.addMockSystemService(Context.USAGE_STATS_SERVICE, mockUsageStatsManager) + educationFilter = AppHandleEducationFilter(testableContext, datastoreRepository) + } + + @Test + fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest { + // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation + // should return true + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isTrue() + } + + @Test + fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest { + // Pass Youtube as current focus app, it is not in allowlist hence + // #shouldShowAppHandleEducation + // should return false + testableResources.addOverride( + R.array.desktop_windowing_app_handle_education_allowlist_apps, + arrayOf(GMAIL_PACKAGE_NAME), + ) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(YOUTUBE_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + val captionState = + createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME)) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(captionState) + + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest { + // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation + // should + // return false + testableResources.addOverride( + R.integer.desktop_windowing_education_required_time_since_setup_seconds, + MAX_VALUE, + ) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest { + // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return + // false + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appHandleHintViewedTimestampMillis = 123L, + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest { + // App handle hint has been used before, hence #shouldShowAppHandleEducation should return + // false + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appHandleHintUsedTimestampMillis = 123L, + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest { + // Simulate that gmail app has been launched twice before, minimum app launch count is 3, + // hence + // #shouldShowAppHandleEducation should return false + testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest { + // UsageStats caching interval is set to 0ms, that means caching should happen very + // frequently + testableResources.addOverride( + R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, + 0, + ) + // The DataStore currently holds a proto object where Gmail's app launch count is recorded + // as 4. + // This value exceeds the minimum required count of 3. + testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = 0, + ) + // The mocked UsageStatsManager is configured to return a launch count of 2 for Gmail. + // This value is below the minimum required count of 3. + `when`(mockUsageStatsManager.queryAndAggregateUsageStats(anyLong(), anyLong())) + .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 })) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + // Result should be false as queried usage stats should be considered to determine the + // result + // instead of cached stats + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest { + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + // Simulate app handle menu is expanded + val captionState = createAppHandleState(isHandleMenuExpanded = true) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(captionState) + + // We should not show app handle education if app menu is expanded + assertThat(result).isFalse() + } + + @Test + fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest { + // Simulate that gmail app has been launched twice before, minimum app launch count is 3, + // hence + // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite + // conditions, #shouldShowAppHandleEducation should return true. + testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) + val systemPropertiesKey = + "persist.desktop_windowing_app_handle_education_override_conditions" + whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) + .thenReturn(true) + val windowingEducationProto = + createWindowingEducationProto( + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), + appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, + ) + `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) + + val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + + assertThat(result).isTrue() + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt index 6a5d9f67e4a7..8d7fb5d7af85 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt @@ -66,16 +66,27 @@ class DesktopWindowLimitRemoteHandlerTest { private fun createRemoteHandler(taskIdToMinimize: Int) = DesktopWindowLimitRemoteHandler( - shellExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize) + shellExecutor, + rootTaskDisplayAreaOrganizer, + remoteTransition, + taskIdToMinimize, + ) @Test fun startAnimation_dontSetTransition_returnsFalse() { val minimizeTask = createDesktopTask() val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId) - assertThat(remoteHandler.startAnimation(transition, - createMinimizeTransitionInfo(minimizeTask), startT, finishT, finishCallback) - ).isFalse() + assertThat( + remoteHandler.startAnimation( + transition, + createMinimizeTransitionInfo(minimizeTask), + startT, + finishT, + finishCallback, + ) + ) + .isFalse() } @Test @@ -84,9 +95,8 @@ class DesktopWindowLimitRemoteHandlerTest { remoteHandler.setTransition(transition) val info = createToFrontTransitionInfo() - assertThat( - remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback) - ).isFalse() + assertThat(remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)) + .isFalse() } @Test @@ -96,9 +106,8 @@ class DesktopWindowLimitRemoteHandlerTest { remoteHandler.setTransition(transition) val info = createMinimizeTransitionInfo(minimizeTask) - assertThat( - remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback) - ).isTrue() + assertThat(remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)) + .isTrue() } @Test @@ -109,8 +118,7 @@ class DesktopWindowLimitRemoteHandlerTest { remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback) - verify(rootTaskDisplayAreaOrganizer, times(0)) - .reparentToDisplayArea(anyInt(), any(), any()) + verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any()) } @Test @@ -154,14 +162,18 @@ class DesktopWindowLimitRemoteHandlerTest { private fun createToFrontTransitionInfo() = TransitionInfoBuilder(TRANSIT_TO_FRONT) - .addChange(TRANSIT_TO_FRONT, - TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()) + .addChange( + TRANSIT_TO_FRONT, + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(), + ) .build() private fun createMinimizeTransitionInfo(minimizeTask: ActivityManager.RunningTaskInfo) = TransitionInfoBuilder(TRANSIT_TO_FRONT) - .addChange(TRANSIT_TO_FRONT, - TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()) + .addChange( + TRANSIT_TO_FRONT, + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(), + ) .addChange(TRANSIT_TO_BACK, minimizeTask) .build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt index 4f7e80cf8330..eae206664021 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt @@ -63,9 +63,10 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { DataStoreFactory.create( serializer = DesktopPersistentRepository.Companion.DesktopPersistentRepositoriesSerializer, - scope = datastoreScope) { - testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE) - } + scope = datastoreScope, + ) { + testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE) + } datastoreRepository = DesktopPersistentRepository(testDatastore) } @@ -113,7 +114,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, - userId = DEFAULT_USER_ID) + userId = DEFAULT_USER_ID, + ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2) @@ -137,7 +139,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, - userId = DEFAULT_USER_ID) + userId = DEFAULT_USER_ID, + ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState) @@ -161,7 +164,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, freeformTasksInZOrder = freeformTasksInZOrder, - userId = DEFAULT_USER_ID) + userId = DEFAULT_USER_ID, + ) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty() @@ -194,7 +198,7 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { fun createDesktopTask( taskId: Int, - state: DesktopTaskState = DesktopTaskState.VISIBLE + state: DesktopTaskState = DesktopTaskState.VISIBLE, ): DesktopTask = DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt index cdf064b075a1..a3c441698905 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -47,15 +47,12 @@ import org.mockito.Mockito.spy import org.mockito.kotlin.mock import org.mockito.kotlin.whenever - @SmallTest @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi class DesktopRepositoryInitializerTest : ShellTestCase() { - @JvmField - @Rule - val setFlagsRule = SetFlagsRule() + @JvmField @Rule val setFlagsRule = SetFlagsRule() private lateinit var repositoryInitializer: DesktopRepositoryInitializer private lateinit var shellInit: ShellInit @@ -82,7 +79,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { persistentRepository, repositoryInitializer, datastoreScope, - userManager + userManager, ) } @@ -90,101 +87,94 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) fun initWithPersistence_multipleUsers_addedCorrectly() = runTest(StandardTestDispatcher()) { - whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( - mapOf( - USER_ID_1 to desktopRepositoryState1, - USER_ID_2 to desktopRepositoryState2 + whenever(persistentRepository.getUserDesktopRepositoryMap()) + .thenReturn( + mapOf( + USER_ID_1 to desktopRepositoryState1, + USER_ID_2 to desktopRepositoryState2, + ) ) - ) whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) .thenReturn(desktopRepositoryState1) whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2)) .thenReturn(desktopRepositoryState2) - whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) - .thenReturn(desktop1) - whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) - .thenReturn(desktop2) - whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3)) - .thenReturn(desktop3) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2) + whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3)).thenReturn(desktop3) repositoryInitializer.initialize(desktopUserRepositories) // Desktop Repository currently returns all tasks across desktops for a specific user - // since the repository currently doesn't handle desktops. This test logic should be updated + // since the repository currently doesn't handle desktops. This test logic should be + // updated // once the repository handles multiple desktops. assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getActiveTasks(DEFAULT_DISPLAY) - ) + desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY) + ) .containsExactly(1, 3, 4, 5) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) - ) + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) .containsExactly(5, 1) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getMinimizedTasks(DEFAULT_DISPLAY) - ) + desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY) + ) .containsExactly(3, 4) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_2) - .getActiveTasks(DEFAULT_DISPLAY) - ) + desktopUserRepositories.getProfile(USER_ID_2).getActiveTasks(DEFAULT_DISPLAY) + ) .containsExactly(7, 8) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_2) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) - ) + desktopUserRepositories + .getProfile(USER_ID_2) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) .contains(7) assertThat( - desktopUserRepositories.getProfile(USER_ID_2) - .getMinimizedTasks(DEFAULT_DISPLAY) - ).containsExactly(8) + desktopUserRepositories.getProfile(USER_ID_2).getMinimizedTasks(DEFAULT_DISPLAY) + ) + .containsExactly(8) } @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) fun initWithPersistence_singleUser_addedCorrectly() = runTest(StandardTestDispatcher()) { - whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( - mapOf( - USER_ID_1 to desktopRepositoryState1, - ) - ) + whenever(persistentRepository.getUserDesktopRepositoryMap()) + .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) .thenReturn(desktopRepositoryState1) - whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) - .thenReturn(desktop1) - whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) - .thenReturn(desktop2) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2) repositoryInitializer.initialize(desktopUserRepositories) // Desktop Repository currently returns all tasks across desktops for a specific user - // since the repository currently doesn't handle desktops. This test logic should be updated + // since the repository currently doesn't handle desktops. This test logic should be + // updated // once the repository handles multiple desktops. assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getActiveTasks(DEFAULT_DISPLAY) - ) + desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY) + ) .containsExactly(1, 3, 4, 5) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) - ) + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) .containsExactly(5, 1) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1) - .getMinimizedTasks(DEFAULT_DISPLAY) - ) + desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY) + ) .containsExactly(3, 4) .inOrder() } @@ -202,70 +192,73 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { const val DESKTOP_ID_3 = 4 val freeformTasksInZOrder1 = listOf(1, 3) - val desktop1: Desktop = Desktop.newBuilder() - .setDesktopId(DESKTOP_ID_1) - .addAllZOrderedTasks(freeformTasksInZOrder1) - .putTasksByTaskId( - 1, - DesktopTask.newBuilder() - .setTaskId(1) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build() - ) - .putTasksByTaskId( - 3, - DesktopTask.newBuilder() - .setTaskId(3) - .setDesktopTaskState(DesktopTaskState.MINIMIZED) - .build() - ) - .build() + val desktop1: Desktop = + Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_1) + .addAllZOrderedTasks(freeformTasksInZOrder1) + .putTasksByTaskId( + 1, + DesktopTask.newBuilder() + .setTaskId(1) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build(), + ) + .putTasksByTaskId( + 3, + DesktopTask.newBuilder() + .setTaskId(3) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build(), + ) + .build() val freeformTasksInZOrder2 = listOf(4, 5) - val desktop2: Desktop = Desktop.newBuilder() - .setDesktopId(DESKTOP_ID_2) - .addAllZOrderedTasks(freeformTasksInZOrder2) - .putTasksByTaskId( - 4, - DesktopTask.newBuilder() - .setTaskId(4) - .setDesktopTaskState(DesktopTaskState.MINIMIZED) - .build() - ) - .putTasksByTaskId( - 5, - DesktopTask.newBuilder() - .setTaskId(5) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build() - ) - .build() + val desktop2: Desktop = + Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_2) + .addAllZOrderedTasks(freeformTasksInZOrder2) + .putTasksByTaskId( + 4, + DesktopTask.newBuilder() + .setTaskId(4) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build(), + ) + .putTasksByTaskId( + 5, + DesktopTask.newBuilder() + .setTaskId(5) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build(), + ) + .build() val freeformTasksInZOrder3 = listOf(7, 8) - val desktop3: Desktop = Desktop.newBuilder() - .setDesktopId(DESKTOP_ID_3) - .addAllZOrderedTasks(freeformTasksInZOrder3) - .putTasksByTaskId( - 7, - DesktopTask.newBuilder() - .setTaskId(7) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build() - ) - .putTasksByTaskId( - 8, - DesktopTask.newBuilder() - .setTaskId(8) - .setDesktopTaskState(DesktopTaskState.MINIMIZED) - .build() - ) - .build() - val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder() - .putDesktop(DESKTOP_ID_1, desktop1) - .putDesktop(DESKTOP_ID_2, desktop2) - .build() - val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder() - .putDesktop(DESKTOP_ID_3, desktop3) - .build() + val desktop3: Desktop = + Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_3) + .addAllZOrderedTasks(freeformTasksInZOrder3) + .putTasksByTaskId( + 7, + DesktopTask.newBuilder() + .setTaskId(7) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build(), + ) + .putTasksByTaskId( + 8, + DesktopTask.newBuilder() + .setTaskId(8) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build(), + ) + .build() + val desktopRepositoryState1: DesktopRepositoryState = + DesktopRepositoryState.newBuilder() + .putDesktop(DESKTOP_ID_1, desktop1) + .putDesktop(DESKTOP_ID_2, desktop2) + .build() + val desktopRepositoryState2: DesktopRepositoryState = + DesktopRepositoryState.newBuilder().putDesktop(DESKTOP_ID_3, desktop3).build() } } |