Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca4dca12 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Fix: Let A11y services consume keys if not a multi-key gesture

Only block keys to reach A11y filter if the key is a multi-key
combination gesture like Vol up + Vol down

Test: atest KeygestureControllerTests
Bug: 433733515
Flag: com.android.hardware.input.fix_keyboard_interceptor_policy_call
Change-Id: I3ea12097feac648ef7cb5fcd39dfe77f27250fa4
parent 4b03c792
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -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)
+25 −11
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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);
+73 −0
Original line number Diff line number Diff line
@@ -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
@@ -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 }
@@ -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(