Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +63 −1 Original line number Diff line number Diff line Loading @@ -177,7 +177,27 @@ class DesktopModeKeyGestureHandler( } KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK -> { logV("Key gesture KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK is handled") quitFocusedDesktopTask() val focusedTask = if ( DesktopExperienceFlags.CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT .isTrue ) { getGloballyFocusedTaskToClose() } else { getGloballyFocusedDesktopTask().also { task -> if (task != null) { logV("Found focused desktop task %d to close", task.taskId) } else { logV( "Globally focused desktop task is not found to close. focusedDisplay=%d", focusTransitionObserver.globallyFocusedDisplayId, ) } } } ?: return mainExecutor.execute { desktopModeWindowDecorViewModel.get().closeTask(focusedTask) } } KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_FULLSCREEN -> { logV("Key gesture TOGGLE_FULLSCREEN is handled") Loading Loading @@ -285,6 +305,48 @@ class DesktopModeKeyGestureHandler( return null } private fun getGloballyFocusedTaskToClose(): RunningTaskInfo? { getGloballyFocusedDesktopTask()?.let { desktopTask -> logV("getGloballyFocusedTaskToClose: Found desktop task: %d", desktopTask.taskId) return@getGloballyFocusedTaskToClose desktopTask } val tasks = desktopTasksController .get() .getFocusedNonDesktopTasks( displayId = focusTransitionObserver.globallyFocusedDisplayId, userId = desktopUserRepositories.current.userId, ) return when (tasks.size) { 0 -> { logW( "getGloballyFocusedTaskToClose: Task not found to close: " + "globallyFocusedTaskId=%d globallyFocusedDisplayId=%d", focusTransitionObserver.globallyFocusedTaskId, focusTransitionObserver.globallyFocusedDisplayId, ) null } 1 -> { val task = tasks.single() if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { logV("getGloballyFocusedTaskToClose: Found fullscreen task: %d", task.taskId) task } else { logW( "getGloballyFocusedTaskToClose: Ignored focused single non-fullscreen " + "task." ) null } } else -> { logW("getGloballyFocusedTaskToClose: Ignored focused 2+ tasks.") null } } } private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +39 −24 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; Loading Loading @@ -1224,13 +1225,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void onCloseTask(int taskId) { if (isTaskInSplitScreen(taskId)) { ProtoLog.i(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing split screen", TAG, taskId); mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(taskId).taskId, SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { return; } final WindowDecorationWrapper decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { ProtoLog.e(WM_SHELL_WINDOW_DECORATION, "%s: handled close key gesture but decoration is null, ignoring", TAG); "%s: onCloseTask(taskId=%d): decoration is null, ignoring", TAG, taskId); return; } if (DesktopExperienceFlags .CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT.isTrue() && decoration.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { ProtoLog.i(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing fullscreen task", TAG, taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.removeTask(decoration.getTaskInfo().token); mTransitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, null); return; } if (DesktopExperienceFlags Loading @@ -1243,6 +1257,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, nextFocusedWindow.a11yAnnounceNewFocusedWindow(); } } ProtoLog.w(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing desktop task", TAG, taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); final Function1<IBinder, Unit> runOnTransitionStart = mDesktopTasksController.onDesktopWindowClose(wct, Loading @@ -1253,7 +1269,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, runOnTransitionStart.invoke(transition); } } } /** Listener for caption touch events. */ public interface CaptionTouchStatusListener { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +26 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT 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 Loading Loading @@ -387,6 +388,31 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { verify(desktopModeWindowDecorViewModel).closeTask(task) } @Test @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT) fun keyGestureQuitFocusedDesktopTask_shouldQuitFullscreenTask() { // Setup a focused fullscreen task val task = setUpFullscreenTask() whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(task.displayId) whenever( desktopTasksController.getFocusedNonDesktopTasks(task.displayId, repository.userId) ) .thenReturn(listOf(task)) // Create and handle the key gesture event val event = KeyGestureEvent.Builder() .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK) .setKeycodes(intArrayOf(KeyEvent.KEYCODE_Q)) .setModifierState(KeyEvent.META_META_ON) .build() keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() // Verify closeTask is called verify(desktopModeWindowDecorViewModel).closeTask(task) } @Test fun keyGestureSwitchToPreviousDesk_activatesDesk() { val displayId = 2 Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +26 −0 Original line number Diff line number Diff line Loading @@ -57,11 +57,13 @@ import android.view.SurfaceView import android.view.View import android.view.ViewRootImpl import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.DesktopImmersiveController Loading Loading @@ -349,6 +351,30 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(mockFreeformTaskTransitionStarter, never()).startRemoveTransition(any()) } @Test @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT) fun testCloseTask_fullscreen_closesTask() { desktopModeWindowDecorViewModel.setFreeformTaskTransitionStarter( mockFreeformTaskTransitionStarter ) val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FULLSCREEN) desktopModeWindowDecorViewModel.closeTask(decor.taskInfo) val transactionCaptor = argumentCaptor<WindowContainerTransaction>() verify(mockTransitions) .startTransition( eq(WindowManager.TRANSIT_CLOSE), transactionCaptor.capture(), anyOrNull(), ) val wct = transactionCaptor.firstValue assertThat(wct.hierarchyOps).hasSize(1) val hierarchyOp = wct.hierarchyOps[0] assertThat(hierarchyOp.type).isEqualTo(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK) assertThat(hierarchyOp.container).isEqualTo(decor.taskInfo.token.asBinder()) } @Test @EnableFlags(Flags.FLAG_ENABLE_MINIMIZE_BUTTON) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS) Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +63 −1 Original line number Diff line number Diff line Loading @@ -177,7 +177,27 @@ class DesktopModeKeyGestureHandler( } KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK -> { logV("Key gesture KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK is handled") quitFocusedDesktopTask() val focusedTask = if ( DesktopExperienceFlags.CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT .isTrue ) { getGloballyFocusedTaskToClose() } else { getGloballyFocusedDesktopTask().also { task -> if (task != null) { logV("Found focused desktop task %d to close", task.taskId) } else { logV( "Globally focused desktop task is not found to close. focusedDisplay=%d", focusTransitionObserver.globallyFocusedDisplayId, ) } } } ?: return mainExecutor.execute { desktopModeWindowDecorViewModel.get().closeTask(focusedTask) } } KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_FULLSCREEN -> { logV("Key gesture TOGGLE_FULLSCREEN is handled") Loading Loading @@ -285,6 +305,48 @@ class DesktopModeKeyGestureHandler( return null } private fun getGloballyFocusedTaskToClose(): RunningTaskInfo? { getGloballyFocusedDesktopTask()?.let { desktopTask -> logV("getGloballyFocusedTaskToClose: Found desktop task: %d", desktopTask.taskId) return@getGloballyFocusedTaskToClose desktopTask } val tasks = desktopTasksController .get() .getFocusedNonDesktopTasks( displayId = focusTransitionObserver.globallyFocusedDisplayId, userId = desktopUserRepositories.current.userId, ) return when (tasks.size) { 0 -> { logW( "getGloballyFocusedTaskToClose: Task not found to close: " + "globallyFocusedTaskId=%d globallyFocusedDisplayId=%d", focusTransitionObserver.globallyFocusedTaskId, focusTransitionObserver.globallyFocusedDisplayId, ) null } 1 -> { val task = tasks.single() if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { logV("getGloballyFocusedTaskToClose: Found fullscreen task: %d", task.taskId) task } else { logW( "getGloballyFocusedTaskToClose: Ignored focused single non-fullscreen " + "task." ) null } } else -> { logW("getGloballyFocusedTaskToClose: Ignored focused 2+ tasks.") null } } } private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +39 −24 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.WindowManager; import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; Loading Loading @@ -1224,13 +1225,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void onCloseTask(int taskId) { if (isTaskInSplitScreen(taskId)) { ProtoLog.i(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing split screen", TAG, taskId); mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(taskId).taskId, SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { return; } final WindowDecorationWrapper decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { ProtoLog.e(WM_SHELL_WINDOW_DECORATION, "%s: handled close key gesture but decoration is null, ignoring", TAG); "%s: onCloseTask(taskId=%d): decoration is null, ignoring", TAG, taskId); return; } if (DesktopExperienceFlags .CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT.isTrue() && decoration.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { ProtoLog.i(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing fullscreen task", TAG, taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.removeTask(decoration.getTaskInfo().token); mTransitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, null); return; } if (DesktopExperienceFlags Loading @@ -1243,6 +1257,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, nextFocusedWindow.a11yAnnounceNewFocusedWindow(); } } ProtoLog.w(WM_SHELL_WINDOW_DECORATION, "%s: onCloseTask(taskId=%d): closing desktop task", TAG, taskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); final Function1<IBinder, Unit> runOnTransitionStart = mDesktopTasksController.onDesktopWindowClose(wct, Loading @@ -1253,7 +1269,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, runOnTransitionStart.invoke(transition); } } } /** Listener for caption touch events. */ public interface CaptionTouchStatusListener { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +26 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT 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 Loading Loading @@ -387,6 +388,31 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { verify(desktopModeWindowDecorViewModel).closeTask(task) } @Test @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT) fun keyGestureQuitFocusedDesktopTask_shouldQuitFullscreenTask() { // Setup a focused fullscreen task val task = setUpFullscreenTask() whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(task.displayId) whenever( desktopTasksController.getFocusedNonDesktopTasks(task.displayId, repository.userId) ) .thenReturn(listOf(task)) // Create and handle the key gesture event val event = KeyGestureEvent.Builder() .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_QUIT_FOCUSED_DESKTOP_TASK) .setKeycodes(intArrayOf(KeyEvent.KEYCODE_Q)) .setModifierState(KeyEvent.META_META_ON) .build() keyGestureEventHandler.handleKeyGestureEvent(event, null) testExecutor.flushAll() // Verify closeTask is called verify(desktopModeWindowDecorViewModel).closeTask(task) } @Test fun keyGestureSwitchToPreviousDesk_activatesDesk() { val displayId = 2 Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +26 −0 Original line number Diff line number Diff line Loading @@ -57,11 +57,13 @@ import android.view.SurfaceView import android.view.View import android.view.ViewRootImpl import android.view.WindowInsets.Type.statusBars import android.view.WindowManager import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.DesktopImmersiveController Loading Loading @@ -349,6 +351,30 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(mockFreeformTaskTransitionStarter, never()).startRemoveTransition(any()) } @Test @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT) fun testCloseTask_fullscreen_closesTask() { desktopModeWindowDecorViewModel.setFreeformTaskTransitionStarter( mockFreeformTaskTransitionStarter ) val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FULLSCREEN) desktopModeWindowDecorViewModel.closeTask(decor.taskInfo) val transactionCaptor = argumentCaptor<WindowContainerTransaction>() verify(mockTransitions) .startTransition( eq(WindowManager.TRANSIT_CLOSE), transactionCaptor.capture(), anyOrNull(), ) val wct = transactionCaptor.firstValue assertThat(wct.hierarchyOps).hasSize(1) val hierarchyOp = wct.hierarchyOps[0] assertThat(hierarchyOp.type).isEqualTo(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK) assertThat(hierarchyOp.container).isEqualTo(decor.taskInfo.token.asBinder()) } @Test @EnableFlags(Flags.FLAG_ENABLE_MINIMIZE_BUTTON) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS) Loading