Wire "Action + Ctrl + D" to moveToNextDisplay

This CL wires the key combination of Action + Ctrl + D handled in
PhoneWindowManager to moveToNextDisplay in DesktopTasksController.

Flag: com.android.window.flags.enable_move_to_next_display_shortcut
Bug: 364154795
Test: com.android.server.policy.PhoneWindowManagerTests#testMoveToNextDisplayKeyboardShortcut
Change-Id: Ie186747db13517ccf28b47e77cce56d59b407006
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 5ee61bc..2df5418 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -99,6 +99,7 @@
     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 @@
             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 @@
             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 @@
                 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 1260796..b2ac640 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 03a851b..427df17 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.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 @@
             @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 @@
                 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 1d17cd6..09a4481 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.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.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.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.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.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.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.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 @@
     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 @@
             }
         )
         dragAndDropController.addListener(this)
+        if (useKeyGestureEventHandler() && enableMoveToNextDisplayShortcut()) {
+            inputManager.registerKeyGestureEventHandler(this)
+        }
     }
 
     @VisibleForTesting
@@ -1587,12 +1600,26 @@
         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 @@
         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 af51e32..55b44ac 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.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.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_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.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 @@
   @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 @@
   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 @@
     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 @@
         recentTasksController,
         mockInteractionJankMonitor,
         mockHandler,
+        mockInputManager,
+        mockFocusTransitionObserver,
       )
   }
 
@@ -1467,6 +1486,44 @@
   }
 
   @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 b488db5..2f5236f 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 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 @@
                     }
                 }
                 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 7526737..787ae06 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 @@
     }
 
     @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()