diff options
| author | 2024-10-22 06:59:19 +0000 | |
|---|---|---|
| committer | 2024-10-22 06:59:19 +0000 | |
| commit | 542df67fd50bda9575e3214a18dd87ffe829895f (patch) | |
| tree | f9db24a709b3684078e20a1b4b0f9e54aadcd6c9 | |
| parent | c61bc369738bfbedc83cff68324b0dead00167bc (diff) | |
| parent | df792e1d1580f6befa1872e0f4a8619db15fed77 (diff) | |
Merge "Wire "Action + Ctrl + D" to moveToNextDisplay" into main
7 files changed, 159 insertions, 6 deletions
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 5ee61bcd436a..2df541818e3d 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -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; @@ -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 { @@ -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: @@ -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: diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index 1260796810c2..b2ac640a468d 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -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 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 03a851bb9507..427df1784506 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 @@ -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; @@ -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, @@ -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 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 8dd7589c5937..b505bee469fe 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -226,6 +236,9 @@ class DesktopTasksController( } ) dragAndDropController.addListener(this) + if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) { + inputManager.registerKeyGestureEventHandler(this) + } } @VisibleForTesting @@ -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. @@ -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 { 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 af51e32b2086..55b44ac935ea 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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() @@ -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) @@ -310,6 +327,8 @@ class DesktopTasksControllerTest : ShellTestCase() { recentTasksController, mockInteractionJankMonitor, mockHandler, + mockInputManager, + mockFocusTransitionObserver, ) } @@ -1467,6 +1486,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() val freeformTask = setUpFreeformTask() diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index b488db533d12..2f5236f51c48 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -23,6 +23,7 @@ import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures; import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; +import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; import android.annotation.BinderThread; import android.annotation.MainThread; @@ -654,6 +655,18 @@ final class KeyGestureController { } } break; + case KeyEvent.KEYCODE_D: + if (enableMoveToNextDisplayShortcut()) { + if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { + return handleKeyGesture(deviceId, new int[]{keyCode}, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, + displayId, + focusedToken, /* flags = */0); + } + } + break; case KeyEvent.KEYCODE_SLASH: if (firstDown && event.isMetaPressed()) { return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON, diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 7526737f60bf..787ae06cd856 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -799,6 +799,27 @@ class KeyGestureControllerTests { } @Test + @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) + fun testMoveToNextDisplay() { + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "META + CTRL + D -> Move a task to next display", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_D + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, + intArrayOf(KeyEvent.KEYCODE_D), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test fun testCapsLockPressNotified() { val keyGestureController = KeyGestureController(context, testLooper.looper) val listener = KeyGestureEventListener() |