Loading services/core/java/com/android/server/input/InputManagerService.java +1 −2 Original line number Diff line number Diff line Loading @@ -3934,8 +3934,7 @@ public class InputManagerService extends IInputManager.Stub @Override public long interceptKeyCombinationBeforeAccessibility(@NonNull KeyEvent event) { if (fixKeyboardInterceptorPolicyCall()) { return mKeyGestureController.interceptKeyBeforeDispatching(/* focusedToken= */null, event, /* policyFlags= */0); return mKeyGestureController.interceptKeyCombinationBeforeAccessibility(event); } else { return mWindowManagerCallbacks.interceptKeyBeforeDispatching( /* focusedToken= */null, event) Loading services/core/java/com/android/server/input/KeyGestureController.java +25 −11 Original line number Diff line number Diff line Loading @@ -569,6 +569,12 @@ final class KeyGestureController { return false; } // Need to handle multi-key combinations before providing keys to A11y services like // talkback, etc. public long interceptKeyCombinationBeforeAccessibility(@NonNull KeyEvent event) { return interceptMultiKeyCombination(event); } // Shortcut handling stages: // 1. Before key capture // - All Meta key shortcuts with "allowCaptureByFocusedWindow" set to false Loading Loading @@ -599,17 +605,9 @@ final class KeyGestureController { } // Multi-key shortcuts should never be blocked by any focused window configuration if (mKeyCombinationManager.isKeyConsumed(event)) { return KEY_INTERCEPT_RESULT_CONSUMED; } if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { final long now = SystemClock.uptimeMillis(); final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout( event.getKeyCode()); if (now < interceptTimeout) { return interceptTimeout - now; } long result = interceptMultiKeyCombination(event); if (result != KEY_INTERCEPT_RESULT_NOT_CONSUMED) { return result; } // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts Loading Loading @@ -650,6 +648,22 @@ final class KeyGestureController { return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } private long interceptMultiKeyCombination(@NonNull KeyEvent event) { if (mKeyCombinationManager.isKeyConsumed(event)) { return KEY_INTERCEPT_RESULT_CONSUMED; } if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { final long now = SystemClock.uptimeMillis(); final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout( event.getKeyCode()); if (now < interceptTimeout) { return interceptTimeout - now; } } return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } private boolean shouldInterceptShortcuts(IBinder focusedToken) { KeyInterceptionInfo info = mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); Loading tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +73 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test Loading Loading @@ -144,6 +145,8 @@ class KeyGestureControllerTests { const val RANDOM_PID2 = 12 const val RANDOM_DISPLAY_ID = 123 const val SCREENSHOT_CHORD_DELAY: Long = 1000 // Current default multi-key press timeout used in KeyCombinationManager const val COMBINE_KEY_DELAY_MILLIS: Long = 150 const val LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS: Long = 1000 // App delegate that consumes all keys that it receives val BLOCKING_APP = AppDelegate { _ -> true } Loading Loading @@ -976,6 +979,76 @@ class KeyGestureControllerTests { Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() } @Keep private fun keyCodesUsedForKeyCombinations(): Array<Int> { return arrayOf( KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY, ) } @Test @Parameters(method = "keyCodesUsedForKeyCombinations") fun testInterceptKeyCombinationForAccessibility_blocksKey_whenOngoingKeyCombination( keyCode: Int ) { setupKeyGestureController() val now = SystemClock.uptimeMillis() val downEvent = KeyEvent.obtain( now, now, KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, /* metaState */ 0, DEVICE_ID, /* scanCode= */ 0, /* flags= */ 0, InputDevice.SOURCE_KEYBOARD, /* displayId = */ 0, /* characters= */ "", ) keyGestureController.interceptKeyBeforeQueueing(downEvent, FLAG_INTERACTIVE) testLooper.dispatchAll() // Assert that interceptKeyCombinationBeforeAccessibility returns the delay to wait // until key can be forwarded to A11y services (should be > 0 if there is ongoing gesture) assertTrue(keyGestureController.interceptKeyCombinationBeforeAccessibility(downEvent) > 0) } @Test @Parameters(method = "keyCodesUsedForKeyCombinations") fun testInterceptKeyCombinationForAccessibility_letsKeyPass_whenKeyCombinationTimedOut( keyCode: Int ) { setupKeyGestureController() val now = SystemClock.uptimeMillis() val downEvent = KeyEvent.obtain( now - COMBINE_KEY_DELAY_MILLIS, now - COMBINE_KEY_DELAY_MILLIS, KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, /* metaState */ 0, DEVICE_ID, /* scanCode= */ 0, /* flags= */ 0, InputDevice.SOURCE_KEYBOARD, /* displayId = */ 0, /* characters= */ "", ) keyGestureController.interceptKeyBeforeQueueing(downEvent, FLAG_INTERACTIVE) testLooper.dispatchAll() assertEquals(0, keyGestureController.interceptKeyCombinationBeforeAccessibility(downEvent)) } @Keep private fun screenshotTestArguments(): Array<KeyGestureData> { return arrayOf( Loading Loading
services/core/java/com/android/server/input/InputManagerService.java +1 −2 Original line number Diff line number Diff line Loading @@ -3934,8 +3934,7 @@ public class InputManagerService extends IInputManager.Stub @Override public long interceptKeyCombinationBeforeAccessibility(@NonNull KeyEvent event) { if (fixKeyboardInterceptorPolicyCall()) { return mKeyGestureController.interceptKeyBeforeDispatching(/* focusedToken= */null, event, /* policyFlags= */0); return mKeyGestureController.interceptKeyCombinationBeforeAccessibility(event); } else { return mWindowManagerCallbacks.interceptKeyBeforeDispatching( /* focusedToken= */null, event) Loading
services/core/java/com/android/server/input/KeyGestureController.java +25 −11 Original line number Diff line number Diff line Loading @@ -569,6 +569,12 @@ final class KeyGestureController { return false; } // Need to handle multi-key combinations before providing keys to A11y services like // talkback, etc. public long interceptKeyCombinationBeforeAccessibility(@NonNull KeyEvent event) { return interceptMultiKeyCombination(event); } // Shortcut handling stages: // 1. Before key capture // - All Meta key shortcuts with "allowCaptureByFocusedWindow" set to false Loading Loading @@ -599,17 +605,9 @@ final class KeyGestureController { } // Multi-key shortcuts should never be blocked by any focused window configuration if (mKeyCombinationManager.isKeyConsumed(event)) { return KEY_INTERCEPT_RESULT_CONSUMED; } if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { final long now = SystemClock.uptimeMillis(); final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout( event.getKeyCode()); if (now < interceptTimeout) { return interceptTimeout - now; } long result = interceptMultiKeyCombination(event); if (result != KEY_INTERCEPT_RESULT_NOT_CONSUMED) { return result; } // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts Loading Loading @@ -650,6 +648,22 @@ final class KeyGestureController { return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } private long interceptMultiKeyCombination(@NonNull KeyEvent event) { if (mKeyCombinationManager.isKeyConsumed(event)) { return KEY_INTERCEPT_RESULT_CONSUMED; } if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { final long now = SystemClock.uptimeMillis(); final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout( event.getKeyCode()); if (now < interceptTimeout) { return interceptTimeout - now; } } return KEY_INTERCEPT_RESULT_NOT_CONSUMED; } private boolean shouldInterceptShortcuts(IBinder focusedToken) { KeyInterceptionInfo info = mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken); Loading
tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +73 −0 Original line number Diff line number Diff line Loading @@ -72,6 +72,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test Loading Loading @@ -144,6 +145,8 @@ class KeyGestureControllerTests { const val RANDOM_PID2 = 12 const val RANDOM_DISPLAY_ID = 123 const val SCREENSHOT_CHORD_DELAY: Long = 1000 // Current default multi-key press timeout used in KeyCombinationManager const val COMBINE_KEY_DELAY_MILLIS: Long = 150 const val LONG_PRESS_DELAY_FOR_ESCAPE_MILLIS: Long = 1000 // App delegate that consumes all keys that it receives val BLOCKING_APP = AppDelegate { _ -> true } Loading Loading @@ -976,6 +979,76 @@ class KeyGestureControllerTests { Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() } @Keep private fun keyCodesUsedForKeyCombinations(): Array<Int> { return arrayOf( KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY, ) } @Test @Parameters(method = "keyCodesUsedForKeyCombinations") fun testInterceptKeyCombinationForAccessibility_blocksKey_whenOngoingKeyCombination( keyCode: Int ) { setupKeyGestureController() val now = SystemClock.uptimeMillis() val downEvent = KeyEvent.obtain( now, now, KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, /* metaState */ 0, DEVICE_ID, /* scanCode= */ 0, /* flags= */ 0, InputDevice.SOURCE_KEYBOARD, /* displayId = */ 0, /* characters= */ "", ) keyGestureController.interceptKeyBeforeQueueing(downEvent, FLAG_INTERACTIVE) testLooper.dispatchAll() // Assert that interceptKeyCombinationBeforeAccessibility returns the delay to wait // until key can be forwarded to A11y services (should be > 0 if there is ongoing gesture) assertTrue(keyGestureController.interceptKeyCombinationBeforeAccessibility(downEvent) > 0) } @Test @Parameters(method = "keyCodesUsedForKeyCombinations") fun testInterceptKeyCombinationForAccessibility_letsKeyPass_whenKeyCombinationTimedOut( keyCode: Int ) { setupKeyGestureController() val now = SystemClock.uptimeMillis() val downEvent = KeyEvent.obtain( now - COMBINE_KEY_DELAY_MILLIS, now - COMBINE_KEY_DELAY_MILLIS, KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, /* metaState */ 0, DEVICE_ID, /* scanCode= */ 0, /* flags= */ 0, InputDevice.SOURCE_KEYBOARD, /* displayId = */ 0, /* characters= */ "", ) keyGestureController.interceptKeyBeforeQueueing(downEvent, FLAG_INTERACTIVE) testLooper.dispatchAll() assertEquals(0, keyGestureController.interceptKeyCombinationBeforeAccessibility(downEvent)) } @Keep private fun screenshotTestArguments(): Array<KeyGestureData> { return arrayOf( Loading