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