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