1 /* 2 * Copyright (C) 2007 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.view.Display.DEFAULT_DISPLAY; 20 import static android.view.Display.INVALID_DISPLAY; 21 import static android.view.KeyEvent.KEYCODE_ALT_LEFT; 22 import static android.view.KeyEvent.KEYCODE_ALT_RIGHT; 23 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT; 24 import static android.view.KeyEvent.KEYCODE_CTRL_RIGHT; 25 import static android.view.KeyEvent.KEYCODE_META_LEFT; 26 import static android.view.KeyEvent.KEYCODE_META_RIGHT; 27 import static android.view.KeyEvent.KEYCODE_SHIFT_LEFT; 28 import static android.view.KeyEvent.KEYCODE_SHIFT_RIGHT; 29 import static android.view.KeyEvent.META_ALT_LEFT_ON; 30 import static android.view.KeyEvent.META_ALT_ON; 31 import static android.view.KeyEvent.META_ALT_RIGHT_ON; 32 import static android.view.KeyEvent.META_CTRL_LEFT_ON; 33 import static android.view.KeyEvent.META_CTRL_ON; 34 import static android.view.KeyEvent.META_CTRL_RIGHT_ON; 35 import static android.view.KeyEvent.META_META_LEFT_ON; 36 import static android.view.KeyEvent.META_META_ON; 37 import static android.view.KeyEvent.META_META_RIGHT_ON; 38 import static android.view.KeyEvent.META_SHIFT_LEFT_ON; 39 import static android.view.KeyEvent.META_SHIFT_ON; 40 import static android.view.KeyEvent.META_SHIFT_RIGHT_ON; 41 42 import static java.util.Collections.unmodifiableMap; 43 44 import android.hardware.input.InputManager; 45 import android.os.ShellCommand; 46 import android.os.SystemClock; 47 import android.util.ArrayMap; 48 import android.util.IntArray; 49 import android.view.InputDevice; 50 import android.view.KeyCharacterMap; 51 import android.view.KeyEvent; 52 import android.view.MotionEvent; 53 import android.view.ViewConfiguration; 54 55 import java.io.PrintWriter; 56 import java.util.Map; 57 58 /** 59 * Command that sends input events to the device. 60 */ 61 62 public class InputShellCommand extends ShellCommand { 63 private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: "; 64 private static final String INVALID_DISPLAY_ARGUMENTS = 65 "Error: Invalid arguments for display ID."; 66 private static final int DEFAULT_DEVICE_ID = 0; 67 private static final float DEFAULT_PRESSURE = 1.0f; 68 private static final float NO_PRESSURE = 0.0f; 69 private static final float DEFAULT_SIZE = 1.0f; 70 private static final int DEFAULT_META_STATE = 0; 71 private static final float DEFAULT_PRECISION_X = 1.0f; 72 private static final float DEFAULT_PRECISION_Y = 1.0f; 73 private static final int DEFAULT_EDGE_FLAGS = 0; 74 private static final int DEFAULT_BUTTON_STATE = 0; 75 private static final int DEFAULT_FLAGS = 0; 76 77 /** Modifier key to meta state */ 78 private static final Map<Integer, Integer> MODIFIER; 79 static { 80 final Map<Integer, Integer> map = new ArrayMap<>(); map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON)81 map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON); map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON)82 map.put(KEYCODE_CTRL_RIGHT, META_CTRL_RIGHT_ON | META_CTRL_ON); map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON)83 map.put(KEYCODE_ALT_LEFT, META_ALT_LEFT_ON | META_ALT_ON); map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON)84 map.put(KEYCODE_ALT_RIGHT, META_ALT_RIGHT_ON | META_ALT_ON); map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON)85 map.put(KEYCODE_SHIFT_LEFT, META_SHIFT_LEFT_ON | META_SHIFT_ON); map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON)86 map.put(KEYCODE_SHIFT_RIGHT, META_SHIFT_RIGHT_ON | META_SHIFT_ON); map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON)87 map.put(KEYCODE_META_LEFT, META_META_LEFT_ON | META_META_ON); map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON)88 map.put(KEYCODE_META_RIGHT, META_META_RIGHT_ON | META_META_ON); 89 90 MODIFIER = unmodifiableMap(map); 91 } 92 93 /** String to device source */ 94 private static final Map<String, Integer> SOURCES; 95 static { 96 final Map<String, Integer> map = new ArrayMap<>(); 97 map.put("keyboard", InputDevice.SOURCE_KEYBOARD); 98 map.put("dpad", InputDevice.SOURCE_DPAD); 99 map.put("gamepad", InputDevice.SOURCE_GAMEPAD); 100 map.put("touchscreen", InputDevice.SOURCE_TOUCHSCREEN); 101 map.put("mouse", InputDevice.SOURCE_MOUSE); 102 map.put("stylus", InputDevice.SOURCE_STYLUS); 103 map.put("trackball", InputDevice.SOURCE_TRACKBALL); 104 map.put("touchpad", InputDevice.SOURCE_TOUCHPAD); 105 map.put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION); 106 map.put("joystick", InputDevice.SOURCE_JOYSTICK); 107 108 SOURCES = unmodifiableMap(map); 109 } 110 injectKeyEvent(KeyEvent event)111 private void injectKeyEvent(KeyEvent event) { 112 InputManager.getInstance().injectInputEvent(event, 113 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); 114 } 115 getInputDeviceId(int inputSource)116 private int getInputDeviceId(int inputSource) { 117 int[] devIds = InputDevice.getDeviceIds(); 118 for (int devId : devIds) { 119 InputDevice inputDev = InputDevice.getDevice(devId); 120 if (inputDev.supportsSource(inputSource)) { 121 return devId; 122 } 123 } 124 return DEFAULT_DEVICE_ID; 125 } 126 getDisplayId()127 private int getDisplayId() { 128 String displayArg = getNextArgRequired(); 129 if ("INVALID_DISPLAY".equalsIgnoreCase(displayArg)) { 130 return INVALID_DISPLAY; 131 } else if ("DEFAULT_DISPLAY".equalsIgnoreCase(displayArg)) { 132 return DEFAULT_DISPLAY; 133 } else { 134 try { 135 final int displayId = Integer.parseInt(displayArg); 136 if (displayId == INVALID_DISPLAY) { 137 return INVALID_DISPLAY; 138 } 139 return Math.max(displayId, 0); 140 } catch (NumberFormatException e) { 141 throw new IllegalArgumentException(INVALID_DISPLAY_ARGUMENTS); 142 } 143 } 144 } 145 146 /** 147 * Builds a MotionEvent and injects it into the event stream. 148 * 149 * @param inputSource the InputDevice.SOURCE_* sending the input event 150 * @param action the MotionEvent.ACTION_* for the event 151 * @param downTime the value of the ACTION_DOWN event happened 152 * @param when the value of SystemClock.uptimeMillis() at which the event happened 153 * @param x x coordinate of event 154 * @param y y coordinate of event 155 * @param pressure pressure of event 156 */ injectMotionEvent(int inputSource, int action, long downTime, long when, float x, float y, float pressure, int displayId)157 private void injectMotionEvent(int inputSource, int action, long downTime, long when, 158 float x, float y, float pressure, int displayId) { 159 final int pointerCount = 1; 160 MotionEvent.PointerProperties[] pointerProperties = 161 new MotionEvent.PointerProperties[pointerCount]; 162 MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount]; 163 for (int i = 0; i < pointerCount; i++) { 164 pointerProperties[i] = new MotionEvent.PointerProperties(); 165 pointerProperties[i].id = i; 166 pointerProperties[i].toolType = getToolType(inputSource); 167 pointerCoords[i] = new MotionEvent.PointerCoords(); 168 pointerCoords[i].x = x; 169 pointerCoords[i].y = y; 170 pointerCoords[i].pressure = pressure; 171 pointerCoords[i].size = DEFAULT_SIZE; 172 } 173 if (displayId == INVALID_DISPLAY 174 && (inputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) { 175 displayId = DEFAULT_DISPLAY; 176 } 177 MotionEvent event = MotionEvent.obtain(downTime, when, action, pointerCount, 178 pointerProperties, pointerCoords, DEFAULT_META_STATE, DEFAULT_BUTTON_STATE, 179 DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, getInputDeviceId(inputSource), 180 DEFAULT_EDGE_FLAGS, inputSource, displayId, DEFAULT_FLAGS); 181 InputManager.getInstance().injectInputEvent(event, 182 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); 183 } 184 lerp(float a, float b, float alpha)185 private float lerp(float a, float b, float alpha) { 186 return (b - a) * alpha + a; 187 } 188 getSource(int inputSource, int defaultSource)189 private int getSource(int inputSource, int defaultSource) { 190 return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource; 191 } 192 getToolType(int inputSource)193 private int getToolType(int inputSource) { 194 switch(inputSource) { 195 case InputDevice.SOURCE_MOUSE: 196 case InputDevice.SOURCE_MOUSE_RELATIVE: 197 case InputDevice.SOURCE_TRACKBALL: 198 return MotionEvent.TOOL_TYPE_MOUSE; 199 200 case InputDevice.SOURCE_STYLUS: 201 case InputDevice.SOURCE_BLUETOOTH_STYLUS: 202 return MotionEvent.TOOL_TYPE_STYLUS; 203 204 case InputDevice.SOURCE_TOUCHPAD: 205 case InputDevice.SOURCE_TOUCHSCREEN: 206 case InputDevice.SOURCE_TOUCH_NAVIGATION: 207 return MotionEvent.TOOL_TYPE_FINGER; 208 } 209 return MotionEvent.TOOL_TYPE_UNKNOWN; 210 } 211 212 @Override onCommand(String cmd)213 public final int onCommand(String cmd) { 214 String arg = cmd; 215 int inputSource = InputDevice.SOURCE_UNKNOWN; 216 // Get source (optional). 217 if (SOURCES.containsKey(arg)) { 218 inputSource = SOURCES.get(arg); 219 arg = getNextArgRequired(); 220 } 221 222 // Get displayId (optional). 223 int displayId = INVALID_DISPLAY; 224 if ("-d".equals(arg)) { 225 displayId = getDisplayId(); 226 arg = getNextArgRequired(); 227 } 228 229 try { 230 if ("text".equals(arg)) { 231 runText(inputSource, displayId); 232 } else if ("keyevent".equals(arg)) { 233 runKeyEvent(inputSource, displayId); 234 } else if ("tap".equals(arg)) { 235 runTap(inputSource, displayId); 236 } else if ("swipe".equals(arg)) { 237 runSwipe(inputSource, displayId); 238 } else if ("draganddrop".equals(arg)) { 239 runDragAndDrop(inputSource, displayId); 240 } else if ("press".equals(arg)) { 241 runPress(inputSource, displayId); 242 } else if ("roll".equals(arg)) { 243 runRoll(inputSource, displayId); 244 } else if ("motionevent".equals(arg)) { 245 runMotionEvent(inputSource, displayId); 246 } else if ("keycombination".equals(arg)) { 247 runKeyCombination(inputSource, displayId); 248 } else { 249 handleDefaultCommands(arg); 250 } 251 } catch (NumberFormatException ex) { 252 throw new IllegalArgumentException(INVALID_ARGUMENTS + arg); 253 } 254 return 0; 255 } 256 257 @Override onHelp()258 public final void onHelp() { 259 try (PrintWriter out = getOutPrintWriter();) { 260 out.println("Usage: input [<source>] [-d DISPLAY_ID] <command> [<arg>...]"); 261 out.println(); 262 out.println("The sources are: "); 263 for (String src : SOURCES.keySet()) { 264 out.println(" " + src); 265 } 266 out.println(); 267 out.printf("-d: specify the display ID.\n (Default: %d for key event, " 268 + "%d for motion event if not specified.)", 269 INVALID_DISPLAY, DEFAULT_DISPLAY); 270 out.println(); 271 out.println("The commands and default sources are:"); 272 out.println(" text <string> (Default: touchscreen)"); 273 out.println(" keyevent [--longpress|--doubletap] <key code number or name> ..." 274 + " (Default: keyboard)"); 275 out.println(" tap <x> <y> (Default: touchscreen)"); 276 out.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]" 277 + " (Default: touchscreen)"); 278 out.println(" draganddrop <x1> <y1> <x2> <y2> [duration(ms)]" 279 + " (Default: touchscreen)"); 280 out.println(" press (Default: trackball)"); 281 out.println(" roll <dx> <dy> (Default: trackball)"); 282 out.println(" motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)"); 283 out.println(" keycombination [-t duration(ms)] <key code 1> <key code 2> ..." 284 + " (Default: keyboard, the key order is important here.)"); 285 } 286 } 287 runText(int inputSource, int displayId)288 private void runText(int inputSource, int displayId) { 289 inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD); 290 sendText(inputSource, getNextArgRequired(), displayId); 291 } 292 293 /** 294 * Convert the characters of string text into key event's and send to 295 * device. 296 * 297 * @param text is a string of characters you want to input to the device. 298 */ sendText(int source, final String text, int displayId)299 private void sendText(int source, final String text, int displayId) { 300 final StringBuilder buff = new StringBuilder(text); 301 boolean escapeFlag = false; 302 for (int i = 0; i < buff.length(); i++) { 303 if (escapeFlag) { 304 escapeFlag = false; 305 if (buff.charAt(i) == 's') { 306 buff.setCharAt(i, ' '); 307 buff.deleteCharAt(--i); 308 } 309 } 310 if (buff.charAt(i) == '%') { 311 escapeFlag = true; 312 } 313 } 314 315 final char[] chars = buff.toString().toCharArray(); 316 final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); 317 final KeyEvent[] events = kcm.getEvents(chars); 318 for (int i = 0; i < events.length; i++) { 319 KeyEvent e = events[i]; 320 if (source != e.getSource()) { 321 e.setSource(source); 322 } 323 e.setDisplayId(displayId); 324 injectKeyEvent(e); 325 } 326 } 327 runKeyEvent(int inputSource, int displayId)328 private void runKeyEvent(int inputSource, int displayId) { 329 String arg = getNextArgRequired(); 330 final boolean longpress = "--longpress".equals(arg); 331 if (longpress) { 332 arg = getNextArgRequired(); 333 } else { 334 final boolean doubleTap = "--doubletap".equals(arg); 335 if (doubleTap) { 336 arg = getNextArgRequired(); 337 final int keycode = KeyEvent.keyCodeFromString(arg); 338 sendKeyDoubleTap(inputSource, keycode, displayId); 339 return; 340 } 341 } 342 343 do { 344 final int keycode = KeyEvent.keyCodeFromString(arg); 345 sendKeyEvent(inputSource, keycode, longpress, displayId); 346 } while ((arg = getNextArg()) != null); 347 } 348 sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId)349 private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) { 350 final long now = SystemClock.uptimeMillis(); 351 352 KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */, 353 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 354 inputSource); 355 event.setDisplayId(displayId); 356 357 injectKeyEvent(event); 358 if (longpress) { 359 sleep(ViewConfiguration.getLongPressTimeout()); 360 // Some long press behavior would check the event time, we set a new event time here. 361 final long nextEventTime = now + ViewConfiguration.getLongPressTimeout(); 362 injectKeyEvent(KeyEvent.changeTimeRepeat(event, nextEventTime, 1 /* repeatCount */, 363 KeyEvent.FLAG_LONG_PRESS)); 364 } 365 injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); 366 } 367 sendKeyDoubleTap(int inputSource, int keyCode, int displayId)368 private void sendKeyDoubleTap(int inputSource, int keyCode, int displayId) { 369 sendKeyEvent(inputSource, keyCode, false, displayId); 370 sleep(ViewConfiguration.getDoubleTapMinTime()); 371 sendKeyEvent(inputSource, keyCode, false, displayId); 372 } 373 runTap(int inputSource, int displayId)374 private void runTap(int inputSource, int displayId) { 375 inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); 376 sendTap(inputSource, Float.parseFloat(getNextArgRequired()), 377 Float.parseFloat(getNextArgRequired()), displayId); 378 } 379 sendTap(int inputSource, float x, float y, int displayId)380 private void sendTap(int inputSource, float x, float y, int displayId) { 381 final long now = SystemClock.uptimeMillis(); 382 injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, now, x, y, 1.0f, 383 displayId); 384 injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, now, x, y, 0.0f, displayId); 385 } 386 runPress(int inputSource, int displayId)387 private void runPress(int inputSource, int displayId) { 388 inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL); 389 sendTap(inputSource, 0.0f, 0.0f, displayId); 390 } 391 runSwipe(int inputSource, int displayId)392 private void runSwipe(int inputSource, int displayId) { 393 inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); 394 sendSwipe(inputSource, displayId, false); 395 } 396 sendSwipe(int inputSource, int displayId, boolean isDragDrop)397 private void sendSwipe(int inputSource, int displayId, boolean isDragDrop) { 398 // Parse two points and duration. 399 final float x1 = Float.parseFloat(getNextArgRequired()); 400 final float y1 = Float.parseFloat(getNextArgRequired()); 401 final float x2 = Float.parseFloat(getNextArgRequired()); 402 final float y2 = Float.parseFloat(getNextArgRequired()); 403 String durationArg = getNextArg(); 404 int duration = durationArg != null ? Integer.parseInt(durationArg) : -1; 405 if (duration < 0) { 406 duration = 300; 407 } 408 409 final long down = SystemClock.uptimeMillis(); 410 injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, down, down, x1, y1, 1.0f, 411 displayId); 412 if (isDragDrop) { 413 // long press until drag start. 414 sleep(ViewConfiguration.getLongPressTimeout()); 415 } 416 long now = SystemClock.uptimeMillis(); 417 final long endTime = down + duration; 418 while (now < endTime) { 419 final long elapsedTime = now - down; 420 final float alpha = (float) elapsedTime / duration; 421 injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, down, now, 422 lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f, displayId); 423 now = SystemClock.uptimeMillis(); 424 } 425 injectMotionEvent(inputSource, MotionEvent.ACTION_UP, down, now, x2, y2, 0.0f, 426 displayId); 427 } 428 runDragAndDrop(int inputSource, int displayId)429 private void runDragAndDrop(int inputSource, int displayId) { 430 inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); 431 sendSwipe(inputSource, displayId, true); 432 } 433 runRoll(int inputSource, int displayId)434 private void runRoll(int inputSource, int displayId) { 435 inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL); 436 sendMove(inputSource, Float.parseFloat(getNextArgRequired()), 437 Float.parseFloat(getNextArgRequired()), displayId); 438 } 439 440 /** 441 * Sends a simple zero-pressure move event. 442 * 443 * @param inputSource the InputDevice.SOURCE_* sending the input event 444 * @param dx change in x coordinate due to move 445 * @param dy change in y coordinate due to move 446 */ sendMove(int inputSource, float dx, float dy, int displayId)447 private void sendMove(int inputSource, float dx, float dy, int displayId) { 448 final long now = SystemClock.uptimeMillis(); 449 injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, now, dx, dy, 0.0f, 450 displayId); 451 } 452 getAction()453 private int getAction() { 454 String actionString = getNextArgRequired(); 455 switch (actionString.toUpperCase()) { 456 case "DOWN": 457 return MotionEvent.ACTION_DOWN; 458 case "UP": 459 return MotionEvent.ACTION_UP; 460 case "MOVE": 461 return MotionEvent.ACTION_MOVE; 462 case "CANCEL": 463 return MotionEvent.ACTION_CANCEL; 464 default: 465 throw new IllegalArgumentException("Unknown action: " + actionString); 466 } 467 } 468 runMotionEvent(int inputSource, int displayId)469 private void runMotionEvent(int inputSource, int displayId) { 470 inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); 471 int action = getAction(); 472 float x = 0, y = 0; 473 if (action == MotionEvent.ACTION_DOWN 474 || action == MotionEvent.ACTION_MOVE 475 || action == MotionEvent.ACTION_UP) { 476 x = Float.parseFloat(getNextArgRequired()); 477 y = Float.parseFloat(getNextArgRequired()); 478 } else { 479 // For ACTION_CANCEL, the positions are optional 480 String xString = getNextArg(); 481 String yString = getNextArg(); 482 if (xString != null && yString != null) { 483 x = Float.parseFloat(xString); 484 y = Float.parseFloat(yString); 485 } 486 } 487 488 sendMotionEvent(inputSource, action, x, y, displayId); 489 } 490 sendMotionEvent(int inputSource, int action, float x, float y, int displayId)491 private void sendMotionEvent(int inputSource, int action, float x, float y, 492 int displayId) { 493 float pressure = NO_PRESSURE; 494 495 if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { 496 pressure = DEFAULT_PRESSURE; 497 } 498 499 final long now = SystemClock.uptimeMillis(); 500 injectMotionEvent(inputSource, action, now, now, x, y, pressure, displayId); 501 } 502 runKeyCombination(int inputSource, int displayId)503 private void runKeyCombination(int inputSource, int displayId) { 504 String arg = getNextArgRequired(); 505 506 // Get duration (optional). 507 long duration = 0; 508 if ("-t".equals(arg)) { 509 arg = getNextArgRequired(); 510 duration = Integer.parseInt(arg); 511 arg = getNextArgRequired(); 512 } 513 514 IntArray keyCodes = new IntArray(); 515 while (arg != null) { 516 final int keyCode = KeyEvent.keyCodeFromString(arg); 517 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { 518 throw new IllegalArgumentException("Unknown keycode: " + arg); 519 } 520 keyCodes.add(keyCode); 521 arg = getNextArg(); 522 } 523 524 // At least 2 keys. 525 if (keyCodes.size() < 2) { 526 throw new IllegalArgumentException("keycombination requires at least 2 keycodes"); 527 } 528 529 sendKeyCombination(inputSource, keyCodes, displayId, duration); 530 } 531 injectKeyEventAsync(KeyEvent event)532 private void injectKeyEventAsync(KeyEvent event) { 533 InputManager.getInstance().injectInputEvent(event, 534 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 535 } 536 sendKeyCombination(int inputSource, IntArray keyCodes, int displayId, long duration)537 private void sendKeyCombination(int inputSource, IntArray keyCodes, int displayId, 538 long duration) { 539 final long now = SystemClock.uptimeMillis(); 540 final int count = keyCodes.size(); 541 final KeyEvent[] events = new KeyEvent[count]; 542 int metaState = 0; 543 for (int i = 0; i < count; i++) { 544 final int keyCode = keyCodes.get(i); 545 final KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 546 metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 547 inputSource); 548 event.setDisplayId(displayId); 549 events[i] = event; 550 // The order is important here, metaState could be updated and applied to the next key. 551 metaState |= MODIFIER.getOrDefault(keyCode, 0); 552 } 553 554 for (KeyEvent event: events) { 555 // Use async inject so interceptKeyBeforeQueueing or interceptKeyBeforeDispatching could 556 // handle keys. 557 injectKeyEventAsync(event); 558 } 559 560 sleep(duration); 561 562 for (KeyEvent event: events) { 563 final int keyCode = event.getKeyCode(); 564 final KeyEvent upEvent = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 565 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, 566 inputSource); 567 injectKeyEventAsync(upEvent); 568 metaState &= ~MODIFIER.getOrDefault(keyCode, 0); 569 } 570 } 571 572 /** 573 * Puts the thread to sleep for the provided time. 574 * 575 * @param milliseconds The time to sleep in milliseconds. 576 */ sleep(long milliseconds)577 private void sleep(long milliseconds) { 578 try { 579 Thread.sleep(milliseconds); 580 } catch (InterruptedException e) { 581 throw new RuntimeException(e); 582 } 583 } 584 } 585