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

Commit 3821b8af authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Block addition of custom shortcuts

- If overlapping with system shortcuts
- If ovelapping with bookmarks
- If no modifierState provided for key based shortcut
- If using a system keycode for key based shortcut

DD: go/customizable_shortcuts
PRD: go/custom-kb-shortcuts

Bug: 365064144
Test: atest InputTests
Flag: com.android.hardware.input.enable_customizable_input_gestures
Change-Id: I69dd162f3318a84123b748d5ee5b9074844d8790
parent 695314eb
Loading
Loading
Loading
Loading
+283 −8
Original line number Diff line number Diff line
@@ -16,11 +16,21 @@

package com.android.server.input;

import static android.hardware.input.InputGestureData.createKeyTrigger;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSettings;
import android.hardware.input.KeyGestureEvent;
import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -29,15 +39,17 @@ import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
 * gestures and custom gestures defined by other system components using Input APIs.
 *
 * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts.
 * TODO(b/365064144): Add implementation to persist data.
 *
 */
final class InputGestureManager {
@@ -47,13 +59,242 @@ final class InputGestureManager {
            KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
                    | KeyEvent.META_META_ON;

    @GuardedBy("mCustomInputGestures")
    private final Context mContext;

    private static final Object mGestureLock = new Object();
    @GuardedBy("mGestureLock")
    private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
            mCustomInputGestures = new SparseArray<>();

    @GuardedBy("mGestureLock")
    private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
            new HashMap<>();

    @GuardedBy("mGestureLock")
    private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
            createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
            createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
            createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_SPACE,
                    KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
            createKeyTrigger(KeyEvent.KEYCODE_Z,
                    KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
            createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
            createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
    ));

    public InputGestureManager(Context context) {
        mContext = context;
    }

    public void systemRunning() {
        initSystemShortcuts();
        blockListBookmarkedTriggers();
    }

    private void initSystemShortcuts() {
        // Initialize all system shortcuts
        List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
                createKeyGesture(
                        KeyEvent.KEYCODE_A,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_H,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_ENTER,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_I,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_L,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_N,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_N,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_S,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DEL,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_ESCAPE,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_UP,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_DOWN,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_LEFT,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_LEFT,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_LEFT,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_RIGHT,
                        KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_DPAD_RIGHT,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_SLASH,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
                ),
                createKeyGesture(
                        KeyEvent.KEYCODE_TAB,
                        KeyEvent.META_META_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
                )
        ));
        if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
            systemShortcuts.add(createKeyGesture(
                    KeyEvent.KEYCODE_DEL,
                    KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                    KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
            ));
        }
        if (enableMoveToNextDisplayShortcut()) {
            systemShortcuts.add(createKeyGesture(
                    KeyEvent.KEYCODE_D,
                    KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                    KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
            ));
        }
        if (keyboardA11yShortcutControl()) {
            systemShortcuts.add(createKeyGesture(
                    KeyEvent.KEYCODE_T,
                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                    KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK
            ));
            if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_3,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
                ));
            }
            if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_4,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
                ));
            }
            if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_5,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
                ));
            }
            if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_6,
                        KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
                ));
            }
            if (enableTaskResizingKeyboardShortcuts()) {
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_LEFT_BRACKET,
                        KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
                ));
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_RIGHT_BRACKET,
                        KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
                ));
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_EQUALS,
                        KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW
                ));
                systemShortcuts.add(createKeyGesture(
                        KeyEvent.KEYCODE_MINUS,
                        KeyEvent.META_ALT_ON,
                        KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE
                ));
            }
        }
        synchronized (mGestureLock) {
            for (InputGestureData systemShortcut : systemShortcuts) {
                mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
            }
        }
    }

    private void blockListBookmarkedTriggers() {
        synchronized (mGestureLock) {
            InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
            for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
                mBlockListedTriggers.add(bookmark.getTrigger());
            }
        }
    }

    @InputManager.CustomInputGestureResult
    public int addCustomInputGesture(int userId, InputGestureData newGesture) {
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            if (mBlockListedTriggers.contains(newGesture.getTrigger())
                    || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
                return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
            }
            if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
                if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
                        KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
                    return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
                }
            }
            if (!mCustomInputGestures.contains(userId)) {
                mCustomInputGestures.put(userId, new HashMap<>());
            }
@@ -69,7 +310,7 @@ final class InputGestureManager {

    @InputManager.CustomInputGestureResult
    public int removeCustomInputGesture(int userId, InputGestureData data) {
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            if (!mCustomInputGestures.contains(userId)) {
                return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
            }
@@ -88,14 +329,14 @@ final class InputGestureManager {
    }

    public void removeAllCustomInputGestures(int userId) {
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            mCustomInputGestures.remove(userId);
        }
    }

    @NonNull
    public List<InputGestureData> getCustomInputGestures(int userId) {
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            if (!mCustomInputGestures.contains(userId)) {
                return List.of();
            }
@@ -109,7 +350,7 @@ final class InputGestureManager {
        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
            return null;
        }
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            Map<InputGestureData.Trigger, InputGestureData> customGestures =
                    mCustomInputGestures.get(userId);
            if (customGestures == null) {
@@ -120,10 +361,44 @@ final class InputGestureManager {
        }
    }

    @Nullable
    public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
            return null;
        }
        synchronized (mGestureLock) {
            int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
            return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
        }
    }

    private static InputGestureData createKeyGesture(int keycode, int modifierState,
            int keyGestureType) {
        return new InputGestureData.Builder()
                .setTrigger(createKeyTrigger(keycode, modifierState))
                .setKeyGestureType(keyGestureType)
                .build();
    }

    public void dump(IndentingPrintWriter ipw) {
        ipw.println("InputGestureManager:");
        ipw.increaseIndent();
        synchronized (mCustomInputGestures) {
        synchronized (mGestureLock) {
            ipw.println("System Shortcuts:");
            ipw.increaseIndent();
            for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
                ipw.println(systemShortcut);
            }
            ipw.decreaseIndent();
            ipw.println("Blocklisted Triggers:");
            ipw.increaseIndent();
            for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
                ipw.println(blocklistedTrigger);
            }
            ipw.decreaseIndent();
            ipw.println("Custom Gestures:");
            ipw.increaseIndent();
            int size = mCustomInputGestures.size();
            for (int i = 0; i < size; i++) {
                Map<InputGestureData.Trigger, InputGestureData> customGestures =
+20 −273

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent
import android.platform.test.annotations.Presubmit
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -42,7 +43,7 @@ class InputGestureManagerTests {

    @Before
    fun setup() {
        inputGestureManager = InputGestureManager()
        inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
    }

    @Test
+409 −453

File changed.

Preview size limit exceeded, changes collapsed.