Loading core/java/android/hardware/input/KeyGestureEvent.java +6 −1 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59; public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60; public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61; public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62; public static final int FLAG_CANCELLED = 1; Loading Loading @@ -175,7 +176,7 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { Loading Loading @@ -415,6 +416,8 @@ public final class KeyGestureEvent { case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS; case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MOVE_TO_NEXT_DISPLAY; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT; case KEY_GESTURE_TYPE_LOCK_SCREEN: Loading Loading @@ -530,6 +533,8 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT"; case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT"; case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY: return "KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY"; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT"; case KEY_GESTURE_TYPE_LOCK_SCREEN: Loading libs/WindowManager/Shell/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" /> <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" /> <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" /> <application> <activity Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.LauncherApps; import android.hardware.input.InputManager; import android.os.Handler; import android.os.UserManager; import android.view.Choreographer; Loading Loading @@ -644,7 +645,9 @@ public abstract class WMShellModule { @ShellMainThread Handler mainHandler, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<RecentTasksController> recentTasksController, InteractionJankMonitor interactionJankMonitor) { InteractionJankMonitor interactionJankMonitor, InputManager inputManager, FocusTransitionObserver focusTransitionObserver) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, keyguardManager, Loading @@ -655,7 +658,8 @@ public abstract class WMShellModule { desktopRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), interactionJankMonitor, mainHandler); recentTasksController.orElse(null), interactionJankMonitor, mainHandler, inputManager, focusTransitionObserver); } @WMSingleton Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +55 −3 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.Binder import android.os.Handler import android.os.IBinder Loading @@ -42,6 +45,7 @@ import android.os.SystemProperties import android.util.Size import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.KeyEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE Loading @@ -57,6 +61,7 @@ import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.hardware.input.Flags.useKeyGestureEventHandler import com.android.internal.annotations.VisibleForTesting import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE Loading @@ -65,6 +70,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController Loading @@ -78,12 +84,13 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler Loading @@ -92,7 +99,6 @@ import com.android.wm.shell.shared.ShellSharedConstants import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity Loading @@ -105,6 +111,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility Loading Loading @@ -149,11 +156,14 @@ class DesktopTasksController( private val recentTasksController: RecentTasksController?, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, private val inputManager: InputManager, private val focusTransitionObserver: FocusTransitionObserver, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, DragAndDropController.DragAndDropListener, UserChangeListener { UserChangeListener, KeyGestureEventHandler { private val desktopMode: DesktopModeImpl private var visualIndicator: DesktopModeVisualIndicator? = null Loading Loading @@ -226,6 +236,9 @@ class DesktopTasksController( } ) dragAndDropController.addListener(this) if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) { inputManager.registerKeyGestureEventHandler(this) } } @VisibleForTesting Loading Loading @@ -1587,12 +1600,26 @@ class DesktopTasksController( getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) } } /** Move the focused desktop task in given `displayId` to next display. */ fun moveFocusedTaskToNextDisplay(displayId: Int) { getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) } } private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? { return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo -> taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM } } // TODO(b/364154795): wait for the completion of moveToNextDisplay transition, otherwise it will // pick a wrong task when a user quickly perform other actions with keyboard shortcuts after // moveToNextDisplay. private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? = shellTaskOrganizer.getRunningTasks().find { taskInfo -> taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && focusTransitionObserver.hasGlobalFocus(taskInfo) } /** * Requests a task be transitioned from desktop to split select. Applies needed windowing * changes if this transition is enabled. Loading Loading @@ -1947,6 +1974,31 @@ class DesktopTasksController( taskRepository.dump(pw, innerPrefix) } override fun handleKeyGestureEvent( event: KeyGestureEvent, focusedToken: IBinder? ): Boolean { if (!isKeyGestureSupported(event.keyGestureType)) return false when (event.keyGestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> { if (event.keycodes.contains(KeyEvent.KEYCODE_D) && event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) { logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled") getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) } return true } return false } else -> return false } } override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> enableMoveToNextDisplayShortcut() else -> false } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +57 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.Binder import android.os.Bundle import android.os.Handler Loading @@ -50,6 +53,7 @@ import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.Gravity import android.view.KeyEvent import android.view.SurfaceControl import android.view.WindowInsets import android.view.WindowManager Loading @@ -70,14 +74,18 @@ import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_R import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID 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.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags 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_FULLY_IMMERSIVE_IN_DESKTOP import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT import com.android.wm.shell.MockToken import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer Loading Loading @@ -112,6 +120,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.TestRemoteTransition import com.android.wm.shell.transition.Transitions Loading Loading @@ -206,6 +215,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter @Mock private lateinit var mockHandler: Handler @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock private lateinit var mockInputManager: InputManager @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController Loading @@ -214,6 +225,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private lateinit var testScope: CoroutineScope private lateinit var keyGestureEventHandler: KeyGestureEventHandler private val shellExecutor = TestShellExecutor() Loading Loading @@ -271,6 +283,11 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.setSplitScreenController(splitScreenController) controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter doAnswer { keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler) null }.whenever(mockInputManager).registerKeyGestureEventHandler(any()) shellInit.init() val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java) Loading Loading @@ -310,6 +327,8 @@ class DesktopTasksControllerTest : ShellTestCase() { recentTasksController, mockInteractionJankMonitor, mockHandler, mockInputManager, mockFocusTransitionObserver, ) } Loading Loading @@ -1466,6 +1485,44 @@ class DesktopTasksControllerTest : ShellTestCase() { } } @Test @EnableFlags( FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, FLAG_USE_KEY_GESTURE_EVENT_HANDLER ) fun moveToNextDisplay_withKeyGesture() { // 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) // Setup a focused task on secondary display, which is expected to move to default display val task = setUpFreeformTask(displayId = SECOND_DISPLAY) task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(mockFocusTransitionObserver.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 result = keyGestureEventHandler.handleKeyGestureEvent(event, null) assertThat(result).isTrue() 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 fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() Loading Loading
core/java/android/hardware/input/KeyGestureEvent.java +6 −1 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59; public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60; public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61; public static final int KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY = 62; public static final int FLAG_CANCELLED = 1; Loading Loading @@ -175,7 +176,7 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { Loading Loading @@ -415,6 +416,8 @@ public final class KeyGestureEvent { case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS; case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MOVE_TO_NEXT_DISPLAY; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT; case KEY_GESTURE_TYPE_LOCK_SCREEN: Loading Loading @@ -530,6 +533,8 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT"; case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT"; case KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY: return "KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY"; case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT"; case KEY_GESTURE_TYPE_LOCK_SCREEN: Loading
libs/WindowManager/Shell/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" /> <uses-permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION" /> <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" /> <application> <activity Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.LauncherApps; import android.hardware.input.InputManager; import android.os.Handler; import android.os.UserManager; import android.view.Choreographer; Loading Loading @@ -644,7 +645,9 @@ public abstract class WMShellModule { @ShellMainThread Handler mainHandler, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<RecentTasksController> recentTasksController, InteractionJankMonitor interactionJankMonitor) { InteractionJankMonitor interactionJankMonitor, InputManager inputManager, FocusTransitionObserver focusTransitionObserver) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, keyguardManager, Loading @@ -655,7 +658,8 @@ public abstract class WMShellModule { desktopRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), interactionJankMonitor, mainHandler); recentTasksController.orElse(null), interactionJankMonitor, mainHandler, inputManager, focusTransitionObserver); } @WMSingleton Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +55 −3 Original line number Diff line number Diff line Loading @@ -35,6 +35,9 @@ import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.Binder import android.os.Handler import android.os.IBinder Loading @@ -42,6 +45,7 @@ import android.os.SystemProperties import android.util.Size import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.KeyEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE Loading @@ -57,6 +61,7 @@ import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.hardware.input.Flags.useKeyGestureEventHandler import com.android.internal.annotations.VisibleForTesting import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE Loading @@ -65,6 +70,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController Loading @@ -78,12 +84,13 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler Loading @@ -92,7 +99,6 @@ import com.android.wm.shell.shared.ShellSharedConstants import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity Loading @@ -105,6 +111,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility Loading Loading @@ -149,11 +156,14 @@ class DesktopTasksController( private val recentTasksController: RecentTasksController?, private val interactionJankMonitor: InteractionJankMonitor, @ShellMainThread private val handler: Handler, private val inputManager: InputManager, private val focusTransitionObserver: FocusTransitionObserver, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, DragAndDropController.DragAndDropListener, UserChangeListener { UserChangeListener, KeyGestureEventHandler { private val desktopMode: DesktopModeImpl private var visualIndicator: DesktopModeVisualIndicator? = null Loading Loading @@ -226,6 +236,9 @@ class DesktopTasksController( } ) dragAndDropController.addListener(this) if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) { inputManager.registerKeyGestureEventHandler(this) } } @VisibleForTesting Loading Loading @@ -1587,12 +1600,26 @@ class DesktopTasksController( getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) } } /** Move the focused desktop task in given `displayId` to next display. */ fun moveFocusedTaskToNextDisplay(displayId: Int) { getFocusedFreeformTask(displayId)?.let { moveToNextDisplay(it.taskId) } } private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? { return shellTaskOrganizer.getRunningTasks(displayId).find { taskInfo -> taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM } } // TODO(b/364154795): wait for the completion of moveToNextDisplay transition, otherwise it will // pick a wrong task when a user quickly perform other actions with keyboard shortcuts after // moveToNextDisplay. private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? = shellTaskOrganizer.getRunningTasks().find { taskInfo -> taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && focusTransitionObserver.hasGlobalFocus(taskInfo) } /** * Requests a task be transitioned from desktop to split select. Applies needed windowing * changes if this transition is enabled. Loading Loading @@ -1947,6 +1974,31 @@ class DesktopTasksController( taskRepository.dump(pw, innerPrefix) } override fun handleKeyGestureEvent( event: KeyGestureEvent, focusedToken: IBinder? ): Boolean { if (!isKeyGestureSupported(event.keyGestureType)) return false when (event.keyGestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> { if (event.keycodes.contains(KeyEvent.KEYCODE_D) && event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON)) { logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled") getGloballyFocusedFreeformTask()?.let { moveToNextDisplay(it.taskId) } return true } return false } else -> return false } } override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> enableMoveToNextDisplayShortcut() else -> false } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +57 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.Binder import android.os.Bundle import android.os.Handler Loading @@ -50,6 +53,7 @@ import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.Gravity import android.view.KeyEvent import android.view.SurfaceControl import android.view.WindowInsets import android.view.WindowManager Loading @@ -70,14 +74,18 @@ import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_R import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID 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.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags 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_FULLY_IMMERSIVE_IN_DESKTOP import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT import com.android.wm.shell.MockToken import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer Loading Loading @@ -112,6 +120,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.TestRemoteTransition import com.android.wm.shell.transition.Transitions Loading Loading @@ -206,6 +215,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter @Mock private lateinit var mockHandler: Handler @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock private lateinit var mockInputManager: InputManager @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController Loading @@ -214,6 +225,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private lateinit var testScope: CoroutineScope private lateinit var keyGestureEventHandler: KeyGestureEventHandler private val shellExecutor = TestShellExecutor() Loading Loading @@ -271,6 +283,11 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.setSplitScreenController(splitScreenController) controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter doAnswer { keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler) null }.whenever(mockInputManager).registerKeyGestureEventHandler(any()) shellInit.init() val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java) Loading Loading @@ -310,6 +327,8 @@ class DesktopTasksControllerTest : ShellTestCase() { recentTasksController, mockInteractionJankMonitor, mockHandler, mockInputManager, mockFocusTransitionObserver, ) } Loading Loading @@ -1466,6 +1485,44 @@ class DesktopTasksControllerTest : ShellTestCase() { } } @Test @EnableFlags( FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS, FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, FLAG_USE_KEY_GESTURE_EVENT_HANDLER ) fun moveToNextDisplay_withKeyGesture() { // 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) // Setup a focused task on secondary display, which is expected to move to default display val task = setUpFreeformTask(displayId = SECOND_DISPLAY) task.isFocused = true whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task)) whenever(mockFocusTransitionObserver.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 result = keyGestureEventHandler.handleKeyGestureEvent(event, null) assertThat(result).isTrue() 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 fun getTaskWindowingMode() { val fullscreenTask = setUpFullscreenTask() Loading