Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt +3 −3 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.systemui.keyboard.shortcut.data.source import android.content.res.Resources import android.hardware.input.InputManager import android.hardware.input.KeyGlyphMap import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.KEYCODE_BACK import android.view.KeyEvent.KEYCODE_DPAD_LEFT import android.view.KeyEvent.KEYCODE_ESCAPE Loading @@ -32,6 +31,7 @@ import android.view.KeyEvent.KEYCODE_Q import android.view.KeyEvent.KEYCODE_RECENT_APPS import android.view.KeyEvent.KEYCODE_S import android.view.KeyEvent.KEYCODE_SLASH import android.view.KeyEvent.KEYCODE_SPACE import android.view.KeyEvent.KEYCODE_TAB import android.view.KeyEvent.META_ALT_ON import android.view.KeyEvent.META_CTRL_ON Loading Loading @@ -230,9 +230,9 @@ constructor(@Main private val resources: Resources, private val inputManager: In command(META_META_ON, KEYCODE_I) }, // Access Assistant: // - Meta + A // - Meta + Space shortcutInfo(resources.getString(R.string.group_system_access_google_assistant)) { command(META_META_ON, KEYCODE_A) command(META_META_ON, KEYCODE_SPACE) }, ) } services/core/java/com/android/server/input/InputGestureManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -104,7 +104,7 @@ final class InputGestureManager { // Initialize all system shortcuts List<InputGestureData> systemShortcuts = new ArrayList<>(List.of( createKeyGesture( KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, /* allowCaptureByFocusedWindow = */true Loading services/core/java/com/android/server/input/KeyGestureController.java +4 −0 Original line number Diff line number Diff line Loading @@ -1138,6 +1138,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } } break; Loading @@ -1152,6 +1153,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; case KeyEvent.KEYCODE_SYSRQ: Loading @@ -1160,6 +1162,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; case KeyEvent.KEYCODE_ESCAPE: Loading @@ -1168,6 +1171,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; } Loading tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +49 −10 Original line number Diff line number Diff line Loading @@ -373,6 +373,18 @@ class KeyGestureControllerTests { testKeyGestureProduced(test, PASS_THROUGH_APP) } @Keep private fun multiKeyGestureArguments(): Array<KeyGestureData> { return KeyGestureTestData.MULTI_KEY_SYSTEM_GESTURES } @Test @Parameters(method = "multiKeyGestureArguments") fun testMultiKeyGestures(test: KeyGestureData) { setupKeyGestureController() testKeyGestureProduced(test, BLOCKING_APP) } @Test @EnableFlags(com.android.window.flags.Flags.FLAG_TOGGLE_FULLSCREEN_STATE_VIA_FULLSCREEN_KEY) fun testCustomKeyGesturesNotAllowedForSystemGestures() { Loading Loading @@ -604,7 +616,7 @@ class KeyGestureControllerTests { val listener = KeyGestureEventListener { event -> events.add(KeyGestureEvent(event)) } keyGestureController.registerKeyGestureEventListener(listener, 0) sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK), assertKeysFullyConsumed = false) testLooper.dispatchAll() assertEquals("Listener should get callbacks on key gesture event completed", 1, events.size) assertEquals( Loading Loading @@ -961,7 +973,11 @@ class KeyGestureControllerTests { fun testAccessibilityTvShortcutChordPressed() { setupKeyGestureController() sendKeys(intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 10000) sendKeys( intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 10000, assertKeysFullyConsumed = false, ) Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut() } Loading @@ -980,7 +996,11 @@ class KeyGestureControllerTests { fun testAccessibilityTvShortcutChordPressedForLessThanTimeout() { setupKeyGestureController() sendKeys(intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 0) sendKeys( intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 0, assertKeysFullyConsumed = false, ) Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() } Loading Loading @@ -1321,7 +1341,7 @@ class KeyGestureControllerTests { /* downTime= */ 0, /* eventTime= */ 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_MACRO_1, // Random valid keycode /* repeat= */ 0, KeyEvent.META_META_ON, ) Loading Loading @@ -1363,6 +1383,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = 2 * LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(2, events.size) Loading @@ -1383,6 +1404,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = 2 * LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(0, events.size) Loading @@ -1402,6 +1424,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS / 2, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(2, events.size) Loading Loading @@ -1512,6 +1535,7 @@ class KeyGestureControllerTests { appDelegate: AppDelegate = PASS_THROUGH_APP, timeDelayMs: Long = 0, displayId: Int = DEFAULT_DISPLAY, assertKeysFullyConsumed: Boolean = true, ) { var metaState = 0 val now = SystemClock.uptimeMillis() Loading @@ -1531,7 +1555,10 @@ class KeyGestureControllerTests { displayId, /* characters= */ "", ) interceptKey(downEvent, appDelegate) val consumed = interceptKey(downEvent, appDelegate) if (assertKeysFullyConsumed) { assertTrue("Key $downEvent should be consumed", consumed) } metaState = metaState or MODIFIER.getOrDefault(key, 0) downEvent.recycle() Loading Loading @@ -1559,7 +1586,10 @@ class KeyGestureControllerTests { displayId, /* characters= */ "", ) interceptKey(upEvent, appDelegate) val consumed = interceptKey(upEvent, appDelegate) if (assertKeysFullyConsumed) { assertTrue("Key $upEvent should be consumed", consumed) } metaState = metaState and MODIFIER.getOrDefault(key, 0).inv() upEvent.recycle() Loading @@ -1567,14 +1597,23 @@ class KeyGestureControllerTests { } } private fun interceptKey(event: KeyEvent, appDelegate: AppDelegate) { private fun interceptKey(event: KeyEvent, appDelegate: AppDelegate): Boolean { keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE) testLooper.dispatchAll() val consumed = keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L if (!consumed && !appDelegate.consumeKey(event)) { keyGestureController.interceptUnhandledKey(event, null) if (keyGestureController.interceptKeyBeforeDispatching(null, event, 0) != 0L) { return true } if (appDelegate.consumeKey(event)) { return true } if (keyGestureController.interceptUnhandledKey(event, null)) { return true } if (KeyEvent.isModifierKey(event.keyCode)) { return true } return false } fun overrideSendActionKeyEventsToFocusedWindow( Loading tests/Input/src/com/android/server/input/KeyGestureTestData.kt +11 −7 Original line number Diff line number Diff line Loading @@ -25,10 +25,7 @@ import android.view.KeyEvent /** Test data for Key gestures tests in {@link KeyGestureControllerTests} */ object KeyGestureTestData { // All Key gestures that should always happen regardless of whether focused window captures the // keys should go in this list. // (i.e. Shortcuts and keys handled in INTERCEPT_SHORTCUTS_BEFORE_KEY_CAPTURE stage) val NON_CAPTURABLE_SYSTEM_GESTURES = val MULTI_KEY_SYSTEM_GESTURES = arrayOf( KeyGestureData( "VOLUME_DOWN + POWER -> Screenshot Chord", Loading Loading @@ -89,6 +86,13 @@ object KeyGestureTestData { KeyGestureEvent.ACTION_GESTURE_COMPLETE, ), ), ) // All Key gestures that should always happen regardless of whether focused window captures the // keys should go in this list. // (i.e. Shortcuts and keys handled in INTERCEPT_SHORTCUTS_BEFORE_KEY_CAPTURE stage) val NON_CAPTURABLE_SYSTEM_GESTURES = arrayOf( KeyGestureData( "META + H -> Go Home", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H), Loading Loading @@ -377,10 +381,10 @@ object KeyGestureTestData { val CAPTURABLE_SYSTEM_GESTURES = arrayOf( KeyGestureData( "META + A -> Launch Assistant", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_A), "META + Space -> Launch Assistant", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SPACE), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, intArrayOf(KeyEvent.KEYCODE_A), intArrayOf(KeyEvent.KEYCODE_SPACE), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), ), Loading Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt +3 −3 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package com.android.systemui.keyboard.shortcut.data.source import android.content.res.Resources import android.hardware.input.InputManager import android.hardware.input.KeyGlyphMap import android.view.KeyEvent.KEYCODE_A import android.view.KeyEvent.KEYCODE_BACK import android.view.KeyEvent.KEYCODE_DPAD_LEFT import android.view.KeyEvent.KEYCODE_ESCAPE Loading @@ -32,6 +31,7 @@ import android.view.KeyEvent.KEYCODE_Q import android.view.KeyEvent.KEYCODE_RECENT_APPS import android.view.KeyEvent.KEYCODE_S import android.view.KeyEvent.KEYCODE_SLASH import android.view.KeyEvent.KEYCODE_SPACE import android.view.KeyEvent.KEYCODE_TAB import android.view.KeyEvent.META_ALT_ON import android.view.KeyEvent.META_CTRL_ON Loading Loading @@ -230,9 +230,9 @@ constructor(@Main private val resources: Resources, private val inputManager: In command(META_META_ON, KEYCODE_I) }, // Access Assistant: // - Meta + A // - Meta + Space shortcutInfo(resources.getString(R.string.group_system_access_google_assistant)) { command(META_META_ON, KEYCODE_A) command(META_META_ON, KEYCODE_SPACE) }, ) }
services/core/java/com/android/server/input/InputGestureManager.java +1 −1 Original line number Diff line number Diff line Loading @@ -104,7 +104,7 @@ final class InputGestureManager { // Initialize all system shortcuts List<InputGestureData> systemShortcuts = new ArrayList<>(List.of( createKeyGesture( KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_SPACE, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, /* allowCaptureByFocusedWindow = */true Loading
services/core/java/com/android/server/input/KeyGestureController.java +4 −0 Original line number Diff line number Diff line Loading @@ -1138,6 +1138,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } } break; Loading @@ -1152,6 +1153,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; case KeyEvent.KEYCODE_SYSRQ: Loading @@ -1160,6 +1162,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; case KeyEvent.KEYCODE_ESCAPE: Loading @@ -1168,6 +1171,7 @@ final class KeyGestureController { KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */0, /* appLaunchData = */null); return true; } break; } Loading
tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +49 −10 Original line number Diff line number Diff line Loading @@ -373,6 +373,18 @@ class KeyGestureControllerTests { testKeyGestureProduced(test, PASS_THROUGH_APP) } @Keep private fun multiKeyGestureArguments(): Array<KeyGestureData> { return KeyGestureTestData.MULTI_KEY_SYSTEM_GESTURES } @Test @Parameters(method = "multiKeyGestureArguments") fun testMultiKeyGestures(test: KeyGestureData) { setupKeyGestureController() testKeyGestureProduced(test, BLOCKING_APP) } @Test @EnableFlags(com.android.window.flags.Flags.FLAG_TOGGLE_FULLSCREEN_STATE_VIA_FULLSCREEN_KEY) fun testCustomKeyGesturesNotAllowedForSystemGestures() { Loading Loading @@ -604,7 +616,7 @@ class KeyGestureControllerTests { val listener = KeyGestureEventListener { event -> events.add(KeyGestureEvent(event)) } keyGestureController.registerKeyGestureEventListener(listener, 0) sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK), assertKeysFullyConsumed = false) testLooper.dispatchAll() assertEquals("Listener should get callbacks on key gesture event completed", 1, events.size) assertEquals( Loading Loading @@ -961,7 +973,11 @@ class KeyGestureControllerTests { fun testAccessibilityTvShortcutChordPressed() { setupKeyGestureController() sendKeys(intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 10000) sendKeys( intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 10000, assertKeysFullyConsumed = false, ) Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut() } Loading @@ -980,7 +996,11 @@ class KeyGestureControllerTests { fun testAccessibilityTvShortcutChordPressedForLessThanTimeout() { setupKeyGestureController() sendKeys(intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 0) sendKeys( intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), timeDelayMs = 0, assertKeysFullyConsumed = false, ) Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() } Loading Loading @@ -1321,7 +1341,7 @@ class KeyGestureControllerTests { /* downTime= */ 0, /* eventTime= */ 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_MACRO_1, // Random valid keycode /* repeat= */ 0, KeyEvent.META_META_ON, ) Loading Loading @@ -1363,6 +1383,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = 2 * LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(2, events.size) Loading @@ -1383,6 +1404,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = 2 * LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(0, events.size) Loading @@ -1402,6 +1424,7 @@ class KeyGestureControllerTests { sendKeys( intArrayOf(KeyEvent.KEYCODE_ESCAPE), timeDelayMs = LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS / 2, appDelegate = BLOCKING_APP, ) keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID) assertEquals(2, events.size) Loading Loading @@ -1512,6 +1535,7 @@ class KeyGestureControllerTests { appDelegate: AppDelegate = PASS_THROUGH_APP, timeDelayMs: Long = 0, displayId: Int = DEFAULT_DISPLAY, assertKeysFullyConsumed: Boolean = true, ) { var metaState = 0 val now = SystemClock.uptimeMillis() Loading @@ -1531,7 +1555,10 @@ class KeyGestureControllerTests { displayId, /* characters= */ "", ) interceptKey(downEvent, appDelegate) val consumed = interceptKey(downEvent, appDelegate) if (assertKeysFullyConsumed) { assertTrue("Key $downEvent should be consumed", consumed) } metaState = metaState or MODIFIER.getOrDefault(key, 0) downEvent.recycle() Loading Loading @@ -1559,7 +1586,10 @@ class KeyGestureControllerTests { displayId, /* characters= */ "", ) interceptKey(upEvent, appDelegate) val consumed = interceptKey(upEvent, appDelegate) if (assertKeysFullyConsumed) { assertTrue("Key $upEvent should be consumed", consumed) } metaState = metaState and MODIFIER.getOrDefault(key, 0).inv() upEvent.recycle() Loading @@ -1567,14 +1597,23 @@ class KeyGestureControllerTests { } } private fun interceptKey(event: KeyEvent, appDelegate: AppDelegate) { private fun interceptKey(event: KeyEvent, appDelegate: AppDelegate): Boolean { keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE) testLooper.dispatchAll() val consumed = keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L if (!consumed && !appDelegate.consumeKey(event)) { keyGestureController.interceptUnhandledKey(event, null) if (keyGestureController.interceptKeyBeforeDispatching(null, event, 0) != 0L) { return true } if (appDelegate.consumeKey(event)) { return true } if (keyGestureController.interceptUnhandledKey(event, null)) { return true } if (KeyEvent.isModifierKey(event.keyCode)) { return true } return false } fun overrideSendActionKeyEventsToFocusedWindow( Loading
tests/Input/src/com/android/server/input/KeyGestureTestData.kt +11 −7 Original line number Diff line number Diff line Loading @@ -25,10 +25,7 @@ import android.view.KeyEvent /** Test data for Key gestures tests in {@link KeyGestureControllerTests} */ object KeyGestureTestData { // All Key gestures that should always happen regardless of whether focused window captures the // keys should go in this list. // (i.e. Shortcuts and keys handled in INTERCEPT_SHORTCUTS_BEFORE_KEY_CAPTURE stage) val NON_CAPTURABLE_SYSTEM_GESTURES = val MULTI_KEY_SYSTEM_GESTURES = arrayOf( KeyGestureData( "VOLUME_DOWN + POWER -> Screenshot Chord", Loading Loading @@ -89,6 +86,13 @@ object KeyGestureTestData { KeyGestureEvent.ACTION_GESTURE_COMPLETE, ), ), ) // All Key gestures that should always happen regardless of whether focused window captures the // keys should go in this list. // (i.e. Shortcuts and keys handled in INTERCEPT_SHORTCUTS_BEFORE_KEY_CAPTURE stage) val NON_CAPTURABLE_SYSTEM_GESTURES = arrayOf( KeyGestureData( "META + H -> Go Home", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H), Loading Loading @@ -377,10 +381,10 @@ object KeyGestureTestData { val CAPTURABLE_SYSTEM_GESTURES = arrayOf( KeyGestureData( "META + A -> Launch Assistant", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_A), "META + Space -> Launch Assistant", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SPACE), KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, intArrayOf(KeyEvent.KEYCODE_A), intArrayOf(KeyEvent.KEYCODE_SPACE), KeyEvent.META_META_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), ), Loading