Shift major shortcut handling from PWM to IMS

Use KeyGestureHandler APIs to shift shortcut and system event
trigerring to IMS and the handling to various system components

Test: atest InputTests
Test: atest WmTests
Bug: 358569822
Flag: com.android.hardware.input.use_key_gesture_event_handler
Change-Id: I03f4af47b180ab05fb92b78955ce2f2096707d7b
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index c7ebc63..0cabc4c 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -65,31 +65,34 @@
     public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 23;
     public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 24;
     public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 25;
-    public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION = 26;
-    public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS = 27;
-    public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 28;
-    public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 29;
-    public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 30;
-    public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 31;
-    public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 32;
-    public static final int KEY_GESTURE_TYPE_SLEEP = 33;
-    public static final int KEY_GESTURE_TYPE_WAKEUP = 34;
-    public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 35;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 36;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 37;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 38;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 39;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 40;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 41;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 42;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 43;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 44;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 45;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 46;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 47;
-    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 48;
-    public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 49;
-    public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 50;
+    public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 26;
+    public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 27;
+    public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 28;
+    public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 29;
+    public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 30;
+    public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 31;
+    public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 32;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 33;
+    public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 34;
+    public static final int KEY_GESTURE_TYPE_SLEEP = 35;
+    public static final int KEY_GESTURE_TYPE_WAKEUP = 36;
+    public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 37;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 38;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 39;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 40;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 41;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 42;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 43;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 44;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 45;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 46;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 47;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 48;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 49;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 50;
+    public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 51;
+    public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 52;
+    public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 53;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -130,8 +133,10 @@
             KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
             KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
             KEY_GESTURE_TYPE_SYSTEM_MUTE,
-            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
-            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
             KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
             KEY_GESTURE_TYPE_LOCK_SCREEN,
             KEY_GESTURE_TYPE_OPEN_NOTES,
@@ -155,6 +160,7 @@
             KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
             KEY_GESTURE_TYPE_DESKTOP_MODE,
             KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -331,6 +337,7 @@
             case KEY_GESTURE_TYPE_HOME:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
             case KEY_GESTURE_TYPE_RECENT_APPS:
+            case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
             case KEY_GESTURE_TYPE_BACK:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
@@ -378,9 +385,11 @@
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
             case KEY_GESTURE_TYPE_SYSTEM_MUTE:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
-            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION:
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
-            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS:
+            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_TRIGGER_BUG_REPORT:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
@@ -487,10 +496,14 @@
                 return "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK";
             case KEY_GESTURE_TYPE_SYSTEM_MUTE:
                 return "KEY_GESTURE_TYPE_SYSTEM_MUTE";
-            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION:
-                return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION";
-            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS:
-                return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS";
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
+                return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT";
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
+                return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT";
+            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
+                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_TRIGGER_BUG_REPORT:
                 return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
             case KEY_GESTURE_TYPE_LOCK_SCREEN:
@@ -537,6 +550,8 @@
                 return "KEY_GESTURE_TYPE_DESKTOP_MODE";
             case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
                 return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION";
+            case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
+                return "KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 4478592..1a309c6 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -112,6 +112,13 @@
 }
 
 flag {
+    namespace: "input_native"
+    name: "use_key_gesture_event_handler"
+    description: "Use KeyGestureEvent handler APIs to control system shortcuts and key gestures"
+    bug: "358569822"
+}
+
+flag {
   name: "keyboard_repeat_keys"
   namespace: "input_native"
   description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats"
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ca8ae6e..72f840d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.hardware.input.Flags.touchpadVisualizer;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 
 import android.Manifest;
 import android.annotation.EnforcePermission;
@@ -2116,6 +2117,7 @@
         mKeyboardBacklightController.dump(ipw);
         mKeyboardLedController.dump(ipw);
         mKeyboardGlyphManager.dump(ipw);
+        mKeyGestureController.dump(ipw);
     }
 
     private void dumpAssociations(IndentingPrintWriter pw) {
@@ -2457,13 +2459,18 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
-        // TODO(b/358569822): Move shortcut trigger logic from PWM to KeyGestureController
-        long value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags);
-        if (value != 0) { // If key is consumed (i.e. non-zero value)
-            return value;
+    @VisibleForTesting
+    long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
+        final long keyNotConsumed = 0;
+        long value = keyNotConsumed;
+        if (useKeyGestureEventHandler()) {
+            value = mKeyGestureController.interceptKeyBeforeDispatching(focus, event, policyFlags);
         }
-        return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
+        if (value == keyNotConsumed) {
+            value = mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event,
+                    policyFlags);
+        }
+        return value;
     }
 
     // Native callback.
@@ -2748,12 +2755,14 @@
     private void enforceManageKeyGesturePermission() {
         // TODO(b/361567988): Use @EnforcePermission to enforce permission once flag guarding the
         //  permission is rolled out
-        String systemUIPackage = mContext.getString(R.string.config_systemUi);
-        int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
-                .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
-                        UserHandle.USER_SYSTEM));
-        if (UserHandle.getCallingAppId() == systemUIAppId) {
-            return;
+        if (mSystemReady) {
+            String systemUIPackage = mContext.getString(R.string.config_systemUi);
+            int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal
+                    .getPackageUid(systemUIPackage, PackageManager.MATCH_SYSTEM_ONLY,
+                            UserHandle.USER_SYSTEM));
+            if (UserHandle.getCallingAppId() == systemUIAppId) {
+                return;
+            }
         }
         if (mContext.checkCallingOrSelfPermission(
                 Manifest.permission.MANAGE_KEY_GESTURES) == PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index bfdb1c1..760566b 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -16,10 +16,15 @@
 
 package com.android.server.input;
 
+import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.res.Resources;
+
 import android.hardware.input.AidlKeyGestureEvent;
 import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IKeyGestureHandler;
@@ -31,6 +36,8 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -38,10 +45,14 @@
 import android.view.InputDevice;
 import android.view.KeyEvent;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayDeque;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
 
 /**
@@ -56,12 +67,36 @@
     // 'adb shell setprop log.tag.KeyGestureController DEBUG' (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    // Maximum key gesture events that are tracked and will be available in input dump.
+    private static final int MAX_TRACKED_EVENTS = 10;
+
     private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
 
+    // must match: config_settingsKeyBehavior in config.xml
+    private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0;
+    private static final int SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1;
+    private static final int SETTINGS_KEY_BEHAVIOR_NOTHING = 2;
+    private static final int LAST_SETTINGS_KEY_BEHAVIOR = SETTINGS_KEY_BEHAVIOR_NOTHING;
+
+    // Must match: config_searchKeyBehavior in config.xml
+    private static final int SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0;
+    private static final int SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1;
+    private static final int LAST_SEARCH_KEY_BEHAVIOR = SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY;
+
     private final Context mContext;
     private final Handler mHandler;
     private final int mSystemPid;
 
+    // Pending actions
+    private boolean mPendingMetaAction;
+    private boolean mPendingCapsLockToggle;
+    private boolean mPendingHideRecentSwitcher;
+
+    // Key behaviors
+    private boolean mEnableBugReportKeyboardShortcut;
+    private int mSearchKeyBehavior;
+    private int mSettingsKeyBehavior;
+
     // List of currently registered key gesture event listeners keyed by process pid
     @GuardedBy("mKeyGestureEventListenerRecords")
     private final SparseArray<KeyGestureEventListenerRecord>
@@ -73,6 +108,11 @@
     @GuardedBy("mKeyGestureHandlerRecords")
     private final TreeMap<Integer, KeyGestureHandlerRecord> mKeyGestureHandlerRecords;
 
+    private final ArrayDeque<KeyGestureEvent> mLastHandledEvents = new ArrayDeque<>();
+
+    /** Currently fully consumed key codes per device */
+    private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
+
     KeyGestureController(Context context, Looper looper) {
         mContext = context;
         mHandler = new Handler(looper, this::handleMessage);
@@ -89,12 +129,449 @@
                 return Integer.compare(p1, p2);
             }
         });
+        initBehaviors();
     }
 
-    public int interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
+    private void initBehaviors() {
+        mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable"));
+
+        Resources res = mContext.getResources();
+        mSearchKeyBehavior = res.getInteger(R.integer.config_searchKeyBehavior);
+        if (mSearchKeyBehavior < SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH
+                || mSearchKeyBehavior > LAST_SEARCH_KEY_BEHAVIOR) {
+            mSearchKeyBehavior = SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH;
+        }
+        mSettingsKeyBehavior = res.getInteger(R.integer.config_settingsKeyBehavior);
+        if (mSettingsKeyBehavior < SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY
+                || mSettingsKeyBehavior > LAST_SETTINGS_KEY_BEHAVIOR) {
+            mSettingsKeyBehavior = SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY;
+        }
+    }
+
+    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
+            int policyFlags) {
         // TODO(b/358569822): Handle shortcuts trigger logic here and pass it to appropriate
         //  KeyGestureHandler (PWM is one of the handlers)
-        return 0;
+        final int keyCode = event.getKeyCode();
+        final int deviceId = event.getDeviceId();
+        final long keyConsumed = -1;
+        final long keyNotConsumed = 0;
+
+        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+        if (consumedKeys == null) {
+            consumedKeys = new HashSet<>();
+            mConsumedKeysForDevice.put(deviceId, consumedKeys);
+        }
+
+        if (interceptSystemKeysAndShortcuts(focusedToken, event)
+                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+            consumedKeys.add(keyCode);
+            return keyConsumed;
+        }
+
+        boolean needToConsumeKey = consumedKeys.contains(keyCode);
+        if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+            consumedKeys.remove(keyCode);
+            if (consumedKeys.isEmpty()) {
+                mConsumedKeysForDevice.remove(deviceId);
+            }
+        }
+
+        return needToConsumeKey ? keyConsumed : keyNotConsumed;
+    }
+
+    @SuppressLint("MissingPermission")
+    private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        final int repeatCount = event.getRepeatCount();
+        final int metaState = event.getMetaState();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        final boolean canceled = event.isCanceled();
+        final int displayId = event.getDisplayId();
+        final int deviceId = event.getDeviceId();
+        final boolean firstDown = down && repeatCount == 0;
+
+        // Cancel any pending meta actions if we see any other keys being pressed between the
+        // down of the meta key and its corresponding up.
+        if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
+            mPendingMetaAction = false;
+        }
+        // Any key that is not Alt or Meta cancels Caps Lock combo tracking.
+        if (mPendingCapsLockToggle && !KeyEvent.isMetaKey(keyCode) && !KeyEvent.isAltKey(keyCode)) {
+            mPendingCapsLockToggle = false;
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_A:
+                if (firstDown && event.isMetaPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_RECENT_APPS:
+                if (firstDown) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_APP_SWITCH:
+                if (firstDown) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+                            KeyGestureEvent.ACTION_GESTURE_START, displayId,
+                            focusedToken, /* flags = */0);
+                } else if (!down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_H:
+            case KeyEvent.KEYCODE_ENTER:
+                if (firstDown && event.isMetaPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_I:
+                if (firstDown && event.isMetaPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_L:
+                if (firstDown && event.isMetaPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_N:
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        // TODO(b/358569822): Move open notes handling in System UI instead of PWM
+                    } else {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_S:
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode},
+                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_DEL:
+                if (newBugreportKeyboardShortcut()) {
+                    if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
+                            && event.isCtrlPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                // fall through
+            case KeyEvent.KEYCODE_ESCAPE:
+                if (firstDown && event.isMetaPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode},
+                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode},
+                            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    } else if (event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    } else {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    } else if (event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
+                                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,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                break;
+            case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+            case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+                if (down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP
+                                    ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP
+                                    : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
+                if (down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
+                if (down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
+                // TODO: Add logic
+                if (!down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_ALL_APPS:
+                if (firstDown) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_NOTIFICATION:
+                if (!down) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_SEARCH:
+                if (firstDown && mSearchKeyBehavior == SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) {
+                    return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+
+                }
+                break;
+            case KeyEvent.KEYCODE_SETTINGS:
+                if (firstDown) {
+                    if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
+                        handleKeyGesture(deviceId,
+                                new int[]{keyCode}, /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
+                        handleKeyGesture(deviceId,
+                                new int[]{keyCode}, /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                return true;
+            case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
+                if (firstDown) {
+                    handleKeyGesture(deviceId, new int[]{keyCode},
+                            event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_CAPS_LOCK:
+                // Just logging/notifying purposes
+                // Caps lock is already handled in inputflinger native
+                if (!down) {
+                    AidlKeyGestureEvent eventToNotify = createKeyGestureEvent(deviceId,
+                            new int[]{keyCode}, metaState,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0);
+                    Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT,
+                            eventToNotify);
+                    mHandler.sendMessage(msg);
+                }
+                break;
+            case KeyEvent.KEYCODE_SCREENSHOT:
+                if (firstDown) {
+                    handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                            focusedToken, /* flags = */0);
+                }
+                return true;
+            case KeyEvent.KEYCODE_META_LEFT:
+            case KeyEvent.KEYCODE_META_RIGHT:
+                if (down) {
+                    if (event.isAltPressed()) {
+                        mPendingCapsLockToggle = true;
+                        mPendingMetaAction = false;
+                    } else {
+                        mPendingCapsLockToggle = false;
+                        mPendingMetaAction = true;
+                    }
+                } else {
+                    // Toggle Caps Lock on META-ALT.
+                    if (mPendingCapsLockToggle) {
+                        mPendingCapsLockToggle = false;
+                        handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT,
+                                        KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+
+                    } else if (mPendingMetaAction) {
+                        mPendingMetaAction = false;
+                        if (!canceled) {
+                            handleKeyGesture(deviceId, new int[]{keyCode},
+                                    /* modifierState = */0,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                                    KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                    focusedToken, /* flags = */0);
+                        }
+                    }
+                }
+                return true;
+            case KeyEvent.KEYCODE_TAB:
+                if (firstDown) {
+                    if (event.isMetaPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    } else if (!mPendingHideRecentSwitcher) {
+                        final int shiftlessModifiers =
+                                event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
+                        if (KeyEvent.metaStateHasModifiers(
+                                shiftlessModifiers, KeyEvent.META_ALT_ON)) {
+                            mPendingHideRecentSwitcher = true;
+                            return handleKeyGesture(deviceId, new int[]{keyCode},
+                                    KeyEvent.META_ALT_ON,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+                                    KeyGestureEvent.ACTION_GESTURE_START, displayId,
+                                    focusedToken, /* flags = */0);
+                        }
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_ALT_LEFT:
+            case KeyEvent.KEYCODE_ALT_RIGHT:
+                if (down) {
+                    if (event.isMetaPressed()) {
+                        mPendingCapsLockToggle = true;
+                        mPendingMetaAction = false;
+                    } else {
+                        mPendingCapsLockToggle = false;
+                    }
+                } else {
+                    if (mPendingHideRecentSwitcher) {
+                        mPendingHideRecentSwitcher = false;
+                        return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+
+                    // Toggle Caps Lock on META-ALT.
+                    if (mPendingCapsLockToggle) {
+                        mPendingCapsLockToggle = false;
+                        return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT,
+                                        KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+                                focusedToken, /* flags = */0);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_ASSIST:
+                Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
+                return true;
+            case KeyEvent.KEYCODE_VOICE_ASSIST:
+                Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
+                        + " interceptKeyBeforeQueueing");
+                return true;
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
+            case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
+                Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
+                        + " interceptKeyBeforeQueueing");
+                return true;
+        }
+        return false;
     }
 
     @VisibleForTesting
@@ -147,6 +624,10 @@
                     KeyGestureEvent.keyGestureTypeToLogEvent(event.gestureType));
         }
         notifyAllListeners(event);
+        while (mLastHandledEvents.size() >= MAX_TRACKED_EVENTS) {
+            mLastHandledEvents.removeFirst();
+        }
+        mLastHandledEvents.addLast(new KeyGestureEvent(event));
     }
 
     @MainThread
@@ -347,4 +828,45 @@
         event.flags = flags;
         return event;
     }
+
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("KeyGestureController:");
+        ipw.increaseIndent();
+        ipw.println("mSystemPid = " + mSystemPid);
+        ipw.println("mPendingMetaAction = " + mPendingMetaAction);
+        ipw.println("mPendingCapsLockToggle = " + mPendingCapsLockToggle);
+        ipw.println("mPendingHideRecentSwitcher = " + mPendingHideRecentSwitcher);
+        ipw.println("mSearchKeyBehavior = " + mSearchKeyBehavior);
+        ipw.println("mSettingsKeyBehavior = " + mSettingsKeyBehavior);
+        ipw.print("mKeyGestureEventListenerRecords = {");
+        synchronized (mKeyGestureEventListenerRecords) {
+            int size = mKeyGestureEventListenerRecords.size();
+            for (int i = 0; i < size; i++) {
+                ipw.print(mKeyGestureEventListenerRecords.keyAt(i));
+                if (i < size - 1) {
+                    ipw.print(", ");
+                }
+            }
+        }
+        ipw.println("}");
+        ipw.print("mKeyGestureHandlerRecords = {");
+        synchronized (mKeyGestureHandlerRecords) {
+            int i = mKeyGestureHandlerRecords.size() - 1;
+            for (int processId : mKeyGestureHandlerRecords.keySet()) {
+                ipw.print(processId);
+                if (i > 0) {
+                    ipw.print(", ");
+                }
+                i--;
+            }
+        }
+        ipw.println("}");
+        ipw.decreaseIndent();
+        ipw.println("Last handled KeyGestureEvents: ");
+        ipw.increaseIndent();
+        for (KeyGestureEvent ev : mLastHandledEvents) {
+            ipw.println(ev);
+        }
+        ipw.decreaseIndent();
+    }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ed9dcfa..98091b1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -83,6 +83,7 @@
 
 import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
 import static com.android.hardware.input.Flags.modifierShortcutDump;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
@@ -809,7 +810,7 @@
                     event.recycle();
                     break;
                 case MSG_HANDLE_ALL_APPS:
-                    launchAllAppsAction((KeyEvent) msg.obj);
+                    launchAllAppsAction();
                     break;
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
@@ -820,7 +821,7 @@
                 case MSG_SWITCH_KEYBOARD_LAYOUT:
                     SwitchKeyboardLayoutMessageObject object =
                             (SwitchKeyboardLayoutMessageObject) msg.obj;
-                    handleSwitchKeyboardLayout(object.keyEvent, object.direction,
+                    handleSwitchKeyboardLayout(object.displayId, object.direction,
                             object.focusedToken);
                     break;
                 case MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE:
@@ -937,7 +938,7 @@
         }
     }
 
-    private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken,
+    private record SwitchKeyboardLayoutMessageObject(int displayId, IBinder focusedToken,
                                                      int direction) {
     }
 
@@ -1813,9 +1814,7 @@
                 Settings.Secure.TV_USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
     }
 
-    private void handleShortPressOnHome(KeyEvent event) {
-        notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
-
+    private void handleShortPressOnHome(int displayId) {
         // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
         final HdmiControl hdmiControl = getHdmiControl();
         if (hdmiControl != null) {
@@ -1831,7 +1830,7 @@
         }
 
         // Go home!
-        launchHomeFromHotKey(event.getDisplayId());
+        launchHomeFromHotKey(displayId);
     }
 
     /**
@@ -1878,11 +1877,9 @@
         }
     }
 
-    private void launchAllAppsAction(KeyEvent event) {
+    private void launchAllAppsAction() {
         if (mHasFeatureLeanback || mHasFeatureWatch) {
             // TV and watch support the all apps intent
-            notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
             Intent intent = new Intent(Intent.ACTION_ALL_APPS);
             if (mHasFeatureLeanback) {
                 Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
@@ -1896,8 +1893,6 @@
             }
             startActivityAsUser(intent, UserHandle.CURRENT);
         } else {
-            notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
             AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal();
             if (accessibilityManager != null) {
                 accessibilityManager.performSystemAction(
@@ -1949,7 +1944,9 @@
             @Override
             public void run() {
                 if (mPendingHomeKeyEvent != null) {
-                    handleShortPressOnHome(mPendingHomeKeyEvent);
+                    notifyKeyGestureCompleted(mPendingHomeKeyEvent,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
+                    handleShortPressOnHome(mPendingHomeKeyEvent.getDisplayId());
                     mPendingHomeKeyEvent = null;
                 }
             }
@@ -2002,7 +1999,10 @@
                 }
 
                 // Post to main thread to avoid blocking input pipeline.
-                mHandler.post(() -> handleShortPressOnHome(event));
+                mHandler.post(() -> {
+                    notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
+                    handleShortPressOnHome(event.getDisplayId());
+                });
                 return true;
             }
 
@@ -2080,7 +2080,8 @@
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, "Home - Long Press");
             switch (mLongPressOnHomeBehavior) {
                 case LONG_PRESS_HOME_ALL_APPS:
-                    launchAllAppsAction(event);
+                    notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
+                    launchAllAppsAction();
                     break;
                 case LONG_PRESS_HOME_ASSIST:
                     notifyKeyGestureCompleted(event,
@@ -2430,6 +2431,7 @@
         mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
         initKeyCombinationRules();
         initSingleKeyGestureRules(injector.getLooper());
+        initKeyGestures();
         mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
         mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
     }
@@ -3348,18 +3350,18 @@
             }
         }
 
-        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
-        if (consumedKeys == null) {
-            consumedKeys = new HashSet<>();
-            mConsumedKeysForDevice.put(deviceId, consumedKeys);
-        }
-
         // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
         if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
                 && shouldInterceptShortcuts(focusedToken)) {
             return keyNotConsumed;
         }
 
+        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+        if (consumedKeys == null) {
+            consumedKeys = new HashSet<>();
+            mConsumedKeysForDevice.put(deviceId, consumedKeys);
+        }
+
         if (interceptSystemKeysAndShortcuts(focusedToken, event)
                 && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
             consumedKeys.add(keyCode);
@@ -3388,6 +3390,10 @@
     // conflicting events to application, make sure to consume the event on
     // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
     // to maintain event parity and to not have incomplete key gestures.
+    //
+    // NOTE: Please try not to add new Shortcut combinations here and instead use KeyGestureEvents.
+    // Add shortcut trigger logic in {@code KeyGestureController} and add handling logic in
+    // {@link handleKeyGesture()}
     @SuppressLint("MissingPermission")
     private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
         final boolean keyguardOn = keyguardOn();
@@ -3516,6 +3522,7 @@
                     injectBackGesture(event.getDownTime());
                     return true;
                 }
+                break;
             case KeyEvent.KEYCODE_DPAD_UP:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3544,11 +3551,11 @@
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
                         notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT);
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(true /* leftOrTop */);
                         notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT);
                     } else {
                         notifyKeyGestureCompleted(event,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
@@ -3563,12 +3570,12 @@
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 false /* leftOrTop */);
                         notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT);
                         return true;
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(false /* leftOrTop */);
                         notifyKeyGestureCompleted(event,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT);
                         return true;
                     }
                 }
@@ -3593,30 +3600,7 @@
                 if (down) {
                     int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1;
 
-                    int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
-
-                    float minLinearBrightness = mPowerManager.getBrightnessConstraint(
-                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
-                    float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
-                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
-                    float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
-
-                    float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
-                    float adjustedGammaBrightness =
-                            gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
-                    adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f,
-                            1f);
-                    float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
-                            adjustedGammaBrightness);
-                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness,
-                            minLinearBrightness, maxLinearBrightness);
-                    mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
-
-                    Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
-                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION
-                            | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-                    intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
-                    startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                    changeDisplayBrightnessValue(displayId, direction);
 
                     int gestureType = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
                             ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN
@@ -3689,10 +3673,11 @@
             case KeyEvent.KEYCODE_ALL_APPS:
                 if (firstDown) {
                     mHandler.removeMessages(MSG_HANDLE_ALL_APPS);
-
                     Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS, new KeyEvent(event));
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+
+                    notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
@@ -3720,7 +3705,7 @@
             case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                    sendSwitchKeyboardLayout(event, focusedToken, direction);
+                    sendSwitchKeyboardLayout(displayId, focusedToken, direction);
                     notifyKeyGestureCompleted(event,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH);
                     return true;
@@ -3745,7 +3730,9 @@
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
-                            launchAllAppsAction(event);
+                            launchAllAppsAction();
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3829,6 +3816,254 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    @SuppressLint("MissingPermission")
+    private void initKeyGestures() {
+        if (!useKeyGestureEventHandler()) {
+            return;
+        }
+        mInputManager.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
+            @Override
+            public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
+                    @Nullable IBinder focusedToken) {
+                return PhoneWindowManager.this.handleKeyGestureEvent(event, focusedToken);
+            }
+
+            @Override
+            public boolean isKeyGestureSupported(int gestureType) {
+                switch (gestureType) {
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_BACK:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
+                    case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS:
+                    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;
+                }
+            }
+        });
+    }
+
+    @VisibleForTesting
+    boolean handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) {
+        boolean start = event.getAction() == KeyGestureEvent.ACTION_GESTURE_START;
+        boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                && !event.isCancelled();
+        int deviceId = event.getDeviceId();
+        int gestureType = event.getKeyGestureType();
+        int displayId = event.getDisplayId();
+        int modifierState = event.getModifierState();
+        boolean keyguardOn = keyguardOn();
+        switch (gestureType) {
+            case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
+                if (complete) {
+                    showRecentApps(false);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
+                if (!keyguardOn) {
+                    if (start) {
+                        preloadRecentApps();
+                    } else if (complete) {
+                        toggleRecentApps();
+                    }
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+                if (complete) {
+                    launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
+                            deviceId, SystemClock.uptimeMillis(),
+                            AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
+                if (complete) {
+                    // Post to main thread to avoid blocking input pipeline.
+                    mHandler.post(() -> handleShortPressOnHome(displayId));
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
+                if (complete) {
+                    showSystemSettings();
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
+                if (complete) {
+                    lockNow(null /* options */);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
+                if (complete) {
+                    toggleNotificationPanel();
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
+                if (complete) {
+                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
+                if (complete && mEnableBugReportKeyboardShortcut) {
+                    try {
+                        mActivityManagerService.requestInteractiveBugReport();
+                    } catch (RemoteException e) {
+                        Slog.d(TAG, "Error taking bugreport", e);
+                    }
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_BACK:
+                if (complete) {
+                    injectBackGesture(SystemClock.uptimeMillis());
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
+                if (complete) {
+                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+                    if (statusbar != null) {
+                        statusbar.moveFocusedTaskToFullscreen(
+                                getTargetDisplayIdForKeyGestureEvent(event));
+                    }
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE:
+                if (complete) {
+                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+                    if (statusbar != null) {
+                        statusbar.moveFocusedTaskToDesktop(
+                                getTargetDisplayIdForKeyGestureEvent(event));
+                    }
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
+                if (complete) {
+                    moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event),
+                            true /* leftOrTop */);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT:
+                if (complete) {
+                    setSplitscreenFocus(true /* leftOrTop */);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
+                if (complete) {
+                    moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event),
+                            false /* leftOrTop */);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT:
+                if (complete) {
+                    setSplitscreenFocus(false /* leftOrTop */);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
+                if (complete) {
+                    toggleKeyboardShortcutsMenu(deviceId);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP:
+            case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
+                if (complete) {
+                    int direction =
+                            gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP ? 1 : -1;
+                    changeDisplayBrightnessValue(displayId, direction);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
+                if (start) {
+                    showRecentApps(true);
+                } else {
+                    hideRecentApps(true, false);
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS:
+            case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
+                if (complete) {
+                    launchAllAppsAction();
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
+                if (complete) {
+                    launchTargetSearchActivity();
+                }
+                return true;
+            case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
+                if (complete) {
+                    int direction = (modifierState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+                    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;
+    }
+
+    private void changeDisplayBrightnessValue(int displayId, int direction) {
+        int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
+
+        float minLinearBrightness = mPowerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
+
+        float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
+        float adjustedGammaBrightness = gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
+        adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f, 1f);
+        float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
+                adjustedGammaBrightness);
+        adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness,
+                minLinearBrightness, maxLinearBrightness);
+        mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
+
+        Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION
+                | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+        intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
+        startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+    }
+
     private boolean shouldInterceptShortcuts(IBinder focusedToken) {
         KeyInterceptionInfo info =
                 mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
@@ -4081,7 +4316,7 @@
                     if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
                             KeyEvent.META_CTRL_ON)) {
                         int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                        sendSwitchKeyboardLayout(event, focusedToken, direction);
+                        sendSwitchKeyboardLayout(event.getDisplayId(), focusedToken, direction);
                         return true;
                     }
                 }
@@ -4139,19 +4374,18 @@
         }
     }
 
-    private void sendSwitchKeyboardLayout(@NonNull KeyEvent event,
-            @Nullable IBinder focusedToken, int direction) {
+    private void sendSwitchKeyboardLayout(int displayId, @Nullable IBinder focusedToken,
+            int direction) {
         SwitchKeyboardLayoutMessageObject object =
-                new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction);
+                new SwitchKeyboardLayoutMessageObject(displayId, focusedToken, direction);
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget();
     }
 
-    private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
-            IBinder focusedToken) {
+    private void handleSwitchKeyboardLayout(int displayId, int direction, IBinder focusedToken) {
         IBinder targetWindowToken =
                 mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
-        InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
-                event.getDisplayId(), targetWindowToken);
+        InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, displayId,
+                targetWindowToken);
     }
 
     private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
@@ -7000,16 +7234,22 @@
     }
 
     private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
-        int displayId = event.getDisplayId();
-
-        if (displayId == INVALID_DISPLAY) {
-            displayId = mTopFocusedDisplayId;
+        if (event.getDisplayId() != INVALID_DISPLAY) {
+            return event.getDisplayId();
         }
-
-        if (displayId == INVALID_DISPLAY) {
-            return DEFAULT_DISPLAY;
-        } else {
-            return displayId;
+        if (mTopFocusedDisplayId != INVALID_DISPLAY) {
+            return mTopFocusedDisplayId;
         }
+        return DEFAULT_DISPLAY;
+    }
+
+    private int getTargetDisplayIdForKeyGestureEvent(KeyGestureEvent event) {
+        if (event.getDisplayId() != INVALID_DISPLAY) {
+            return event.getDisplayId();
+        }
+        if (mTopFocusedDisplayId != INVALID_DISPLAY) {
+            return mTopFocusedDisplayId;
+        }
+        return DEFAULT_DISPLAY;
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 8f3adba..3d978e4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.policy;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
@@ -23,21 +25,21 @@
 import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
 
 import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.KeyEvent;
 
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.annotations.Keep;
 
+import junit.framework.Assert;
+
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -46,10 +48,6 @@
 @RunWith(JUnitParamsRunner.class)
 public class KeyGestureEventTests extends ShortcutKeyTestBase {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
     private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
     private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
@@ -149,9 +147,9 @@
                 {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
                         KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
                         KeyEvent.KEYCODE_VOLUME_MUTE, 0},
-                {"ALL_APPS key -> Open App Drawer in Accessibility mode",
+                {"ALL_APPS key -> Open App Drawer",
                         new int[]{KeyEvent.KEYCODE_ALL_APPS},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                         KeyEvent.KEYCODE_ALL_APPS, 0},
                 {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
                         KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
@@ -160,8 +158,8 @@
                         new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
                         KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                         KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
-                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, META_KEY,
+                {"META key -> Open App Drawer", new int[]{META_KEY},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, META_KEY,
                         META_ON},
                 {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
                         KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY,
@@ -182,12 +180,12 @@
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
                         KeyEvent.KEYCODE_DPAD_LEFT,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
-                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
                         KeyEvent.KEYCODE_DPAD_RIGHT,
                         META_ON | CTRL_ON},
                 {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
@@ -320,18 +318,18 @@
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
                         KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
                         META_ON},
-                {"Long press HOME key -> Open App Drawer in Accessibility mode",
+                {"Long press HOME key -> Open App Drawer",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                         KeyEvent.KEYCODE_HOME, 0},
-                {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
+                {"Long press META + ENTER -> Open App Drawer",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                         KeyEvent.KEYCODE_ENTER, META_ON},
-                {"Long press META + H -> Open App Drawer in Accessibility mode",
+                {"Long press META + H -> Open App Drawer",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H},
                         LONG_PRESS_HOME_ALL_APPS,
-                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                         KeyEvent.KEYCODE_H, META_ON}};
     }
 
@@ -428,7 +426,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+    @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
     public void testBugreportShortcutPress() {
         testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
                 new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
@@ -444,4 +442,161 @@
                 new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
+
+    @Test
+    public void testKeyGestureRecentApps() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS));
+        mPhoneWindowManager.assertShowRecentApps();
+    }
+
+    @Test
+    public void testKeyGestureAppSwitch() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH));
+        mPhoneWindowManager.assertToggleRecentApps();
+    }
+
+    @Test
+    public void testKeyGestureLaunchAssistant() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT));
+        mPhoneWindowManager.assertSearchManagerLaunchAssist();
+    }
+
+    @Test
+    public void testKeyGestureGoHome() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME));
+        mPhoneWindowManager.assertGoToHomescreen();
+    }
+
+    @Test
+    public void testKeyGestureLaunchSystemSettings() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS));
+        mPhoneWindowManager.assertLaunchSystemSettings();
+    }
+
+    @Test
+    public void testKeyGestureLock() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN));
+        mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
+    }
+
+    @Test
+    public void testKeyGestureToggleNotificationPanel() throws RemoteException {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL));
+        mPhoneWindowManager.assertTogglePanel();
+    }
+
+    @Test
+    public void testKeyGestureScreenshot() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT));
+        mPhoneWindowManager.assertTakeScreenshotCalled();
+    }
+
+    @Test
+    public void testKeyGestureTriggerBugReport() throws RemoteException {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT));
+        mPhoneWindowManager.assertTakeBugreport(true);
+    }
+
+    @Test
+    public void testKeyGestureBack() {
+        Assert.assertTrue(sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK));
+        mPhoneWindowManager.assertBackEventInjected();
+    }
+
+    @Test
+    public void testKeyGestureMultiWindowNavigation() {
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION));
+        mPhoneWindowManager.assertMoveFocusedTaskToFullscreen();
+    }
+
+    @Test
+    public void testKeyGestureDesktopMode() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE));
+        mPhoneWindowManager.assertMoveFocusedTaskToDesktop();
+    }
+
+    @Test
+    public void testKeyGestureSplitscreenNavigation() {
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT));
+        mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(true);
+
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT));
+        mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(false);
+    }
+
+    @Test
+    public void testKeyGestureSplitscreenFocus() {
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT));
+        mPhoneWindowManager.assertSetSplitscreenFocus(true);
+
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT));
+        mPhoneWindowManager.assertSetSplitscreenFocus(false);
+    }
+
+    @Test
+    public void testKeyGestureShortcutHelper() {
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER));
+        mPhoneWindowManager.assertToggleShortcutsMenu();
+    }
+
+    @Test
+    public void testKeyGestureBrightnessChange() {
+        float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f};
+        float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f};
+
+        for (int i = 0; i < currentBrightness.length; i++) {
+            mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]);
+            Assert.assertTrue(
+                    sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN));
+            mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
+        }
+    }
+
+    @Test
+    public void testKeyGestureRecentAppSwitcher() {
+        Assert.assertTrue(sendKeyGestureEventStart(
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER));
+        mPhoneWindowManager.assertShowRecentApps();
+
+        Assert.assertTrue(sendKeyGestureEventComplete(
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER));
+        mPhoneWindowManager.assertHideRecentApps();
+    }
+
+    @Test
+    public void testKeyGestureLanguageSwitch() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH));
+        mPhoneWindowManager.assertSwitchKeyboardLayout(1, DEFAULT_DISPLAY);
+
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                        KeyEvent.META_SHIFT_ON));
+        mPhoneWindowManager.assertSwitchKeyboardLayout(-1, DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testKeyGestureLaunchSearch() {
+        Assert.assertTrue(
+                sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH));
+        mPhoneWindowManager.assertLaunchSearch();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 536dcfb..af3dc1d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -46,6 +46,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.hardware.input.InputManager;
 import android.os.PowerManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 
@@ -95,6 +96,9 @@
         mStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
         LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
         mPhoneWindowManager.mKeyguardDelegate = mock(KeyguardServiceDelegate.class);
+        final InputManager im = mock(InputManager.class);
+        doNothing().when(im).registerKeyGestureEventHandler(any());
+        doReturn(im).when(mContext).getSystemService(eq(Context.INPUT_SERVICE));
     }
 
     @After
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 37e4fd6..50b7db4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -56,6 +56,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.hardware.input.KeyGestureEvent;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.view.InputDevice;
@@ -228,6 +229,31 @@
         sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
     }
 
+    boolean sendKeyGestureEventStart(int gestureType) {
+        return mPhoneWindowManager.sendKeyGestureEvent(
+                new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
+                        KeyGestureEvent.ACTION_GESTURE_START).build());
+    }
+
+    boolean sendKeyGestureEventComplete(int gestureType) {
+        return mPhoneWindowManager.sendKeyGestureEvent(
+                new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
+                        KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+    }
+
+    boolean sendKeyGestureEventComplete(int gestureType, int modifierState) {
+        return mPhoneWindowManager.sendKeyGestureEvent(
+                new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType(
+                        gestureType).setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+    }
+
+    boolean sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) {
+        return mPhoneWindowManager.sendKeyGestureEvent(
+                new KeyGestureEvent.Builder().setKeycodes(new int[]{keycode}).setModifierState(
+                        modifierState).setKeyGestureType(gestureType).setAction(
+                        KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
+    }
+
     /**
      * Since we use SettingsProviderRule to mock the ContentResolver in these
      * tests, the settings observer registered by PhoneWindowManager will not
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 79c7ac1..0b55e2b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -71,6 +71,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -83,9 +84,11 @@
 import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.os.test.TestLooper;
+import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.view.Display;
+import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.autofill.AutofillManagerInternal;
@@ -118,6 +121,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.quality.Strictness;
 
+import java.util.List;
 import java.util.function.Supplier;
 
 class TestPhoneWindowManager {
@@ -298,6 +302,8 @@
         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
         doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
+        doNothing().when(mInputManager).registerKeyGestureEventHandler(any());
+        doNothing().when(mInputManager).unregisterKeyGestureEventHandler(any());
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(mSensorPrivacyManager).when(mContext).getSystemService(
                 eq(SensorPrivacyManager.class));
@@ -417,6 +423,10 @@
         mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
     }
 
+    boolean sendKeyGestureEvent(KeyGestureEvent event) {
+        return mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken);
+    }
+
     /**
      * Provide access to the SettingsObserver so that tests can manually trigger Settings changes.
      */
@@ -584,6 +594,16 @@
         doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
     }
 
+    void assertBackEventInjected() {
+        ArgumentCaptor<InputEvent> intentCaptor = ArgumentCaptor.forClass(InputEvent.class);
+        verify(mInputManager, times(2)).injectInputEvent(intentCaptor.capture(), anyInt());
+        List<InputEvent> inputEvents = intentCaptor.getAllValues();
+        Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(0)).getKeyCode());
+        Assert.assertEquals(KeyEvent.KEYCODE_BACK, ((KeyEvent) inputEvents.get(1)).getKeyCode());
+        // Reset verifier for next call.
+        Mockito.clearInvocations(mContext);
+    }
+
     void overrideSearchKeyBehavior(int behavior) {
         mPhoneWindowManager.mSearchKeyBehavior = behavior;
     }
@@ -685,6 +705,24 @@
         verify(mSearchManager).launchAssist(any());
     }
 
+    void assertLaunchSystemSettings() {
+        mTestLooper.dispatchAll();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(intentCaptor.capture(), any(), any());
+        Assert.assertEquals(Settings.ACTION_SETTINGS, intentCaptor.getValue().getAction());
+        // Reset verifier for next call.
+        Mockito.clearInvocations(mContext);
+    }
+
+    void assertLaunchSearch() {
+        mTestLooper.dispatchAll();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(intentCaptor.capture(), any(), any());
+        Assert.assertEquals(Intent.ACTION_WEB_SEARCH, intentCaptor.getValue().getAction());
+        // Reset verifier for next call.
+        Mockito.clearInvocations(mContext);
+    }
+
     void assertLaunchCategory(String category) {
         mTestLooper.dispatchAll();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -725,6 +763,36 @@
         verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
     }
 
+    void assertHideRecentApps() {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).hideRecentApps(anyBoolean(), anyBoolean());
+    }
+
+    void assertToggleRecentApps() {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).toggleRecentApps();
+    }
+
+    void assertMoveFocusedTaskToDesktop() {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).moveFocusedTaskToDesktop(anyInt());
+    }
+
+    void assertMoveFocusedTaskToFullscreen() {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).moveFocusedTaskToFullscreen(anyInt());
+    }
+
+    void assertMoveFocusedTaskToStageSplit(boolean leftOrTop) {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).moveFocusedTaskToStageSplit(anyInt(), eq(leftOrTop));
+    }
+
+    void assertSetSplitscreenFocus(boolean leftOrTop) {
+        mTestLooper.dispatchAll();
+        verify(mStatusBarManagerInternal).setSplitscreenFocus(eq(leftOrTop));
+    }
+
     void assertStatusBarStartAssist() {
         mTestLooper.dispatchAll();
         verify(mStatusBarManagerInternal).startAssist(any());
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index f3e040a..6742cbe 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -39,6 +39,7 @@
         "flag-junit",
         "frameworks-base-testutils",
         "hamcrest-library",
+        "junit-params",
         "kotlin-test",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index e126797..28550cd 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -25,19 +25,27 @@
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
 import android.hardware.input.KeyGestureEvent
+import android.hardware.input.KeyGestureEvent.KeyGestureType
 import android.os.IBinder
 import android.os.Process
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
+import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import androidx.test.core.app.ApplicationProvider
+import com.android.internal.annotations.Keep
 import com.android.internal.util.FrameworkStatsLog
 import com.android.modules.utils.testing.ExtendedMockitoRule
+import junitparams.JUnitParamsRunner
+import junitparams.Parameters
+import org.junit.Assert.assertArrayEquals
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
 
@@ -48,6 +56,7 @@
  * atest InputTests:KeyGestureControllerTests
  */
 @Presubmit
+@RunWith(JUnitParamsRunner::class)
 class KeyGestureControllerTests {
 
     companion object {
@@ -59,6 +68,16 @@
             .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
             .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
             .build()
+        val MODIFIER = mapOf(
+            KeyEvent.KEYCODE_CTRL_LEFT to (KeyEvent.META_CTRL_LEFT_ON or KeyEvent.META_CTRL_ON),
+            KeyEvent.KEYCODE_CTRL_RIGHT to (KeyEvent.META_CTRL_RIGHT_ON or KeyEvent.META_CTRL_ON),
+            KeyEvent.KEYCODE_ALT_LEFT to (KeyEvent.META_ALT_LEFT_ON or KeyEvent.META_ALT_ON),
+            KeyEvent.KEYCODE_ALT_RIGHT to (KeyEvent.META_ALT_RIGHT_ON or KeyEvent.META_ALT_ON),
+            KeyEvent.KEYCODE_SHIFT_LEFT to (KeyEvent.META_SHIFT_LEFT_ON or KeyEvent.META_SHIFT_ON),
+            KeyEvent.KEYCODE_SHIFT_RIGHT to (KeyEvent.META_SHIFT_RIGHT_ON or KeyEvent.META_SHIFT_ON),
+            KeyEvent.KEYCODE_META_LEFT to (KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON),
+            KeyEvent.KEYCODE_META_RIGHT to (KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON),
+        )
     }
 
     @JvmField
@@ -75,6 +94,7 @@
     private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
     private lateinit var testLooper: TestLooper
     private var events = mutableListOf<KeyGestureEvent>()
+    private var handleEvents = mutableListOf<KeyGestureEvent>()
 
     @Before
     fun setup() {
@@ -184,6 +204,440 @@
         )
     }
 
+    class TestData(
+        val name: String,
+        val keys: IntArray,
+        val expectedKeyGestureType: Int,
+        val expectedKeys: IntArray,
+        val expectedModifierState: Int,
+        val expectedActions: IntArray,
+    ) {
+        override fun toString(): String = name
+    }
+
+    @Keep
+    private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+        return arrayOf(
+            TestData(
+                "META + A -> Launch Assistant",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_A),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+                intArrayOf(KeyEvent.KEYCODE_A),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "RECENT_APPS -> Show Overview",
+                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "APP_SWITCH -> App Switch",
+                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+                KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+                0,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_START,
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+            TestData(
+                "META + H -> Go Home",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
+                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                intArrayOf(KeyEvent.KEYCODE_H),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ENTER -> Go Home",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ENTER),
+                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                intArrayOf(KeyEvent.KEYCODE_ENTER),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + I -> Launch System Settings",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_I),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+                intArrayOf(KeyEvent.KEYCODE_I),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + L -> Lock",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_L),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+                intArrayOf(KeyEvent.KEYCODE_L),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + N -> Toggle Notification",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_N),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+                intArrayOf(KeyEvent.KEYCODE_N),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + S -> Take Screenshot",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_S
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+                intArrayOf(KeyEvent.KEYCODE_S),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + DEL -> Back",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DEL),
+                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+                intArrayOf(KeyEvent.KEYCODE_DEL),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ESC -> Back",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ESCAPE),
+                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+                intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + DPAD_LEFT -> Back",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DPAD_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + DPAD_UP -> Multi Window Navigation",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_DPAD_UP
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_UP),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + DPAD_DOWN -> Desktop Mode",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_DPAD_DOWN
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_DOWN),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + DPAD_LEFT -> Splitscreen Navigation Left",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_DPAD_LEFT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + DPAD_RIGHT -> Splitscreen Navigation Right",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_DPAD_RIGHT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
+                KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT + DPAD_LEFT -> Change Splitscreen Focus Left",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_LEFT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + CTRL + DPAD_RIGHT -> Change Splitscreen Focus Right",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_DPAD_RIGHT
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
+                intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "BRIGHTNESS_UP -> Brightness Up",
+                intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
+                KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+                intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "BRIGHTNESS_DOWN -> Brightness Down",
+                intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_DOWN),
+                KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+                intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_DOWN),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "KEYBOARD_BACKLIGHT_UP -> Keyboard Backlight Up",
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP),
+                KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "KEYBOARD_BACKLIGHT_DOWN -> Keyboard Backlight Down",
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN),
+                KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "KEYBOARD_BACKLIGHT_TOGGLE -> Keyboard Backlight Toggle",
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE),
+                KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+                intArrayOf(KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALL_APPS -> Open App Drawer",
+                intArrayOf(KeyEvent.KEYCODE_ALL_APPS),
+                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
+                intArrayOf(KeyEvent.KEYCODE_ALL_APPS),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "NOTIFICATION -> Toggle Notification Panel",
+                intArrayOf(KeyEvent.KEYCODE_NOTIFICATION),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+                intArrayOf(KeyEvent.KEYCODE_NOTIFICATION),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "LANGUAGE_SWITCH -> Switch Language Forward",
+                intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "SHIFT + LANGUAGE_SWITCH -> Switch Language Backward",
+                intArrayOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+                intArrayOf(KeyEvent.KEYCODE_LANGUAGE_SWITCH),
+                KeyEvent.META_SHIFT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "SCREENSHOT -> Take Screenshot",
+                intArrayOf(KeyEvent.KEYCODE_SCREENSHOT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+                intArrayOf(KeyEvent.KEYCODE_SCREENSHOT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META -> Open Apps Drawer",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT -> Toggle Caps Lock",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + META -> Toggle Caps Lock",
+                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
+                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + TAB -> Open Overview",
+                intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+                intArrayOf(KeyEvent.KEYCODE_TAB),
+                KeyEvent.META_META_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "ALT + TAB -> Toggle Recent Apps Switcher",
+                intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+                intArrayOf(KeyEvent.KEYCODE_TAB),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_START,
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+        )
+    }
+
+    @Test
+    @Parameters(method = "keyGestureEventHandlerTestArguments")
+    fun testKeyGestures(test: TestData) {
+        val handler = KeyGestureHandler { event, _ ->
+            handleEvents.add(KeyGestureEvent(event))
+            true
+        }
+        keyGestureController.registerKeyGestureHandler(handler, 0)
+        handleEvents.clear()
+
+        sendKeys(test.keys, /* assertAllConsumed = */ false)
+
+        assertEquals(
+            "Test: $test doesn't produce correct number of key gesture events",
+            test.expectedActions.size,
+            handleEvents.size
+        )
+        for (i in handleEvents.indices) {
+            val event = handleEvents[i]
+            assertArrayEquals(
+                "Test: $test doesn't produce correct key gesture keycodes",
+                test.expectedKeys,
+                event.keycodes
+            )
+            assertEquals(
+                "Test: $test doesn't produce correct key gesture modifier state",
+                test.expectedModifierState,
+                event.modifierState
+            )
+            assertEquals(
+                "Test: $test doesn't produce correct key gesture type",
+                test.expectedKeyGestureType,
+                event.keyGestureType
+            )
+            assertEquals(
+                "Test: $test doesn't produce correct key gesture action",
+                test.expectedActions[i],
+                event.action
+            )
+        }
+
+        keyGestureController.unregisterKeyGestureHandler(handler, 0)
+    }
+
+    @Test
+    fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
+        val testKeys = intArrayOf(
+            KeyEvent.KEYCODE_RECENT_APPS,
+            KeyEvent.KEYCODE_APP_SWITCH,
+            KeyEvent.KEYCODE_BRIGHTNESS_UP,
+            KeyEvent.KEYCODE_BRIGHTNESS_DOWN,
+            KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN,
+            KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP,
+            KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE,
+            KeyEvent.KEYCODE_ALL_APPS,
+            KeyEvent.KEYCODE_NOTIFICATION,
+            KeyEvent.KEYCODE_SETTINGS,
+            KeyEvent.KEYCODE_LANGUAGE_SWITCH,
+            KeyEvent.KEYCODE_SCREENSHOT,
+            KeyEvent.KEYCODE_META_LEFT,
+            KeyEvent.KEYCODE_META_RIGHT,
+            KeyEvent.KEYCODE_ASSIST,
+            KeyEvent.KEYCODE_VOICE_ASSIST,
+            KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY,
+            KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
+            KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
+            KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
+        )
+
+        val handler = KeyGestureHandler { _, _ -> false }
+        keyGestureController.registerKeyGestureHandler(handler, 0)
+
+        for (key in testKeys) {
+            sendKeys(intArrayOf(key), /* assertAllConsumed = */ true)
+        }
+    }
+
+    private fun sendKeys(testKeys: IntArray, assertAllConsumed: Boolean) {
+        var metaState = 0
+        for (key in testKeys) {
+            val downEvent = KeyEvent(
+                /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_DOWN, key,
+                0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
+                0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+            )
+            val consumed =
+                keyGestureController.interceptKeyBeforeDispatching(null, downEvent, 0) == -1L
+            if (assertAllConsumed) {
+                assertTrue(
+                    "interceptKeyBeforeDispatching should consume all events $downEvent",
+                    consumed
+                )
+            }
+            metaState = metaState or MODIFIER.getOrDefault(key, 0)
+
+            downEvent.recycle()
+            testLooper.dispatchAll()
+        }
+
+        for (key in testKeys.reversed()) {
+            val upEvent = KeyEvent(
+                /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_UP, key,
+                0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
+                0 /*flags*/, InputDevice.SOURCE_KEYBOARD
+            )
+            val consumed =
+                keyGestureController.interceptKeyBeforeDispatching(null, upEvent, 0) == -1L
+            if (assertAllConsumed) {
+                assertTrue(
+                    "interceptKeyBeforeDispatching should consume all events $upEvent",
+                    consumed
+                )
+            }
+
+            upEvent.recycle()
+            testLooper.dispatchAll()
+        }
+    }
+
     inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() {
         override fun onKeyGestureEvent(event: AidlKeyGestureEvent) {
             events.add(KeyGestureEvent(event))