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