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
17package com.android.commands.input;
18
19import android.hardware.input.InputManager;
20import android.os.SystemClock;
21import android.util.Log;
22import android.view.InputDevice;
23import android.view.KeyCharacterMap;
24import android.view.KeyEvent;
25import android.view.MotionEvent;
26
27import java.util.HashMap;
28import java.util.Map;
29
30/**
31 * Command that sends key events to the device, either by their keycode, or by
32 * desired character output.
33 */
34
35public class Input {
36    private static final String TAG = "Input";
37    private static final String INVALID_ARGUMENTS = "Error: Invalid arguments for command: ";
38
39    private static final Map<String, Integer> SOURCES = new HashMap<String, Integer>() {{
40        put("keyboard", InputDevice.SOURCE_KEYBOARD);
41        put("dpad", InputDevice.SOURCE_DPAD);
42        put("gamepad", InputDevice.SOURCE_GAMEPAD);
43        put("touchscreen", InputDevice.SOURCE_TOUCHSCREEN);
44        put("mouse", InputDevice.SOURCE_MOUSE);
45        put("stylus", InputDevice.SOURCE_STYLUS);
46        put("trackball", InputDevice.SOURCE_TRACKBALL);
47        put("touchpad", InputDevice.SOURCE_TOUCHPAD);
48        put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION);
49        put("joystick", InputDevice.SOURCE_JOYSTICK);
50    }};
51
52
53    /**
54     * Command-line entry point.
55     *
56     * @param args The command-line arguments
57     */
58    public static void main(String[] args) {
59        (new Input()).run(args);
60    }
61
62    private void run(String[] args) {
63        if (args.length < 1) {
64            showUsage();
65            return;
66        }
67
68        int index = 0;
69        String command = args[index];
70        int inputSource = InputDevice.SOURCE_UNKNOWN;
71        if (SOURCES.containsKey(command)) {
72            inputSource = SOURCES.get(command);
73            index++;
74            command = args[index];
75        }
76        final int length = args.length - index;
77
78        try {
79            if (command.equals("text")) {
80                if (length == 2) {
81                    inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
82                    sendText(inputSource, args[index+1]);
83                    return;
84                }
85            } else if (command.equals("keyevent")) {
86                if (length >= 2) {
87                    final boolean longpress = "--longpress".equals(args[index + 1]);
88                    final int start = longpress ? index + 2 : index + 1;
89                    inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
90                    if (length > start) {
91                        for (int i = start; i < length; i++) {
92                            int keyCode = KeyEvent.keyCodeFromString(args[i]);
93                            if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
94                                keyCode = KeyEvent.keyCodeFromString("KEYCODE_" + args[i]);
95                            }
96                            sendKeyEvent(inputSource, keyCode, longpress);
97                        }
98                        return;
99                    }
100                }
101            } else if (command.equals("tap")) {
102                if (length == 3) {
103                    inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
104                    sendTap(inputSource, Float.parseFloat(args[index+1]),
105                            Float.parseFloat(args[index+2]));
106                    return;
107                }
108            } else if (command.equals("swipe")) {
109                int duration = -1;
110                inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN);
111                switch (length) {
112                    case 6:
113                        duration = Integer.parseInt(args[index+5]);
114                    case 5:
115                        sendSwipe(inputSource,
116                                Float.parseFloat(args[index+1]), Float.parseFloat(args[index+2]),
117                                Float.parseFloat(args[index+3]), Float.parseFloat(args[index+4]),
118                                duration);
119                        return;
120                }
121            } else if (command.equals("press")) {
122                inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
123                if (length == 1) {
124                    sendTap(inputSource, 0.0f, 0.0f);
125                    return;
126                }
127            } else if (command.equals("roll")) {
128                inputSource = getSource(inputSource, InputDevice.SOURCE_TRACKBALL);
129                if (length == 3) {
130                    sendMove(inputSource, Float.parseFloat(args[index+1]),
131                            Float.parseFloat(args[index+2]));
132                    return;
133                }
134            } else {
135                System.err.println("Error: Unknown command: " + command);
136                showUsage();
137                return;
138            }
139        } catch (NumberFormatException ex) {
140        }
141        System.err.println(INVALID_ARGUMENTS + command);
142        showUsage();
143    }
144
145    /**
146     * Convert the characters of string text into key event's and send to
147     * device.
148     *
149     * @param text is a string of characters you want to input to the device.
150     */
151    private void sendText(int source, String text) {
152
153        StringBuffer buff = new StringBuffer(text);
154
155        boolean escapeFlag = false;
156        for (int i=0; i<buff.length(); i++) {
157            if (escapeFlag) {
158                escapeFlag = false;
159                if (buff.charAt(i) == 's') {
160                    buff.setCharAt(i, ' ');
161                    buff.deleteCharAt(--i);
162                }
163            }
164            if (buff.charAt(i) == '%') {
165                escapeFlag = true;
166            }
167        }
168
169        char[] chars = buff.toString().toCharArray();
170
171        KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
172        KeyEvent[] events = kcm.getEvents(chars);
173        for(int i = 0; i < events.length; i++) {
174            KeyEvent e = events[i];
175            if (source != e.getSource()) {
176                e.setSource(source);
177            }
178            injectKeyEvent(e);
179        }
180    }
181
182    private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
183        long now = SystemClock.uptimeMillis();
184        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
185                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
186        if (longpress) {
187            injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
188                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
189                    inputSource));
190        }
191        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
192                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
193    }
194
195    private void sendTap(int inputSource, float x, float y) {
196        long now = SystemClock.uptimeMillis();
197        injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
198        injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x, y, 0.0f);
199    }
200
201    private void sendSwipe(int inputSource, float x1, float y1, float x2, float y2, int duration) {
202        if (duration < 0) {
203            duration = 300;
204        }
205        long now = SystemClock.uptimeMillis();
206        injectMotionEvent(inputSource, MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
207        long startTime = now;
208        long endTime = startTime + duration;
209        while (now < endTime) {
210            long elapsedTime = now - startTime;
211            float alpha = (float) elapsedTime / duration;
212            injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, lerp(x1, x2, alpha),
213                    lerp(y1, y2, alpha), 1.0f);
214            now = SystemClock.uptimeMillis();
215        }
216        injectMotionEvent(inputSource, MotionEvent.ACTION_UP, now, x2, y2, 0.0f);
217    }
218
219    /**
220     * Sends a simple zero-pressure move event.
221     *
222     * @param inputSource the InputDevice.SOURCE_* sending the input event
223     * @param dx change in x coordinate due to move
224     * @param dy change in y coordinate due to move
225     */
226    private void sendMove(int inputSource, float dx, float dy) {
227        long now = SystemClock.uptimeMillis();
228        injectMotionEvent(inputSource, MotionEvent.ACTION_MOVE, now, dx, dy, 0.0f);
229    }
230
231    private void injectKeyEvent(KeyEvent event) {
232        Log.i(TAG, "injectKeyEvent: " + event);
233        InputManager.getInstance().injectInputEvent(event,
234                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
235    }
236
237    /**
238     * Builds a MotionEvent and injects it into the event stream.
239     *
240     * @param inputSource the InputDevice.SOURCE_* sending the input event
241     * @param action the MotionEvent.ACTION_* for the event
242     * @param when the value of SystemClock.uptimeMillis() at which the event happened
243     * @param x x coordinate of event
244     * @param y y coordinate of event
245     * @param pressure pressure of event
246     */
247    private void injectMotionEvent(int inputSource, int action, long when, float x, float y, float pressure) {
248        final float DEFAULT_SIZE = 1.0f;
249        final int DEFAULT_META_STATE = 0;
250        final float DEFAULT_PRECISION_X = 1.0f;
251        final float DEFAULT_PRECISION_Y = 1.0f;
252        final int DEFAULT_DEVICE_ID = 0;
253        final int DEFAULT_EDGE_FLAGS = 0;
254        MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, DEFAULT_SIZE,
255                DEFAULT_META_STATE, DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID,
256                DEFAULT_EDGE_FLAGS);
257        event.setSource(inputSource);
258        Log.i(TAG, "injectMotionEvent: " + event);
259        InputManager.getInstance().injectInputEvent(event,
260                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
261    }
262
263    private static final float lerp(float a, float b, float alpha) {
264        return (b - a) * alpha + a;
265    }
266
267    private static final int getSource(int inputSource, int defaultSource) {
268        return inputSource == InputDevice.SOURCE_UNKNOWN ? defaultSource : inputSource;
269    }
270
271    private void showUsage() {
272        System.err.println("Usage: input [<source>] <command> [<arg>...]");
273        System.err.println();
274        System.err.println("The sources are: ");
275        for (String src : SOURCES.keySet()) {
276            System.err.println("      " + src);
277        }
278        System.err.println();
279        System.err.println("The commands and default sources are:");
280        System.err.println("      text <string> (Default: touchscreen)");
281        System.err.println("      keyevent [--longpress] <key code number or name> ..."
282                + " (Default: keyboard)");
283        System.err.println("      tap <x> <y> (Default: touchscreen)");
284        System.err.println("      swipe <x1> <y1> <x2> <y2> [duration(ms)]"
285                + " (Default: touchscreen)");
286        System.err.println("      press (Default: trackball)");
287        System.err.println("      roll <dx> <dy> (Default: trackball)");
288    }
289}
290