2 * Copyright 2009, The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.commands.monkey;
18 import android.util.Log;
19 import android.view.KeyEvent;
20 import android.view.MotionEvent;
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.io.PrintWriter;
26 import java.lang.Integer;
27 import java.lang.NumberFormatException;
28 import java.net.InetAddress;
29 import java.net.ServerSocket;
30 import java.net.Socket;
31 import java.util.ArrayList;
32 import java.util.HashMap;
34 import java.util.List;
35 import java.util.StringTokenizer;
38 * An Event source for getting Monkey Network Script commands from
41 public class MonkeySourceNetwork implements MonkeyEventSource {
42 private static final String TAG = "MonkeyStub";
44 private interface MonkeyCommand {
45 MonkeyEvent translateCommand(List<String> command);
49 * Command to simulate closing and opening the keyboard.
51 private static class FlipCommand implements MonkeyCommand {
54 public MonkeyEvent translateCommand(List<String> command) {
55 if (command.size() > 1) {
56 String direction = command.get(1);
57 if ("open".equals(direction)) {
58 return new MonkeyFlipEvent(true);
59 } else if ("close".equals(direction)) {
60 return new MonkeyFlipEvent(false);
68 * Command to send touch events to the input system.
70 private static class TouchCommand implements MonkeyCommand {
71 // touch [down|up|move] [x] [y]
75 public MonkeyEvent translateCommand(List<String> command) {
76 if (command.size() == 4) {
77 String actionName = command.get(1);
81 x = Integer.parseInt(command.get(2));
82 y = Integer.parseInt(command.get(3));
83 } catch (NumberFormatException e) {
84 // Ok, it wasn't a number
85 Log.e(TAG, "Got something that wasn't a number", e);
89 // figure out the action
91 if ("down".equals(actionName)) {
92 action = MotionEvent.ACTION_DOWN;
93 } else if ("up".equals(actionName)) {
94 action = MotionEvent.ACTION_UP;
95 } else if ("move".equals(actionName)) {
96 action = MotionEvent.ACTION_MOVE;
99 Log.e(TAG, "Got a bad action: " + actionName);
103 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
104 -1, action, x, y, 0);
112 * Command to send Trackball events to the input system.
114 private static class TrackballCommand implements MonkeyCommand {
115 // trackball [dx] [dy]
116 // trackball 1 0 -- move right
117 // trackball -1 0 -- move left
118 public MonkeyEvent translateCommand(List<String> command) {
119 if (command.size() == 3) {
123 dx = Integer.parseInt(command.get(1));
124 dy = Integer.parseInt(command.get(2));
125 } catch (NumberFormatException e) {
126 // Ok, it wasn't a number
127 Log.e(TAG, "Got something that wasn't a number", e);
130 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
131 MotionEvent.ACTION_MOVE, dx, dy, 0);
139 * Command to send Key events to the input system.
141 private static class KeyCommand implements MonkeyCommand {
142 // key [down|up] [keycode]
145 public MonkeyEvent translateCommand(List<String> command) {
146 if (command.size() == 3) {
148 String keyName = command.get(2);
150 keyCode = Integer.parseInt(keyName);
151 } catch (NumberFormatException e) {
152 // Ok, it wasn't a number, see if we have a
153 // keycode name for it
154 keyCode = MonkeySourceRandom.getKeyCode(keyName);
156 // OK, one last ditch effort to find a match.
157 // Build the KEYCODE_STRING from the string
158 // we've been given and see if that key
159 // exists. This would allow you to do "key
160 // down menu", for example.
161 keyCode = MonkeySourceRandom.getKeyCode("KEYCODE_" + keyName.toUpperCase());
163 // Ok, you gave us something bad.
164 Log.e(TAG, "Can't find keyname: " + keyName);
169 Log.d(TAG, "keycode: " + keyCode);
171 if ("down".equals(command.get(1))) {
172 action = KeyEvent.ACTION_DOWN;
173 } else if ("up".equals(command.get(1))) {
174 action = KeyEvent.ACTION_UP;
177 Log.e(TAG, "got unknown action.");
180 return new MonkeyKeyEvent(action, keyCode);
187 * Command to put the Monkey to sleep.
189 private static class SleepCommand implements MonkeyCommand {
191 public MonkeyEvent translateCommand(List<String> command) {
192 if (command.size() == 2) {
194 String sleepStr = command.get(1);
196 sleep = Integer.parseInt(sleepStr);
197 } catch (NumberFormatException e) {
198 Log.e(TAG, "Not a number: " + sleepStr, e);
200 return new MonkeyThrottleEvent(sleep);
206 // This maps from command names to command implementations.
207 private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>();
210 // Add in all the commands we support
211 COMMAND_MAP.put("flip", new FlipCommand());
212 COMMAND_MAP.put("touch", new TouchCommand());
213 COMMAND_MAP.put("trackball", new TrackballCommand());
214 COMMAND_MAP.put("key", new KeyCommand());
215 COMMAND_MAP.put("sleep", new SleepCommand());
219 private static final String QUIT = "quit";
221 // command response strings
222 private static final String OK = "OK";
223 private static final String ERROR = "ERROR";
226 private final int port;
227 private BufferedReader input;
228 private PrintWriter output;
229 private boolean started = false;
231 public MonkeySourceNetwork(int port) {
236 * Start a network server listening on the specified port. The
237 * network protocol is a line oriented protocol, where each line
238 * is a different command that can be run.
240 * @param port the port to listen on
242 private void startServer() throws IOException {
243 // Only bind this to local host. This means that you can only
244 // talk to the monkey locally, or though adb port forwarding.
245 ServerSocket server = new ServerSocket(port,
246 0, // default backlog
247 InetAddress.getLocalHost());
248 Socket s = server.accept();
249 input = new BufferedReader(new InputStreamReader(s.getInputStream()));
251 output = new PrintWriter(s.getOutputStream(), true);
255 * This function splits the given line into String parts. It obey's quoted
256 * strings and returns them as a single part.
258 * "This is a test" -> returns only one element
259 * This is a test -> returns four elements
261 * @param line the line to parse
262 * @return the List of elements
264 private static List<String> commandLineSplit(String line) {
265 ArrayList<String> result = new ArrayList<String>();
266 StringTokenizer tok = new StringTokenizer(line);
268 boolean insideQuote = false;
269 StringBuffer quotedWord = new StringBuffer();
270 while (tok.hasMoreTokens()) {
271 String cur = tok.nextToken();
272 if (!insideQuote && cur.startsWith("\"")) {
274 quotedWord.append(cur);
276 } else if (insideQuote) {
278 if (cur.endsWith("\"")) {
280 quotedWord.append(cur);
281 String word = quotedWord.toString();
283 // trim off the quotes
284 result.add(word.substring(1, word.length() - 1));
286 quotedWord.append(cur);
296 * Translate the given command line into a MonkeyEvent.
298 * @param commandLine the full command line given.
299 * @returns the MonkeyEvent corresponding to the command, or null
300 * if there was an issue.
302 private MonkeyEvent translateCommand(String commandLine) {
303 Log.d(TAG, "translateCommand: " + commandLine);
304 List<String> parts = commandLineSplit(commandLine);
305 if (parts.size() > 0) {
306 MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
307 if (command != null) {
308 return command.translateCommand(parts);
315 public MonkeyEvent getNextEvent() {
319 } catch (IOException e) {
320 Log.e(TAG, "Got IOException from server", e);
326 // Now, get the next command. This call may block, but that's OK
329 String command = input.readLine();
330 if (command == null) {
331 Log.d(TAG, "Connection dropped.");
334 // Do quit checking here
335 if (QUIT.equals(command)) {
337 Log.d(TAG, "Quit requested");
338 // let the host know the command ran OK
343 // Do comment checking here. Comments aren't a
344 // command, so we don't echo anything back to the
346 if (command.startsWith("#")) {
351 // Translate the command line
352 MonkeyEvent event = translateCommand(command);
354 // let the host know the command ran OK
358 // keep going. maybe the next command will make more sense
359 Log.e(TAG, "Got unknown command! \"" + command + "\"");
360 output.println(ERROR);
362 } catch (IOException e) {
363 Log.e(TAG, "Exception: ", e);
368 public void setVerbose(int verbose) {
369 // We're not particualy verbose
372 public boolean validate() {
373 // we have no pre-conditions to validate