diff options
4 files changed, 182 insertions, 81 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 968f40c3df5d..afa27f9f1309 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -43,6 +43,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT @@ -1061,7 +1062,10 @@ class DesktopTasksController( // Check if freeform task launch during recents should be handled shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task) // Check if the closing task needs to be handled - TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task) + TransitionUtil.isClosingType(request.type) -> handleTaskClosing( + task, + request.type + ) // Check if the top task shouldn't be allowed to enter desktop mode isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) // Check if fullscreen task should be updated @@ -1288,7 +1292,10 @@ class DesktopTasksController( } /** Handle task closing by removing wallpaper activity if it's the last active task */ - private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleTaskClosing( + task: RunningTaskInfo, + transitionType: Int + ): WindowContainerTransaction? { logV("handleTaskClosing") if (!isDesktopModeShowing(task.displayId)) return null @@ -1301,9 +1308,10 @@ class DesktopTasksController( removeWallpaperActivity(wct) } taskRepository.addClosingTask(task.displayId, task.taskId) - // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task. + // If a CLOSE is triggered on a desktop task, remove the task. if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() && - taskRepository.isVisibleTask(task.taskId) + taskRepository.isVisibleTask(task.taskId) && + transitionType == TRANSIT_CLOSE ) { wct.removeTask(task.token) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 0841628853a3..4796c4d0655a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -16,16 +16,19 @@ package com.android.wm.shell.desktopmode +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager +import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction +import android.window.flags.DesktopModeFlags +import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -64,6 +67,30 @@ class DesktopTasksTransitionObserver( ) { // TODO: b/332682201 Update repository state updateWallpaperToken(info) + + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { + handleBackNavigation(info) + } + } + + private fun handleBackNavigation(info: TransitionInfo) { + // When default back navigation happens, transition type is TO_BACK and the change is + // TO_BACK. Mark the task going to back as minimized. + if (info.type == TRANSIT_TO_BACK) { + for (change in info.changes) { + val taskInfo = change.taskInfo + if (taskInfo == null || taskInfo.taskId == -1) { + continue + } + + if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 && + change.mode == TRANSIT_TO_BACK && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM + ) { + desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + } + } + } } override fun onTransitionStarting(transition: IBinder) { 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 ee545209904f..29aea006c076 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 @@ -2086,16 +2086,13 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { + @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)) - assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token) + assertNull(result, "Should not handle request") } @Test @@ -2137,26 +2134,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_singleTask_withWallpaper_withBackNav_removesWallpaperAndTask() { - 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) - result.assertRemoveAt(index = 1, task.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleTaskWithToken_noBackNav_removesWallpaper() { + fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -2183,23 +2162,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags( - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION - ) - fun handleRequest_backTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { - val task1 = setUpFreeformTask() - setUpFreeformTask() - - taskRepository.wallpaperActivityToken = MockToken().token() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token) - } - - @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2226,29 +2189,11 @@ class DesktopTasksControllerTest : ShellTestCase() { // Should create remove wallpaper transaction assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - result.assertRemoveAt(index = 1, task1.token) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_noBackNav_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_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, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -2261,23 +2206,6 @@ class DesktopTasksControllerTest : ShellTestCase() { // Should create remove wallpaper transaction assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) - result.assertRemoveAt(index = 1, task1.token) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noBackNav_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_TO_BACK)) - - // Should create remove wallpaper transaction - assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken) } @Test 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 new file mode 100644 index 000000000000..c989d1640f80 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.platform.test.annotations.EnableFlags +import android.view.Display.DEFAULT_DISPLAY +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 com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.window.flags.Flags +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 org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class DesktopTasksTransitionObserverTest { + + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeStatus::class.java) + .build()!! + + private val testExecutor = mock<ShellExecutor>() + private val mockShellInit = mock<ShellInit>() + private val transitions = mock<Transitions>() + private val context = mock<Context>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val taskRepository = mock<DesktopModeTaskRepository>() + + private lateinit var transitionObserver: DesktopTasksTransitionObserver + private lateinit var shellInit: ShellInit + + @Before + fun setup() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + shellInit = spy(ShellInit(testExecutor)) + + transitionObserver = + DesktopTasksTransitionObserver( + context, taskRepository, transitions, shellTaskOrganizer, shellInit + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun backNavigation_taskMinimized() { + val task = createTaskInfo(1) + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) + + transitionObserver.onTransitionReady( + transition = mock(), + info = + createBackNavigationTransition(task), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).minimizeTask(task.displayId, task.taskId) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun backNavigation_nullTaskInfo_taskNotMinimized() { + val task = createTaskInfo(1) + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) + + transitionObserver.onTransitionReady( + transition = mock(), + info = + createBackNavigationTransition(null), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId) + } + + private fun createBackNavigationTransition( + task: RunningTaskInfo? + ): TransitionInfo { + return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_TO_BACK + parent = null + taskInfo = task + flags = flags + } + ) + } + } + + private fun createTaskInfo(id: Int) = + RunningTaskInfo().apply { + taskId = id + displayId = DEFAULT_DISPLAY + configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)) + baseIntent = Intent().apply { + component = ComponentName("package", "component.name") + } + } +} |