• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.input;
18 
19 import static android.hardware.input.InputGestureData.createKeyTrigger;
20 
21 import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
22 import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
23 import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
24 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
25 import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.UserIdInt;
30 import android.content.Context;
31 import android.hardware.input.InputGestureData;
32 import android.hardware.input.InputManager;
33 import android.hardware.input.InputSettings;
34 import android.hardware.input.KeyGestureEvent;
35 import android.os.SystemProperties;
36 import android.util.IndentingPrintWriter;
37 import android.util.SparseArray;
38 import android.view.KeyEvent;
39 import android.window.DesktopModeFlags;
40 
41 import com.android.internal.annotations.GuardedBy;
42 
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.Set;
50 
51 /**
52  * A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
53  * gestures and custom gestures defined by other system components using Input APIs.
54  *
55  * TODO(b/365064144): Add implementation to persist data.
56  *
57  */
58 final class InputGestureManager {
59     private static final String TAG = "InputGestureManager";
60 
61     private static final int KEY_GESTURE_META_MASK =
62             KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
63                     | KeyEvent.META_META_ON;
64 
65     private final Context mContext;
66 
67     private static final Object mGestureLock = new Object();
68     @GuardedBy("mGestureLock")
69     private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
70             mCustomInputGestures = new SparseArray<>();
71 
72     @GuardedBy("mGestureLock")
73     private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
74             new HashMap<>();
75 
76     @GuardedBy("mGestureLock")
77     private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
78             createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
79             createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
80             createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
81             createKeyTrigger(KeyEvent.KEYCODE_SPACE,
82                     KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
83             createKeyTrigger(KeyEvent.KEYCODE_Z,
84                     KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
85             createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
86             createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
87             createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
88             createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
89             createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
90             createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
91     ));
92 
InputGestureManager(Context context)93     public InputGestureManager(Context context) {
94         mContext = context;
95     }
96 
init(List<InputGestureData> bookmarks)97     public void init(List<InputGestureData> bookmarks) {
98         initSystemShortcuts();
99         blockListBookmarkedTriggers(bookmarks);
100     }
101 
initSystemShortcuts()102     private void initSystemShortcuts() {
103         // Initialize all system shortcuts
104         List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
105                 createKeyGesture(
106                         KeyEvent.KEYCODE_A,
107                         KeyEvent.META_META_ON,
108                         KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
109                 ),
110                 createKeyGesture(
111                         KeyEvent.KEYCODE_H,
112                         KeyEvent.META_META_ON,
113                         KeyGestureEvent.KEY_GESTURE_TYPE_HOME
114                 ),
115                 createKeyGesture(
116                         KeyEvent.KEYCODE_ENTER,
117                         KeyEvent.META_META_ON,
118                         KeyGestureEvent.KEY_GESTURE_TYPE_HOME
119                 ),
120                 createKeyGesture(
121                         KeyEvent.KEYCODE_I,
122                         KeyEvent.META_META_ON,
123                         KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
124                 ),
125                 createKeyGesture(
126                         KeyEvent.KEYCODE_L,
127                         KeyEvent.META_META_ON,
128                         KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
129                 ),
130                 createKeyGesture(
131                         KeyEvent.KEYCODE_N,
132                         KeyEvent.META_META_ON,
133                         KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
134                 ),
135                 createKeyGesture(
136                         KeyEvent.KEYCODE_S,
137                         KeyEvent.META_META_ON,
138                         KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
139                 ),
140                 createKeyGesture(
141                         KeyEvent.KEYCODE_ESCAPE,
142                         KeyEvent.META_META_ON,
143                         KeyGestureEvent.KEY_GESTURE_TYPE_BACK
144                 ),
145                 createKeyGesture(
146                         KeyEvent.KEYCODE_DPAD_UP,
147                         KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
148                         KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
149                 ),
150                 createKeyGesture(
151                         KeyEvent.KEYCODE_DPAD_DOWN,
152                         KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
153                         KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
154                 ),
155                 createKeyGesture(
156                         KeyEvent.KEYCODE_DPAD_LEFT,
157                         KeyEvent.META_META_ON,
158                         KeyGestureEvent.KEY_GESTURE_TYPE_BACK
159                 ),
160                 createKeyGesture(
161                         KeyEvent.KEYCODE_DPAD_LEFT,
162                         KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
163                         KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
164                 ),
165                 createKeyGesture(
166                         KeyEvent.KEYCODE_DPAD_RIGHT,
167                         KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
168                         KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
169                 ),
170                 createKeyGesture(
171                         KeyEvent.KEYCODE_SLASH,
172                         KeyEvent.META_META_ON,
173                         KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
174                 ),
175                 createKeyGesture(
176                         KeyEvent.KEYCODE_TAB,
177                         KeyEvent.META_META_ON,
178                         KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
179                 )
180         ));
181         if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
182             systemShortcuts.add(createKeyGesture(
183                     KeyEvent.KEYCODE_DEL,
184                     KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
185                     KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
186             ));
187         }
188         if (enableMoveToNextDisplayShortcut()) {
189             systemShortcuts.add(createKeyGesture(
190                     KeyEvent.KEYCODE_D,
191                     KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
192                     KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
193             ));
194         }
195         if (enableTalkbackAndMagnifierKeyGestures()) {
196             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
197                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
198                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
199             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_M,
200                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
201                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION));
202             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_S,
203                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
204                     KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
205         }
206         if (enableVoiceAccessKeyGestures()) {
207             systemShortcuts.add(
208                     createKeyGesture(
209                             KeyEvent.KEYCODE_V,
210                             KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
211                             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
212         }
213         if (DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue()) {
214             systemShortcuts.add(createKeyGesture(
215                     KeyEvent.KEYCODE_LEFT_BRACKET,
216                     KeyEvent.META_META_ON,
217                     KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
218             ));
219             systemShortcuts.add(createKeyGesture(
220                     KeyEvent.KEYCODE_RIGHT_BRACKET,
221                     KeyEvent.META_META_ON,
222                     KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
223             ));
224             systemShortcuts.add(createKeyGesture(
225                     KeyEvent.KEYCODE_EQUALS,
226                     KeyEvent.META_META_ON,
227                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW
228             ));
229             systemShortcuts.add(createKeyGesture(
230                     KeyEvent.KEYCODE_MINUS,
231                     KeyEvent.META_META_ON,
232                     KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
233             ));
234         }
235         if (keyboardA11yShortcutControl()) {
236             systemShortcuts.add(createKeyGesture(
237                     KeyEvent.KEYCODE_3,
238                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
239                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
240             ));
241             if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
242                 systemShortcuts.add(createKeyGesture(
243                         KeyEvent.KEYCODE_4,
244                         KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
245                         KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
246                 ));
247             }
248             systemShortcuts.add(createKeyGesture(
249                     KeyEvent.KEYCODE_5,
250                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
251                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
252             ));
253             systemShortcuts.add(createKeyGesture(
254                     KeyEvent.KEYCODE_6,
255                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
256                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
257             ));
258         }
259         synchronized (mGestureLock) {
260             for (InputGestureData systemShortcut : systemShortcuts) {
261                 mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
262             }
263         }
264     }
265 
blockListBookmarkedTriggers(List<InputGestureData> bookmarks)266     private void blockListBookmarkedTriggers(List<InputGestureData> bookmarks) {
267         synchronized (mGestureLock) {
268             for (InputGestureData bookmark : bookmarks) {
269                 mBlockListedTriggers.add(bookmark.getTrigger());
270             }
271         }
272     }
273 
274     @Nullable
getInputGesture(int userId, InputGestureData.Trigger trigger)275     public InputGestureData getInputGesture(int userId, InputGestureData.Trigger trigger) {
276         synchronized (mGestureLock) {
277             if (mBlockListedTriggers.contains(trigger)) {
278                 return new InputGestureData.Builder().setTrigger(trigger).setKeyGestureType(
279                         KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_RESERVED).build();
280             }
281             if (trigger instanceof InputGestureData.KeyTrigger keyTrigger) {
282                 if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
283                         KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
284                     return new InputGestureData.Builder().setTrigger(trigger).setKeyGestureType(
285                             KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_RESERVED).build();
286                 }
287             }
288             InputGestureData gestureData = mSystemShortcuts.get(trigger);
289             if (gestureData != null) {
290                 return gestureData;
291             }
292             if (!mCustomInputGestures.contains(userId)) {
293                 return null;
294             }
295             return mCustomInputGestures.get(userId).get(trigger);
296         }
297     }
298 
299     @InputManager.CustomInputGestureResult
addCustomInputGesture(int userId, InputGestureData newGesture)300     public int addCustomInputGesture(int userId, InputGestureData newGesture) {
301         synchronized (mGestureLock) {
302             if (mBlockListedTriggers.contains(newGesture.getTrigger())
303                     || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
304                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
305             }
306             if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
307                 if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
308                         KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
309                     return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
310                 }
311             }
312             if (!mCustomInputGestures.contains(userId)) {
313                 mCustomInputGestures.put(userId, new HashMap<>());
314             }
315             Map<InputGestureData.Trigger, InputGestureData> customGestures =
316                     mCustomInputGestures.get(userId);
317             if (customGestures.containsKey(newGesture.getTrigger())) {
318                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS;
319             }
320             customGestures.put(newGesture.getTrigger(), newGesture);
321             return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
322         }
323     }
324 
325     @InputManager.CustomInputGestureResult
removeCustomInputGesture(int userId, InputGestureData data)326     public int removeCustomInputGesture(int userId, InputGestureData data) {
327         synchronized (mGestureLock) {
328             if (!mCustomInputGestures.contains(userId)) {
329                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
330             }
331             Map<InputGestureData.Trigger, InputGestureData> customGestures =
332                     mCustomInputGestures.get(userId);
333             InputGestureData customGesture = customGestures.get(data.getTrigger());
334             if (!Objects.equals(data, customGesture)) {
335                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
336             }
337             customGestures.remove(data.getTrigger());
338             if (customGestures.isEmpty()) {
339                 mCustomInputGestures.remove(userId);
340             }
341             return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
342         }
343     }
344 
removeAllCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter)345     public void removeAllCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter) {
346         synchronized (mGestureLock) {
347             Map<InputGestureData.Trigger, InputGestureData> customGestures =
348                     mCustomInputGestures.get(userId);
349             if (customGestures == null) {
350                 return;
351             }
352             if (filter == null) {
353                 mCustomInputGestures.remove(userId);
354                 return;
355             }
356             customGestures.entrySet().removeIf(entry -> filter.matches(entry.getValue()));
357             if (customGestures.isEmpty()) {
358                 mCustomInputGestures.remove(userId);
359             }
360         }
361     }
362 
363     @NonNull
getCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter)364     public List<InputGestureData> getCustomInputGestures(int userId,
365             @Nullable InputGestureData.Filter filter) {
366         synchronized (mGestureLock) {
367             if (!mCustomInputGestures.contains(userId)) {
368                 return List.of();
369             }
370             Map<InputGestureData.Trigger, InputGestureData> customGestures =
371                     mCustomInputGestures.get(userId);
372             if (filter == null) {
373                 return new ArrayList<>(customGestures.values());
374             }
375             List<InputGestureData> result = new ArrayList<>();
376             for (InputGestureData customGesture : customGestures.values()) {
377                 if (filter.matches(customGesture)) {
378                     result.add(customGesture);
379                 }
380             }
381             return result;
382         }
383     }
384 
385     @Nullable
getCustomGestureForKeyEvent(@serIdInt int userId, KeyEvent event)386     public InputGestureData getCustomGestureForKeyEvent(@UserIdInt int userId, KeyEvent event) {
387         final int keyCode = event.getKeyCode();
388         if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
389             return null;
390         }
391         synchronized (mGestureLock) {
392             Map<InputGestureData.Trigger, InputGestureData> customGestures =
393                     mCustomInputGestures.get(userId);
394             if (customGestures == null) {
395                 return null;
396             }
397             int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
398             return customGestures.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
399         }
400     }
401 
402     @Nullable
getCustomGestureForTouchpadGesture(@serIdInt int userId, int touchpadGestureType)403     public InputGestureData getCustomGestureForTouchpadGesture(@UserIdInt int userId,
404             int touchpadGestureType) {
405         if (touchpadGestureType == InputGestureData.TOUCHPAD_GESTURE_TYPE_UNKNOWN) {
406             return null;
407         }
408         synchronized (mGestureLock) {
409             Map<InputGestureData.Trigger, InputGestureData> customGestures =
410                     mCustomInputGestures.get(userId);
411             if (customGestures == null) {
412                 return null;
413             }
414             return customGestures.get(InputGestureData.createTouchpadTrigger(touchpadGestureType));
415         }
416     }
417 
418     @Nullable
getSystemShortcutForKeyEvent(KeyEvent event)419     public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
420         final int keyCode = event.getKeyCode();
421         if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
422             return null;
423         }
424         synchronized (mGestureLock) {
425             int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
426             return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
427         }
428     }
429 
createKeyGesture(int keycode, int modifierState, int keyGestureType)430     private static InputGestureData createKeyGesture(int keycode, int modifierState,
431             int keyGestureType) {
432         return new InputGestureData.Builder()
433                 .setTrigger(createKeyTrigger(keycode, modifierState))
434                 .setKeyGestureType(keyGestureType)
435                 .build();
436     }
437 
dump(IndentingPrintWriter ipw)438     public void dump(IndentingPrintWriter ipw) {
439         ipw.println("InputGestureManager:");
440         ipw.increaseIndent();
441         synchronized (mGestureLock) {
442             ipw.println("System Shortcuts:");
443             ipw.increaseIndent();
444             for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
445                 ipw.println(systemShortcut);
446             }
447             ipw.decreaseIndent();
448             ipw.println("Blocklisted Triggers:");
449             ipw.increaseIndent();
450             for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
451                 ipw.println(blocklistedTrigger);
452             }
453             ipw.decreaseIndent();
454             ipw.println("Custom Gestures:");
455             ipw.increaseIndent();
456             int size = mCustomInputGestures.size();
457             for (int i = 0; i < size; i++) {
458                 Map<InputGestureData.Trigger, InputGestureData> customGestures =
459                         mCustomInputGestures.valueAt(i);
460                 ipw.println("UserId = " + mCustomInputGestures.keyAt(i));
461                 ipw.increaseIndent();
462                 for (InputGestureData customGesture : customGestures.values()) {
463                     ipw.println(customGesture);
464                 }
465                 ipw.decreaseIndent();
466             }
467         }
468         ipw.decreaseIndent();
469     }
470 }
471