Shift Key gestures handled by InputManager to IMS from PWM

Test: atest InputTests
Bug: 358569822
Flag: com.android.hardware.input.use_key_gesture_event_handler
Change-Id: Ia104f506a22dacf6886bc86c9876c66bfd9751c6
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index edc35e5..65adaba 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -456,6 +456,14 @@
         void registerLocalService(InputManagerInternal localService) {
             LocalServices.addService(InputManagerInternal.class, localService);
         }
+
+        KeyboardBacklightControllerInterface getKeyboardBacklightController(
+                NativeInputManagerService nativeService, PersistentDataStore dataStore) {
+            return InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+                    ? new KeyboardBacklightController(mContext, nativeService, dataStore,
+                    mLooper, mUEventManager)
+                    : new KeyboardBacklightControllerInterface() {};
+        }
     }
 
     public InputManagerService(Context context) {
@@ -479,10 +487,7 @@
                         injector.getLooper(), this) : null;
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
                 injector.getUEventManager());
-        mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
-                ? new KeyboardBacklightController(mContext, mNative, mDataStore,
-                        injector.getLooper(), injector.getUEventManager())
-                : new KeyboardBacklightControllerInterface() {};
+        mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative, mDataStore);
         mStickyModifierStateController = new StickyModifierStateController();
         mKeyGestureController = new KeyGestureController(mContext, injector.getLooper());
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
@@ -606,6 +611,8 @@
         mKeyRemapper.systemRunning();
         mPointerIconCache.systemRunning();
         mKeyboardGlyphManager.systemRunning();
+
+        initKeyGestures();
     }
 
     private void reloadDeviceAliases() {
@@ -2504,6 +2511,59 @@
                 null, null, null) == PERMISSION_GRANTED;
     }
 
+    private void initKeyGestures() {
+        if (!useKeyGestureEventHandler()) {
+            return;
+        }
+        InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+        im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
+            @Override
+            public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
+                    @Nullable IBinder focussedToken) {
+                int deviceId = event.getDeviceId();
+                boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                        && !event.isCancelled();
+                switch (event.getKeyGestureType()) {
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                        if (complete) {
+                            mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
+                        }
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                        if (complete) {
+                            mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
+                        }
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                        // TODO(b/367748270): Add functionality to turn keyboard backlight on/off.
+                        return true;
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                        if (complete) {
+                            mNative.toggleCapsLock(deviceId);
+                        }
+                        return true;
+                    default:
+                        return false;
+
+                }
+            }
+
+            @Override
+            public boolean isKeyGestureSupported(int gestureType) {
+                switch (gestureType) {
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                        return true;
+                    default:
+                        return false;
+
+                }
+            }
+        });
+    }
+
     // Native callback.
     @SuppressWarnings("unused")
     private KeyEvent dispatchUnhandledKey(IBinder focus, KeyEvent event, int policyFlags) {
@@ -3207,6 +3267,8 @@
             mKeyboardBacklightController.onInteractiveChanged(interactive);
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void toggleCapsLock(int deviceId) {
             mNative.toggleCapsLock(deviceId);
@@ -3294,11 +3356,15 @@
             mKeyboardBacklightController.notifyUserActivity();
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void incrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
         }
 
+        // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
+        //  handler refactoring complete
         @Override
         public void decrementKeyboardBacklight(int deviceId) {
             mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ef37464..02c02b0 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3849,10 +3849,6 @@
                     case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
                     case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
-                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
                         return true;
                     default:
                         return false;
@@ -4010,25 +4006,6 @@
                     sendSwitchKeyboardLayout(displayId, focusedToken, direction);
                 }
                 return true;
-            // TODO (b/358569822): Move these input specific gesture handling to IMS since we
-            //  are calling into InputManager through internal API anyways
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
-                if (complete) {
-                    mInputManagerInternal.incrementKeyboardBacklight(deviceId);
-                }
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
-                if (complete) {
-                    mInputManagerInternal.decrementKeyboardBacklight(deviceId);
-                }
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
-                return true;
-            case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
-                if (complete) {
-                    mInputManagerInternal.toggleCapsLock(deviceId);
-                }
-                return true;
         }
         return false;
     }
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index c2e71f8..2a82d5f 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -21,6 +21,8 @@
 import android.content.Context
 import android.content.ContextWrapper
 import android.content.PermissionChecker
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
 import android.hardware.display.DisplayManager
 import android.hardware.display.DisplayViewport
 import android.hardware.display.VirtualDisplay
@@ -29,10 +31,13 @@
 import android.os.InputEventInjectionSync
 import android.os.SystemClock
 import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings
 import android.view.View.OnKeyListener
 import android.view.InputDevice
+import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.SurfaceHolder
 import android.view.SurfaceView
@@ -94,6 +99,10 @@
         ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
             .mockStatic(PermissionChecker::class.java).build()!!
 
+    @JvmField
+    @Rule
+    val setFlagsRule = SetFlagsRule()
+
     @get:Rule
     val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
 
@@ -107,8 +116,14 @@
     private lateinit var windowManagerInternal: WindowManagerInternal
 
     @Mock
+    private lateinit var packageManagerInternal: PackageManagerInternal
+
+    @Mock
     private lateinit var uEventManager: UEventManager
 
+    @Mock
+    private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+
     private lateinit var service: InputManagerService
     private lateinit var localService: InputManagerInternal
     private lateinit var context: Context
@@ -135,15 +150,29 @@
                 override fun registerLocalService(service: InputManagerInternal?) {
                     localService = service!!
                 }
+
+                override fun getKeyboardBacklightController(
+                    nativeService: NativeInputManagerService?,
+                    dataStore: PersistentDataStore?
+                ): InputManagerService.KeyboardBacklightControllerInterface {
+                    return kbdController
+                }
             })
         inputManagerGlobalSession = InputManagerGlobal.createTestSession(service)
         val inputManager = InputManager(context)
         whenever(context.getSystemService(InputManager::class.java)).thenReturn(inputManager)
         whenever(context.getSystemService(Context.INPUT_SERVICE)).thenReturn(inputManager)
+        whenever(context.checkCallingOrSelfPermission(Manifest.permission.MANAGE_KEY_GESTURES))
+            .thenReturn(
+                PackageManager.PERMISSION_GRANTED
+            )
 
         ExtendedMockito.doReturn(windowManagerInternal).`when` {
             LocalServices.getService(eq(WindowManagerInternal::class.java))
         }
+        ExtendedMockito.doReturn(packageManagerInternal).`when` {
+            LocalServices.getService(eq(PackageManagerInternal::class.java))
+        }
 
         assertTrue("Local service must be registered", this::localService.isInitialized)
         service.setWindowManagerCallbacks(wmCallbacks)
@@ -442,6 +471,37 @@
         verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
     }
 
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun handleKeyGestures_keyboardBacklight() {
+        service.systemRunning()
+
+        val backlightDownEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN)
+        service.interceptKeyBeforeDispatching(null, backlightDownEvent, /* policyFlags = */0)
+        verify(kbdController).decrementKeyboardBacklight(anyInt())
+
+        val backlightUpEvent = createKeyEvent(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP)
+        service.interceptKeyBeforeDispatching(null, backlightUpEvent, /* policyFlags = */0)
+        verify(kbdController).incrementKeyboardBacklight(anyInt())
+    }
+
+    @Test
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+    fun handleKeyGestures_toggleCapsLock() {
+        service.systemRunning()
+
+        val metaDownEvent = createKeyEvent(KeyEvent.KEYCODE_META_LEFT)
+        service.interceptKeyBeforeDispatching(null, metaDownEvent, /* policyFlags = */0)
+        val altDownEvent =
+            createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_DOWN)
+        service.interceptKeyBeforeDispatching(null, altDownEvent, /* policyFlags = */0)
+        val altUpEvent =
+            createKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_META_ON, KeyEvent.ACTION_UP)
+        service.interceptKeyBeforeDispatching(null, altUpEvent, /* policyFlags = */0)
+
+        verify(native).toggleCapsLock(anyInt())
+    }
+
     fun overrideSendActionKeyEventsToFocusedWindow(
         hasPermission: Boolean,
         hasPrivateFlag: Boolean
@@ -476,6 +536,25 @@
         )
         whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
     }
+
+    private fun createKeyEvent(
+        keycode: Int,
+        modifierState: Int = 0,
+        action: Int = KeyEvent.ACTION_DOWN
+    ): KeyEvent {
+        return KeyEvent(
+            /* downTime = */0,
+            /* eventTime = */0,
+            action,
+            keycode,
+            /* repeat = */0,
+            modifierState,
+            KeyCharacterMap.VIRTUAL_KEYBOARD,
+            /* scancode = */0,
+            /* flags = */0,
+            InputDevice.SOURCE_KEYBOARD
+        )
+    }
 }
 
 private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)