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.accessibility; 18 19 import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER; 20 import static android.util.MathUtils.sqrt; 21 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.companion.virtual.VirtualDeviceManager; 25 import android.companion.virtual.VirtualDeviceParams; 26 import android.hardware.input.InputManager; 27 import android.hardware.input.VirtualMouse; 28 import android.hardware.input.VirtualMouseButtonEvent; 29 import android.hardware.input.VirtualMouseConfig; 30 import android.hardware.input.VirtualMouseRelativeEvent; 31 import android.hardware.input.VirtualMouseScrollEvent; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.util.Log; 36 import android.util.Slog; 37 import android.util.SparseArray; 38 import android.view.InputDevice; 39 import android.view.KeyEvent; 40 41 import androidx.annotation.VisibleForTesting; 42 43 import com.android.server.LocalServices; 44 import com.android.server.companion.virtual.VirtualDeviceManagerInternal; 45 46 /** 47 * Implements the "mouse keys" accessibility feature for physical keyboards. 48 * 49 * If enabled, mouse keys will allow users to use a physical keyboard to 50 * control the mouse on the display. 51 * The following mouse functionality is supported by the mouse keys: 52 * <ul> 53 * <li> Move the mouse pointer in different directions (up, down, left, right and diagonally). 54 * <li> Click the mouse button (left, right and middle click). 55 * <li> Press and hold the mouse button. 56 * <li> Release the mouse button. 57 * <li> Scroll (up and down). 58 * </ul> 59 * 60 * The keys that are mapped to mouse keys are consumed by {@link AccessibilityInputFilter}. 61 * Non-mouse key {@link KeyEvent} will be passed to the parent handler to be handled as usual. 62 * A new {@link VirtualMouse} is created whenever the mouse keys feature is turned on in Settings. 63 * In case multiple physical keyboard are connected to a device, 64 * mouse keys of each physical keyboard will control a single (global) mouse pointer. 65 */ 66 public class MouseKeysInterceptor extends BaseEventStreamTransformation 67 implements Handler.Callback, InputManager.InputDeviceListener { 68 private static final String LOG_TAG = "MouseKeysInterceptor"; 69 70 // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG' 71 // (requires restart) 72 private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); 73 74 private static final int MESSAGE_MOVE_MOUSE_POINTER = 1; 75 private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2; 76 private static final int KEY_NOT_SET = -1; 77 78 /** Time interval after which mouse action will be repeated */ 79 private static final int INTERVAL_MILLIS = 10; 80 81 @VisibleForTesting 82 public static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f; 83 @VisibleForTesting 84 public static final float MOUSE_SCROLL_STEP = 0.2f; 85 86 private final AccessibilityManagerService mAms; 87 private final Handler mHandler; 88 private final InputManager mInputManager; 89 90 /** Thread to wait for virtual mouse creation to complete */ 91 private final Thread mCreateVirtualMouseThread; 92 93 /** 94 * Map of device IDs to a map of key codes to their corresponding {@link MouseKeyEvent} values. 95 * To ensure thread safety for the map, all access and modification of the map 96 * should happen on the same thread, i.e., on the handler thread. 97 */ 98 private final SparseArray<SparseArray<MouseKeyEvent>> mDeviceKeyCodeMap = 99 new SparseArray<>(); 100 101 VirtualDeviceManager.VirtualDevice mVirtualDevice = null; 102 103 private VirtualMouse mVirtualMouse = null; 104 105 /** 106 * State of the active directional mouse key. 107 * Multiple mouse keys will not be allowed to be used simultaneously i.e., 108 * once a mouse key is pressed, other mouse key presses will be disregarded 109 * (except for when the "HOLD" key is pressed). 110 */ 111 private int mActiveMoveKey = KEY_NOT_SET; 112 113 /** State of the active scroll mouse key. */ 114 private int mActiveScrollKey = KEY_NOT_SET; 115 116 /** Last time the key action was performed */ 117 private long mLastTimeKeyActionPerformed = 0; 118 119 /** Whether scroll toggle is on */ 120 private boolean mScrollToggleOn = false; 121 122 /** The ID of the input device that is currently active */ 123 private int mActiveInputDeviceId = 0; 124 125 /** 126 * Enum representing different types of mouse key events, each associated with a specific 127 * key code. 128 * 129 * <p> These events correspond to various mouse actions such as directional movements, 130 * clicks, and scrolls, mapped to specific keys on the keyboard. 131 * The key codes here are the QWERTY key codes, and should be accessed via 132 * {@link MouseKeyEvent#getKeyCode(InputDevice)} 133 * so that it is mapped to the equivalent key on the keyboard layout of the keyboard device 134 * that is actually in use. 135 * </p> 136 */ 137 public enum MouseKeyEvent { 138 DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7), 139 UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8), 140 DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9), 141 LEFT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_U), 142 RIGHT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_O), 143 DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J), 144 DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K), 145 DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L), 146 LEFT_CLICK(KeyEvent.KEYCODE_I), 147 RIGHT_CLICK(KeyEvent.KEYCODE_SLASH), 148 HOLD(KeyEvent.KEYCODE_M), 149 RELEASE(KeyEvent.KEYCODE_COMMA), 150 SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD); 151 152 private final int mLocationKeyCode; MouseKeyEvent(int enumValue)153 MouseKeyEvent(int enumValue) { 154 mLocationKeyCode = enumValue; 155 } 156 157 @VisibleForTesting getKeyCodeValue()158 public final int getKeyCodeValue() { 159 return mLocationKeyCode; 160 } 161 162 /** 163 * Get the key code associated with the given MouseKeyEvent for the given keyboard 164 * input device, taking into account its layout. 165 * The default is to return the keycode for the default layout (QWERTY). 166 * We check if the input device has been generated using {@link InputDevice#getGeneration()} 167 * to test with the default {@link MouseKeyEvent} values in the unit tests. 168 */ getKeyCode(InputDevice inputDevice)169 public int getKeyCode(InputDevice inputDevice) { 170 if (inputDevice.getGeneration() == -1) { 171 return mLocationKeyCode; 172 } 173 return inputDevice.getKeyCodeForKeyLocation(mLocationKeyCode); 174 } 175 176 /** 177 * Convert int value of the key code to corresponding {@link MouseKeyEvent} 178 * enum for a particular device ID. 179 * If no matching value is found, this will return {@code null}. 180 */ 181 @Nullable from(int keyCode, int deviceId, SparseArray<SparseArray<MouseKeyEvent>> deviceKeyCodeMap)182 public static MouseKeyEvent from(int keyCode, int deviceId, 183 SparseArray<SparseArray<MouseKeyEvent>> deviceKeyCodeMap) { 184 SparseArray<MouseKeyEvent> keyCodeToEnumMap = deviceKeyCodeMap.get(deviceId); 185 if (keyCodeToEnumMap != null) { 186 return keyCodeToEnumMap.get(keyCode); 187 } 188 return null; 189 } 190 } 191 192 /** 193 * Create a map of key codes to their corresponding {@link MouseKeyEvent} values 194 * for a specific input device. 195 * The key for {@code mDeviceKeyCodeMap} is the deviceId. 196 * The key for {@code keyCodeToEnumMap} is the keycode for each 197 * {@link MouseKeyEvent} according to the keyboard layout of the input device. 198 */ initializeDeviceToEnumMap(InputDevice inputDevice)199 public void initializeDeviceToEnumMap(InputDevice inputDevice) { 200 int deviceId = inputDevice.getId(); 201 SparseArray<MouseKeyEvent> keyCodeToEnumMap = new SparseArray<>(); 202 for (MouseKeyEvent mouseKeyEventType : MouseKeyEvent.values()) { 203 int keyCode = mouseKeyEventType.getKeyCode(inputDevice); 204 keyCodeToEnumMap.put(keyCode, mouseKeyEventType); 205 } 206 mDeviceKeyCodeMap.put(deviceId, keyCodeToEnumMap); 207 } 208 209 /** 210 * Construct a new MouseKeysInterceptor. 211 * 212 * @param service The service to notify of key events 213 * @param looper Looper to use for callbacks and messages 214 * @param displayId Display ID to send mouse events to 215 */ 216 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) MouseKeysInterceptor(AccessibilityManagerService service, InputManager inputManager, Looper looper, int displayId)217 public MouseKeysInterceptor(AccessibilityManagerService service, 218 InputManager inputManager, Looper looper, int displayId) { 219 mAms = service; 220 mInputManager = inputManager; 221 mHandler = new Handler(looper, this); 222 // Create the virtual mouse on a separate thread since virtual device creation 223 // should happen on an auxiliary thread, and not from the handler's thread. 224 // This is because the handler thread is the same as the main thread, 225 // and the main thread will be blocked waiting for the virtual device to be created. 226 mCreateVirtualMouseThread = new Thread(() -> { 227 mVirtualMouse = createVirtualMouse(displayId); 228 }); 229 mCreateVirtualMouseThread.start(); 230 // Register an input device listener to watch when input devices are 231 // added, removed or reconfigured. 232 mInputManager.registerInputDeviceListener(this, mHandler); 233 } 234 235 /** 236 * Wait for {@code mVirtualMouse} to be created. 237 * This will ensure that {@code mVirtualMouse} is always created before 238 * trying to send mouse events. 239 **/ waitForVirtualMouseCreation()240 private void waitForVirtualMouseCreation() { 241 try { 242 // Block the current thread until the virtual mouse creation thread completes. 243 mCreateVirtualMouseThread.join(); 244 } catch (InterruptedException e) { 245 Thread.currentThread().interrupt(); 246 throw new RuntimeException(e); 247 } 248 } 249 250 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) sendVirtualMouseRelativeEvent(float x, float y)251 private void sendVirtualMouseRelativeEvent(float x, float y) { 252 waitForVirtualMouseCreation(); 253 mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder() 254 .setRelativeX(x) 255 .setRelativeY(y) 256 .build() 257 ); 258 } 259 260 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) sendVirtualMouseButtonEvent(int buttonCode, int actionCode)261 private void sendVirtualMouseButtonEvent(int buttonCode, int actionCode) { 262 waitForVirtualMouseCreation(); 263 mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder() 264 .setAction(actionCode) 265 .setButtonCode(buttonCode) 266 .build() 267 ); 268 } 269 270 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) sendVirtualMouseScrollEvent(float x, float y)271 private void sendVirtualMouseScrollEvent(float x, float y) { 272 waitForVirtualMouseCreation(); 273 mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder() 274 .setXAxisMovement(x) 275 .setYAxisMovement(y) 276 .build() 277 ); 278 } 279 280 /** 281 * Performs a mouse scroll action based on the provided key code. 282 * The scroll action will only be performed if the scroll toggle is on. 283 * This method interprets the key code as a mouse scroll and sends 284 * the corresponding {@code VirtualMouseScrollEvent#mYAxisMovement}. 285 286 * @param keyCode The key code representing the mouse scroll action. 287 * Supported keys are: 288 * <ul> 289 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL} 290 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL} 291 * </ul> 292 */ 293 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) performMouseScrollAction(int keyCode)294 private void performMouseScrollAction(int keyCode) { 295 MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from( 296 keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap); 297 float x = 0f; 298 float y = 0f; 299 300 switch (mouseKeyEvent) { 301 case UP_MOVE_OR_SCROLL -> { 302 y = MOUSE_SCROLL_STEP; 303 } 304 case DOWN_MOVE_OR_SCROLL -> { 305 y = -MOUSE_SCROLL_STEP; 306 } 307 case LEFT_MOVE_OR_SCROLL -> { 308 x = MOUSE_SCROLL_STEP; 309 } 310 case RIGHT_MOVE_OR_SCROLL -> { 311 x = -MOUSE_SCROLL_STEP; 312 } 313 default -> { 314 x = 0.0f; 315 y = 0.0f; 316 } 317 } 318 sendVirtualMouseScrollEvent(x, y); 319 if (DEBUG) { 320 Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name() 321 + " for scroll action with axis movement (x=" + x + ", y=" + y + ")"); 322 } 323 } 324 325 /** 326 * Performs a mouse button action based on the provided key code. 327 * This method interprets the key code as a mouse button press and sends 328 * the corresponding press and release events to the virtual mouse. 329 330 * @param keyCode The key code representing the mouse button action. 331 * Supported keys are: 332 * <ul> 333 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_CLICK} (Primary Button) 334 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_CLICK} (Secondary 335 * Button) 336 * </ul> 337 */ 338 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) performMouseButtonAction(int keyCode)339 private void performMouseButtonAction(int keyCode) { 340 MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from( 341 keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap); 342 int buttonCode = switch (mouseKeyEvent) { 343 case LEFT_CLICK -> VirtualMouseButtonEvent.BUTTON_PRIMARY; 344 case RIGHT_CLICK -> VirtualMouseButtonEvent.BUTTON_SECONDARY; 345 default -> VirtualMouseButtonEvent.BUTTON_UNKNOWN; 346 }; 347 if (buttonCode != VirtualMouseButtonEvent.BUTTON_UNKNOWN) { 348 sendVirtualMouseButtonEvent(buttonCode, 349 VirtualMouseButtonEvent.ACTION_BUTTON_PRESS); 350 sendVirtualMouseButtonEvent(buttonCode, 351 VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE); 352 } 353 if (DEBUG) { 354 if (buttonCode == VirtualMouseButtonEvent.BUTTON_UNKNOWN) { 355 Slog.d(LOG_TAG, "Button code is unknown for mouse key event: " 356 + mouseKeyEvent.name()); 357 } else { 358 Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name() 359 + " for button action"); 360 } 361 } 362 } 363 364 /** 365 * Performs a mouse pointer action based on the provided key code. 366 * The method calculates the relative movement of the mouse pointer 367 * and sends the corresponding event to the virtual mouse. 368 * 369 * The UP, DOWN, LEFT, RIGHT pointer actions will only take place for their 370 * respective keys if the scroll toggle is off. 371 * 372 * @param keyCode The key code representing the direction or button press. 373 * Supported keys are: 374 * <ul> 375 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE} 376 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL} 377 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE} 378 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE_OR_SCROLL} 379 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE_OR_SCROLL} 380 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE} 381 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL} 382 * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE} 383 * </ul> 384 */ 385 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) performMousePointerAction(int keyCode)386 private void performMousePointerAction(int keyCode) { 387 float x = 0f; 388 float y = 0f; 389 MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from( 390 keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap); 391 392 switch (mouseKeyEvent) { 393 case DIAGONAL_DOWN_LEFT_MOVE -> { 394 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 395 y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 396 } 397 case DOWN_MOVE_OR_SCROLL -> { 398 if (!mScrollToggleOn) { 399 y = MOUSE_POINTER_MOVEMENT_STEP; 400 } 401 } 402 case DIAGONAL_DOWN_RIGHT_MOVE -> { 403 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 404 y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 405 } 406 case LEFT_MOVE_OR_SCROLL -> { 407 x = -MOUSE_POINTER_MOVEMENT_STEP; 408 } 409 case RIGHT_MOVE_OR_SCROLL -> { 410 x = MOUSE_POINTER_MOVEMENT_STEP; 411 } 412 case DIAGONAL_UP_LEFT_MOVE -> { 413 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 414 y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 415 } 416 case UP_MOVE_OR_SCROLL -> { 417 if (!mScrollToggleOn) { 418 y = -MOUSE_POINTER_MOVEMENT_STEP; 419 } 420 } 421 case DIAGONAL_UP_RIGHT_MOVE -> { 422 x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 423 y = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); 424 } 425 default -> { 426 x = 0.0f; 427 y = 0.0f; 428 } 429 } 430 sendVirtualMouseRelativeEvent(x, y); 431 if (DEBUG) { 432 Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name() 433 + " for relative pointer movement (x=" + x + ", y=" + y + ")"); 434 } 435 } 436 isMouseKey(int keyCode, int deviceId)437 private boolean isMouseKey(int keyCode, int deviceId) { 438 SparseArray<MouseKeyEvent> keyCodeToEnumMap = mDeviceKeyCodeMap.get(deviceId); 439 return keyCodeToEnumMap.contains(keyCode); 440 } 441 isMouseButtonKey(int keyCode, InputDevice inputDevice)442 private boolean isMouseButtonKey(int keyCode, InputDevice inputDevice) { 443 return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCode(inputDevice) 444 || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCode(inputDevice); 445 } 446 isMouseScrollKey(int keyCode, InputDevice inputDevice)447 private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) { 448 return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice) 449 || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice) 450 || keyCode == MouseKeyEvent.LEFT_MOVE_OR_SCROLL.getKeyCode(inputDevice) 451 || keyCode == MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.getKeyCode(inputDevice); 452 } 453 454 /** 455 * Create a virtual mouse using the VirtualDeviceManagerInternal. 456 * 457 * @return The created VirtualMouse. 458 */ 459 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) createVirtualMouse(int displayId)460 private VirtualMouse createVirtualMouse(int displayId) { 461 final VirtualDeviceManagerInternal localVdm = 462 LocalServices.getService(VirtualDeviceManagerInternal.class); 463 mVirtualDevice = localVdm.createVirtualDevice( 464 new VirtualDeviceParams.Builder().setName("Mouse Keys Virtual Device").build()); 465 VirtualMouse virtualMouse = mVirtualDevice.createVirtualMouse( 466 new VirtualMouseConfig.Builder() 467 .setInputDeviceName("Mouse Keys Virtual Mouse") 468 .setAssociatedDisplayId(displayId) 469 .build()); 470 return virtualMouse; 471 } 472 473 /** 474 * Handles key events and forwards mouse key events to the virtual mouse on the handler thread. 475 * 476 * @param event The key event to handle. 477 * @param policyFlags The policy flags associated with the key event. 478 */ 479 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) 480 @Override onKeyEvent(KeyEvent event, int policyFlags)481 public void onKeyEvent(KeyEvent event, int policyFlags) { 482 if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) { 483 mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent", 484 FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags); 485 } 486 487 mHandler.post(() -> { 488 onKeyEventInternal(event, policyFlags); 489 }); 490 } 491 492 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) onKeyEventInternal(KeyEvent event, int policyFlags)493 private void onKeyEventInternal(KeyEvent event, int policyFlags) { 494 boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN; 495 int keyCode = event.getKeyCode(); 496 mActiveInputDeviceId = event.getDeviceId(); 497 InputDevice inputDevice = mInputManager.getInputDevice(mActiveInputDeviceId); 498 499 if (!mDeviceKeyCodeMap.contains(mActiveInputDeviceId)) { 500 initializeDeviceToEnumMap(inputDevice); 501 } 502 503 if (!isMouseKey(keyCode, mActiveInputDeviceId)) { 504 // Pass non-mouse key events to the next handler 505 super.onKeyEvent(event, policyFlags); 506 } else if (isDown) { 507 if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCode(inputDevice)) { 508 mScrollToggleOn = !mScrollToggleOn; 509 if (DEBUG) { 510 Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF")); 511 } 512 } else if (keyCode == MouseKeyEvent.HOLD.getKeyCode(inputDevice)) { 513 sendVirtualMouseButtonEvent( 514 VirtualMouseButtonEvent.BUTTON_PRIMARY, 515 VirtualMouseButtonEvent.ACTION_BUTTON_PRESS 516 ); 517 } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCode(inputDevice)) { 518 sendVirtualMouseButtonEvent( 519 VirtualMouseButtonEvent.BUTTON_PRIMARY, 520 VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE 521 ); 522 } else if (isMouseButtonKey(keyCode, inputDevice)) { 523 performMouseButtonAction(keyCode); 524 } else if (mScrollToggleOn && isMouseScrollKey(keyCode, inputDevice)) { 525 // If the scroll key is pressed down and no other key is active, 526 // set it as the active key and send a message to scroll the pointer 527 if (mActiveScrollKey == KEY_NOT_SET) { 528 mActiveScrollKey = keyCode; 529 mLastTimeKeyActionPerformed = event.getDownTime(); 530 mHandler.sendEmptyMessage(MESSAGE_SCROLL_MOUSE_POINTER); 531 } 532 } else { 533 // This is a directional key. 534 // If the key is pressed down and no other key is active, 535 // set it as the active key and send a message to move the pointer 536 if (mActiveMoveKey == KEY_NOT_SET) { 537 mActiveMoveKey = keyCode; 538 mLastTimeKeyActionPerformed = event.getDownTime(); 539 mHandler.sendEmptyMessage(MESSAGE_MOVE_MOUSE_POINTER); 540 } 541 } 542 } else { 543 // Up event received 544 if (mActiveMoveKey == keyCode) { 545 // If the key is released, and it is the active key, stop moving the pointer 546 mActiveMoveKey = KEY_NOT_SET; 547 mHandler.removeMessages(MESSAGE_MOVE_MOUSE_POINTER); 548 } else if (mActiveScrollKey == keyCode) { 549 // If the key is released, and it is the active key, stop scrolling the pointer 550 mActiveScrollKey = KEY_NOT_SET; 551 mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER); 552 } else { 553 Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode 554 + "', with no matching down event from deviceId = " 555 + event.getDeviceId()); 556 } 557 } 558 } 559 560 /** 561 * Handle messages for moving or scrolling the mouse pointer. 562 * 563 * @param msg The message to handle. 564 * @return True if the message was handled, false otherwise. 565 */ 566 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) 567 @Override handleMessage(Message msg)568 public boolean handleMessage(Message msg) { 569 switch (msg.what) { 570 case MESSAGE_MOVE_MOUSE_POINTER -> 571 handleMouseMessage(msg.getWhen(), mActiveMoveKey, MESSAGE_MOVE_MOUSE_POINTER); 572 case MESSAGE_SCROLL_MOUSE_POINTER -> 573 handleMouseMessage(msg.getWhen(), mActiveScrollKey, 574 MESSAGE_SCROLL_MOUSE_POINTER); 575 default -> { 576 Slog.e(LOG_TAG, "Unexpected message type"); 577 return false; 578 } 579 } 580 return true; 581 } 582 583 /** 584 * Handles mouse-related messages for moving or scrolling the mouse pointer. 585 * This method checks if the specified time interval {@code INTERVAL_MILLIS} has passed since 586 * the last movement or scroll action and performs the corresponding action if necessary. 587 * If there is an active key, the message is rescheduled to be handled again 588 * after the specified {@code INTERVAL_MILLIS}. 589 * 590 * @param currentTime The current time when the message is being handled. 591 * @param activeKey The key code representing the active key. This determines 592 * the direction or type of action to be performed. 593 * @param messageType The type of message to be handled. It can be one of the 594 * following: 595 * <ul> 596 * <li>{@link #MESSAGE_MOVE_MOUSE_POINTER} - for moving the mouse pointer. 597 * <li>{@link #MESSAGE_SCROLL_MOUSE_POINTER} - for scrolling mouse pointer. 598 * </ul> 599 */ 600 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) handleMouseMessage(long currentTime, int activeKey, int messageType)601 public void handleMouseMessage(long currentTime, int activeKey, int messageType) { 602 if (currentTime - mLastTimeKeyActionPerformed >= INTERVAL_MILLIS) { 603 if (messageType == MESSAGE_MOVE_MOUSE_POINTER) { 604 performMousePointerAction(activeKey); 605 } else if (messageType == MESSAGE_SCROLL_MOUSE_POINTER) { 606 performMouseScrollAction(activeKey); 607 } 608 mLastTimeKeyActionPerformed = currentTime; 609 } 610 if (activeKey != KEY_NOT_SET) { 611 // Reschedule the message if the key is still active 612 mHandler.sendEmptyMessageDelayed(messageType, INTERVAL_MILLIS); 613 } 614 } 615 616 @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) 617 @Override onDestroy()618 public void onDestroy() { 619 mHandler.post(() -> { 620 // Clear mouse state 621 mActiveMoveKey = KEY_NOT_SET; 622 mActiveScrollKey = KEY_NOT_SET; 623 mLastTimeKeyActionPerformed = 0; 624 mDeviceKeyCodeMap.clear(); 625 }); 626 627 mHandler.removeCallbacksAndMessages(null); 628 if (mVirtualDevice != null) { 629 mVirtualDevice.close(); 630 } 631 } 632 633 @Override onInputDeviceAdded(int deviceId)634 public void onInputDeviceAdded(int deviceId) { 635 } 636 637 @Override onInputDeviceRemoved(int deviceId)638 public void onInputDeviceRemoved(int deviceId) { 639 mDeviceKeyCodeMap.remove(deviceId); 640 } 641 642 /** 643 * The user can change the keyboard layout from settings at anytime, which would change 644 * key character map for that device. Hence, we should use this callback to 645 * update the key code to enum mapping if there is a change in the physical keyboard detected. 646 * 647 * @param deviceId The id of the input device that changed. 648 */ 649 @Override onInputDeviceChanged(int deviceId)650 public void onInputDeviceChanged(int deviceId) { 651 InputDevice inputDevice = mInputManager.getInputDevice(deviceId); 652 // Update the enum mapping only if input device that changed is a keyboard 653 if (inputDevice.isFullKeyboard() && !mDeviceKeyCodeMap.contains(deviceId)) { 654 initializeDeviceToEnumMap(inputDevice); 655 } 656 } 657 } 658