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

Commit b7e88c95 authored by Jimmy's avatar Jimmy
Browse files

Allow search kcm fallbacks

Fixes a bug in where kcm fallbacks with search based modifiers could
not activate since PhoneWindowManager would consume all search events.
This prevented the InputDispatcher from generating the fallback.

This change modifies the behavior so that when a search-based
fallback is detected, the key event will be directly sent to
InputDispatcher to handle the fallback. This will NOT send the
original event to applications.

Bug: 384113980
Test: atest PhoneWindowManagerTest
Test: atest InputTests:InputManagerServiceTests

Flag: com.android.hardware.input.fix_search_modifier_fallbacks

Change-Id: Iacecf945ea6be9841b6009d5f30bcf83f68309ef
parent acbbc9c3
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -213,3 +213,12 @@ flag {
   is_fixed_read_only: true
}

flag {
    name: "fix_search_modifier_fallbacks"
    namespace: "input"
    description: "Fixes a bug in which fallbacks from Search based key combinations were not activating."
    bug: "384113980"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+17 −3
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.fixSearchModifierFallbacks;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
@@ -2653,6 +2654,8 @@ public class InputManagerService extends IInputManager.Stub
    @SuppressWarnings("unused")
    @VisibleForTesting
    long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
        final long keyNotConsumedGoFallback = -2;
        final long keyConsumed = -1;
        final long keyNotConsumed = 0;
        long value = keyNotConsumed;
        // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
@@ -2667,6 +2670,16 @@ public class InputManagerService extends IInputManager.Stub
            value = mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event,
                    policyFlags);
        }
        if (fixSearchModifierFallbacks() && value == keyNotConsumed && event.isMetaPressed()) {
            // If the key has not been consumed and includes the meta key, do not send the event
            // to the app and attempt to generate a fallback.
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final KeyCharacterMap.FallbackAction fallbackAction =
                    kcm.getFallbackAction(event.getKeyCode(), event.getMetaState());
            if (fallbackAction != null) {
                return keyNotConsumedGoFallback;
            }
        }
        return value;
    }

@@ -3310,9 +3323,10 @@ public class InputManagerService extends IInputManager.Stub
         * @param token the window token that's about to receive this event
         * @param event the key event that's being dispatched
         * @param policyFlags the policy flags
         * @return negative value if the key should be skipped (not sent to the app). 0 if the key
         * should proceed getting dispatched to the app. positive value to indicate the additional
         * time delay, in nanoseconds, to wait before sending this key to the app.
         * @return -1 if the key should be skipped (not sent to the app). -2 if the key should not
         * be sent to the app, but it should still generate a fallback.
         * 0 if the key should proceed getting dispatched to the app. positive value to indicate the
         * additional time delay, in nanoseconds, to wait before sending this key to the app.
         */
        long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags);

+8 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.fixSearchModifierFallbacks;
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
@@ -4181,6 +4182,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            return true;
        }

        if (fixSearchModifierFallbacks()) {
            // Pass event as unhandled to give other services, e.g. InputManagerService, the
            // opportunity to determine if the event can be modified, e.g. generating a fallback for
            // meta/search events.
            return false;
        }

        // Reserve all the META modifier combos for system behavior
        return (metaState & KeyEvent.META_META_ON) != 0;
    }
+5 −0
Original line number Diff line number Diff line
@@ -1978,6 +1978,11 @@ NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
        return inputdispatcher::KeyEntry::InterceptKeyResult::SKIP;
    }

    // -2 : Skip sending even to application and go directly to post processing e.g. fallbacks.
    if (delayMillis == -2) {
        return inputdispatcher::KeyEntry::InterceptKeyResult::FALLBACK;
    }

    return milliseconds_to_nanoseconds(delayMillis);
}

+39 −4
Original line number Diff line number Diff line
@@ -53,14 +53,16 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.testing.TestableContext;
import android.view.contentprotection.flags.Flags;
import android.view.KeyEvent;

import androidx.test.filters.SmallTest;

@@ -102,6 +104,8 @@ public class PhoneWindowManagerTests {
    public final TestableContext mContext = spy(
            new TestableContext(getInstrumentation().getContext()));

    @Mock private IBinder mInputToken;

    PhoneWindowManager mPhoneWindowManager;
    @Mock
    private ActivityTaskManagerInternal mAtmInternal;
@@ -125,6 +129,8 @@ public class PhoneWindowManagerTests {
    @Mock
    private LockPatternUtils mLockPatternUtils;

    private static final int INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY = 0;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
@@ -216,7 +222,7 @@ public class PhoneWindowManagerTests {

    @Test
    public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
        mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        int[] outAppOp = new int[1];
        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -225,7 +231,7 @@ public class PhoneWindowManagerTests {

    @Test
    public void testCheckAddPermission_withAccessibilityOverlay() {
        mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        int[] outAppOp = new int[1];
        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -234,7 +240,7 @@ public class PhoneWindowManagerTests {

    @Test
    public void testCheckAddPermission_withAccessibilityOverlay_flagDisabled() {
        mSetFlagsRule.disableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
        int[] outAppOp = new int[1];
        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -401,6 +407,35 @@ public class PhoneWindowManagerTests {
        verify(mDreamManagerInternal).requestDream();
    }

    @EnableFlags(com.android.hardware.input.Flags.FLAG_FIX_SEARCH_MODIFIER_FALLBACKS)
    public void testInterceptKeyBeforeDispatching() {
        // Handle sub-tasks of init().
        doNothing().when(mPhoneWindowManager).updateSettings(any());
        doNothing().when(mPhoneWindowManager).initializeHdmiState();
        final DisplayPolicy displayPolicy = mock(DisplayPolicy.class);
        mPhoneWindowManager.mDefaultDisplayPolicy = displayPolicy;
        mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
        final PowerManager pm = mock(PowerManager.class);
        doReturn(true).when(pm).isInteractive();
        doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));

        mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init(
                new PhoneWindowManager.Injector(mContext,
                        mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0);

        // Case: KeyNotConsumed with meta key.
        KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
                KeyEvent.KEYCODE_A, 0, KeyEvent.META_META_ON);
        long result = mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, keyEvent, 0);
        assertEquals(INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY, result);

        // Case: KeyNotConsumed without meta key.
        KeyEvent keyEvent1 = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
                KeyEvent.KEYCODE_ESCAPE, 0, 0);
        long result1 = mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, keyEvent1, 0);
        assertEquals(INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY, result1);
    }

    private void initPhoneWindowManager() {
        mPhoneWindowManager.mDefaultDisplayPolicy = mDisplayPolicy;
        mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
Loading