Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +41 −14 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.Context import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction Loading @@ -36,8 +37,8 @@ import com.android.wm.shell.transition.Transitions /** * A [Transitions.TransitionObserver] that observes shell transitions and updates the * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop * mode and other transitions that originate both within and outside shell. * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop mode and * other transitions that originate both within and outside shell. */ class DesktopTasksTransitionObserver( private val context: Context, Loading @@ -47,6 +48,8 @@ class DesktopTasksTransitionObserver( shellInit: ShellInit ) : Transitions.TransitionObserver { private var transitionToCloseWallpaper: IBinder? = null init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) Loading @@ -70,6 +73,7 @@ class DesktopTasksTransitionObserver( handleBackNavigation(info) removeTaskIfNeeded(info) } removeWallpaperOnLastTaskClosingIfNeeded(transition, info) } private fun removeTaskIfNeeded(info: TransitionInfo) { Loading @@ -81,13 +85,9 @@ class DesktopTasksTransitionObserver( val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) continue if (desktopRepository.isActiveTask(taskInfo.taskId) && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM ) { desktopRepository.removeFreeformTask( taskInfo.displayId, taskInfo.taskId ) if (desktopRepository.isActiveTask(taskInfo.taskId) && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) } } } Loading @@ -104,14 +104,32 @@ class DesktopTasksTransitionObserver( if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && change.mode == TRANSIT_TO_BACK && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM ) { taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) } } } } private fun removeWallpaperOnLastTaskClosingIfNeeded( transition: IBinder, info: TransitionInfo ) { for (change in info.changes) { val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) { continue } if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 && change.mode == TRANSIT_CLOSE && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && desktopRepository.wallpaperActivityToken != null) { transitionToCloseWallpaper = transition } } } override fun onTransitionStarting(transition: IBinder) { // TODO: b/332682201 Update repository state } Loading @@ -122,6 +140,16 @@ class DesktopTasksTransitionObserver( override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { // TODO: b/332682201 Update repository state if (transitionToCloseWallpaper == transition) { // TODO: b/362469671 - Handle merging the animation when desktop is also closing. desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken -> transitions.startTransition( TRANSIT_CLOSE, WindowContainerTransaction().removeTask(wallpaperActivityToken), null) } transitionToCloseWallpaper = null } } private fun updateWallpaperToken(info: TransitionInfo) { Loading @@ -139,10 +167,9 @@ class DesktopTasksTransitionObserver( // task. shellTaskOrganizer.applyTransaction( WindowContainerTransaction() .setTaskTrimmableFromRecents(taskInfo.token, false) ) .setTaskTrimmableFromRecents(taskInfo.token, false)) } WindowManager.TRANSIT_CLOSE -> TRANSIT_CLOSE -> desktopRepository.wallpaperActivityToken = null else -> {} } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +75 −0 Original line number Diff line number Diff line Loading @@ -22,26 +22,38 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.IBinder import android.platform.test.annotations.EnableFlags import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.window.IWindowContainerToken import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isA import org.mockito.Mockito import org.mockito.kotlin.any import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.spy Loading Loading @@ -130,6 +142,27 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).removeFreeformTask(task.displayId, task.taskId) } @Test fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() { val mockTransition = Mockito.mock(IBinder::class.java) val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) val wallpaperToken = MockToken().token() whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1) whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken) transitionObserver.onTransitionReady( transition = mockTransition, info = createCloseTransition(task), startTransaction = mock(), finishTransaction = mock(), ) transitionObserver.onTransitionFinished(mockTransition, false) val wct = getLatestWct(type = TRANSIT_CLOSE) assertThat(wct.hierarchyOps).hasSize(1) wct.assertRemoveAt(index = 0, wallpaperToken) } private fun createBackNavigationTransition( task: RunningTaskInfo? ): TransitionInfo { Loading Loading @@ -160,6 +193,48 @@ class DesktopTasksTransitionObserverTest { } } private fun createCloseTransition( task: RunningTaskInfo? ): TransitionInfo { return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_CLOSE parent = null taskInfo = task flags = flags } ) } } private fun getLatestWct( @WindowManager.TransitionType type: Int = TRANSIT_OPEN, handlerClass: Class<out Transitions.TransitionHandler>? = null ): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (handlerClass == null) { Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull()) } else { Mockito.verify(transitions) .startTransition(eq(type), arg.capture(), isA(handlerClass)) } return arg.value } private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) { assertIndexInBounds(index) val op = hierarchyOps[index] assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) assertThat(op.container).isEqualTo(token.asBinder()) } private fun WindowContainerTransaction.assertIndexInBounds(index: Int) { assertWithMessage("WCT does not have a hierarchy operation at index $index") .that(hierarchyOps.size) .isGreaterThan(index) } private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) = RunningTaskInfo().apply { taskId = id Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +41 −14 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.Context import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction Loading @@ -36,8 +37,8 @@ import com.android.wm.shell.transition.Transitions /** * A [Transitions.TransitionObserver] that observes shell transitions and updates the * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop * mode and other transitions that originate both within and outside shell. * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop mode and * other transitions that originate both within and outside shell. */ class DesktopTasksTransitionObserver( private val context: Context, Loading @@ -47,6 +48,8 @@ class DesktopTasksTransitionObserver( shellInit: ShellInit ) : Transitions.TransitionObserver { private var transitionToCloseWallpaper: IBinder? = null init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) Loading @@ -70,6 +73,7 @@ class DesktopTasksTransitionObserver( handleBackNavigation(info) removeTaskIfNeeded(info) } removeWallpaperOnLastTaskClosingIfNeeded(transition, info) } private fun removeTaskIfNeeded(info: TransitionInfo) { Loading @@ -81,13 +85,9 @@ class DesktopTasksTransitionObserver( val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) continue if (desktopRepository.isActiveTask(taskInfo.taskId) && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM ) { desktopRepository.removeFreeformTask( taskInfo.displayId, taskInfo.taskId ) if (desktopRepository.isActiveTask(taskInfo.taskId) && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) } } } Loading @@ -104,14 +104,32 @@ class DesktopTasksTransitionObserver( if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && change.mode == TRANSIT_TO_BACK && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM ) { taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) } } } } private fun removeWallpaperOnLastTaskClosingIfNeeded( transition: IBinder, info: TransitionInfo ) { for (change in info.changes) { val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) { continue } if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 && change.mode == TRANSIT_CLOSE && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && desktopRepository.wallpaperActivityToken != null) { transitionToCloseWallpaper = transition } } } override fun onTransitionStarting(transition: IBinder) { // TODO: b/332682201 Update repository state } Loading @@ -122,6 +140,16 @@ class DesktopTasksTransitionObserver( override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { // TODO: b/332682201 Update repository state if (transitionToCloseWallpaper == transition) { // TODO: b/362469671 - Handle merging the animation when desktop is also closing. desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken -> transitions.startTransition( TRANSIT_CLOSE, WindowContainerTransaction().removeTask(wallpaperActivityToken), null) } transitionToCloseWallpaper = null } } private fun updateWallpaperToken(info: TransitionInfo) { Loading @@ -139,10 +167,9 @@ class DesktopTasksTransitionObserver( // task. shellTaskOrganizer.applyTransaction( WindowContainerTransaction() .setTaskTrimmableFromRecents(taskInfo.token, false) ) .setTaskTrimmableFromRecents(taskInfo.token, false)) } WindowManager.TRANSIT_CLOSE -> TRANSIT_CLOSE -> desktopRepository.wallpaperActivityToken = null else -> {} } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +75 −0 Original line number Diff line number Diff line Loading @@ -22,26 +22,38 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.IBinder import android.platform.test.annotations.EnableFlags import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.window.IWindowContainerToken import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isA import org.mockito.Mockito import org.mockito.kotlin.any import org.mockito.kotlin.isNull import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.spy Loading Loading @@ -130,6 +142,27 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).removeFreeformTask(task.displayId, task.taskId) } @Test fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() { val mockTransition = Mockito.mock(IBinder::class.java) val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) val wallpaperToken = MockToken().token() whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1) whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken) transitionObserver.onTransitionReady( transition = mockTransition, info = createCloseTransition(task), startTransaction = mock(), finishTransaction = mock(), ) transitionObserver.onTransitionFinished(mockTransition, false) val wct = getLatestWct(type = TRANSIT_CLOSE) assertThat(wct.hierarchyOps).hasSize(1) wct.assertRemoveAt(index = 0, wallpaperToken) } private fun createBackNavigationTransition( task: RunningTaskInfo? ): TransitionInfo { Loading Loading @@ -160,6 +193,48 @@ class DesktopTasksTransitionObserverTest { } } private fun createCloseTransition( task: RunningTaskInfo? ): TransitionInfo { return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_CLOSE parent = null taskInfo = task flags = flags } ) } } private fun getLatestWct( @WindowManager.TransitionType type: Int = TRANSIT_OPEN, handlerClass: Class<out Transitions.TransitionHandler>? = null ): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (handlerClass == null) { Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull()) } else { Mockito.verify(transitions) .startTransition(eq(type), arg.capture(), isA(handlerClass)) } return arg.value } private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) { assertIndexInBounds(index) val op = hierarchyOps[index] assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) assertThat(op.container).isEqualTo(token.asBinder()) } private fun WindowContainerTransaction.assertIndexInBounds(index: Int) { assertWithMessage("WCT does not have a hierarchy operation at index $index") .that(hierarchyOps.size) .isGreaterThan(index) } private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) = RunningTaskInfo().apply { taskId = id Loading