Extend close window shortcut to split-screen tasks
This change extends the Action+Ctrl+W keyboard shortcut to close the
focused task in a split-screen pair. This builds upon the existing
functionality that allows closing desktop and fullscreen windows.
The feature is controlled by the
`close_fullscreen_and_splitscreen_keyboard_shortcut` flag.
Bug: 441147192
Test: DesktopModeKeyGestureHandlerTest, DesktopModeWindowDecorViewModelTests
Flag: com.android.window.flags.close_fullscreen_and_splitscreen_keyboard_shortcut
Change-Id: Ibb9758db29714c02fdc785a5b93b2d9f27a56b52
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 9905435..3f6a7d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -1259,7 +1259,8 @@
FocusTransitionObserver focusTransitionObserver,
@ShellMainThread ShellExecutor mainExecutor,
DisplayController displayController,
- DesktopState desktopState) {
+ DesktopState desktopState,
+ Optional<SplitScreenController> splitScreenController) {
if (desktopState.canEnterDesktopMode()
&& (DesktopExperienceFlags.ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT.isTrue()
|| DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue())) {
@@ -1267,7 +1268,7 @@
desktopModeWindowDecorViewModel, desktopTasksController,
desktopUserRepositories,
inputManager, shellTaskOrganizer, focusTransitionObserver,
- mainExecutor, displayController, desktopState));
+ mainExecutor, displayController, desktopState, splitScreenController));
}
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 8c2bab2..0f60b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
@@ -37,6 +38,7 @@
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.desktopmode.DesktopState
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
import java.util.Optional
@@ -53,6 +55,7 @@
@ShellMainThread private val mainExecutor: ShellExecutor,
private val displayController: DisplayController,
private val desktopState: DesktopState,
+ private val splitScreenController: Optional<SplitScreenController>,
) : KeyGestureEventHandler {
init {
@@ -340,8 +343,21 @@
null
}
}
+ 2 -> {
+ val task = DesktopTasksController.getSplitFocusedTask(tasks[0], tasks[1])
+ if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ logV("getGloballyFocusedTaskToClose: Found split screen task: %d", task.taskId)
+ task
+ } else {
+ logW(
+ "getGloballyFocusedTaskToClose: Ignored focused pair non-split-screen " +
+ "tasks."
+ )
+ null
+ }
+ }
else -> {
- logW("getGloballyFocusedTaskToClose: Ignored focused 2+ tasks.")
+ logW("getGloballyFocusedTaskToClose: Ignored focused 3+ tasks.")
null
}
}
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 28c936a..448a24a 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
@@ -526,10 +526,6 @@
}
}
- /** Returns child task from two focused tasks in split screen mode. */
- private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) =
- if (task1.taskId == task2.parentTaskId) task2 else task1
-
/** Moves a desktop task into fullscreen mode. */
private fun moveDesktopTaskToFullscreen(
task: RunningTaskInfo,
@@ -6604,6 +6600,10 @@
UnminimizeReason.TASKBAR_MANAGE_WINDOW
}
+ /** Returns child task from two focused tasks in split screen mode. */
+ fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) =
+ if (task1.taskId == task2.parentTaskId) task2 else task1
+
@JvmField
/**
* A placeholder for a synthetic transition that isn't backed by a true system transition.
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 e8af353..d65383f 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
@@ -52,11 +52,13 @@
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSplitScreenTask
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.data.DesktopRepository
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.desktopmode.FakeDesktopConfig
import com.android.wm.shell.shared.desktopmode.FakeDesktopState
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.FocusTransitionObserver
@@ -104,6 +106,7 @@
private val desktopTasksController = mock<DesktopTasksController>()
private val desktopState = FakeDesktopState()
private val shellController = mock<ShellController>()
+ private val splitScreenController = mock<SplitScreenController>()
private lateinit var desktopModeKeyGestureHandler: DesktopModeKeyGestureHandler
private lateinit var keyGestureEventHandler: KeyGestureEventHandler
@@ -174,6 +177,7 @@
testExecutor,
displayController,
desktopState,
+ Optional.of(splitScreenController),
)
}
@@ -414,6 +418,34 @@
}
@Test
+ @EnableFlags(FLAG_CLOSE_FULLSCREEN_AND_SPLITSCREEN_KEYBOARD_SHORTCUT)
+ fun keyGestureQuitFocusedDesktopTask_shouldQuitSplitScreenTask() {
+ // Setup a focused split screen task
+ val task = setUpSplitScreenTask()
+ val splitRoot = setUpFullscreenTask()
+ task.parentTaskId = splitRoot.taskId
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(task.displayId)
+ whenever(
+ desktopTasksController.getFocusedNonDesktopTasks(task.displayId, repository.userId)
+ )
+ .thenReturn(listOf(splitRoot, task))
+ whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
+
+ // 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
whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(displayId)
@@ -492,6 +524,13 @@
return task
}
+ private fun setUpSplitScreenTask(): RunningTaskInfo {
+ val task = createSplitScreenTask()
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
private companion object {
private const val DEFAULT_USER_ID = 0
const val SECOND_DISPLAY = 2
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 2cba2fe..3bea35a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -78,6 +78,8 @@
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.util.StubTransaction
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DefaultWindowDecorationActions
@@ -376,6 +378,30 @@
}
@Test
+ fun testCloseTask_splitScreen_movesOtherToFullscreen() {
+ desktopModeWindowDecorViewModel.setFreeformTaskTransitionStarter(
+ mockFreeformTaskTransitionStarter
+ )
+ val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_MULTI_WINDOW)
+ val otherTask = createTask(windowingMode = WINDOWING_MODE_MULTI_WINDOW)
+
+ whenever(mockSplitScreenController.isTaskInSplitScreen(decor.taskInfo.taskId))
+ .thenReturn(true)
+ whenever(mockSplitScreenController.getSplitPosition(decor.taskInfo.taskId))
+ .thenReturn(SPLIT_POSITION_TOP_OR_LEFT)
+ whenever(mockSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT))
+ .thenReturn(otherTask)
+
+ desktopModeWindowDecorViewModel.closeTask(decor.taskInfo)
+
+ verify(mockSplitScreenController)
+ .moveTaskToFullscreen(
+ eq(otherTask.taskId),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_MINIMIZE_BUTTON)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_HEADER_STATE_CHANGE_ANNOUNCEMENTS)
fun testMinimizeButtonInFreeform_minimizeWindow() {