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))