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