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.content.Context;
19 import android.os.IPowerManager;
20 import android.os.RemoteException;
21 import android.os.ServiceManager;
22 import android.os.SystemClock;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.MotionEvent;
27 import java.io.BufferedReader;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.io.PrintWriter;
31 import java.lang.Integer;
32 import java.lang.NumberFormatException;
33 import java.net.InetAddress;
34 import java.net.ServerSocket;
35 import java.net.Socket;
36 import java.util.ArrayList;
37 import java.util.HashMap;
39 import java.util.List;
40 import java.util.StringTokenizer;
43 * An Event source for getting Monkey Network Script commands from
46 public class MonkeySourceNetwork implements MonkeyEventSource {
47 private static final String TAG = "MonkeyStub";
49 private interface MonkeyCommand {
50 MonkeyEvent translateCommand(List<String> command);
54 * Command to simulate closing and opening the keyboard.
56 private static class FlipCommand implements MonkeyCommand {
59 public MonkeyEvent translateCommand(List<String> command) {
60 if (command.size() > 1) {
61 String direction = command.get(1);
62 if ("open".equals(direction)) {
63 return new MonkeyFlipEvent(true);
64 } else if ("close".equals(direction)) {
65 return new MonkeyFlipEvent(false);
73 * Command to send touch events to the input system.
75 private static class TouchCommand implements MonkeyCommand {
76 // touch [down|up|move] [x] [y]
80 public MonkeyEvent translateCommand(List<String> command) {
81 if (command.size() == 4) {
82 String actionName = command.get(1);
86 x = Integer.parseInt(command.get(2));
87 y = Integer.parseInt(command.get(3));
88 } catch (NumberFormatException e) {
89 // Ok, it wasn't a number
90 Log.e(TAG, "Got something that wasn't a number", e);
94 // figure out the action
96 if ("down".equals(actionName)) {
97 action = MotionEvent.ACTION_DOWN;
98 } else if ("up".equals(actionName)) {
99 action = MotionEvent.ACTION_UP;
100 } else if ("move".equals(actionName)) {
101 action = MotionEvent.ACTION_MOVE;
104 Log.e(TAG, "Got a bad action: " + actionName);
108 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
109 -1, action, x, y, 0);
117 * Command to send Trackball events to the input system.
119 private static class TrackballCommand implements MonkeyCommand {
120 // trackball [dx] [dy]
121 // trackball 1 0 -- move right
122 // trackball -1 0 -- move left
123 public MonkeyEvent translateCommand(List<String> command) {
124 if (command.size() == 3) {
128 dx = Integer.parseInt(command.get(1));
129 dy = Integer.parseInt(command.get(2));
130 } catch (NumberFormatException e) {
131 // Ok, it wasn't a number
132 Log.e(TAG, "Got something that wasn't a number", e);
135 return new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
136 MotionEvent.ACTION_MOVE, dx, dy, 0);
144 * Command to send Key events to the input system.
146 private static class KeyCommand implements MonkeyCommand {
147 // key [down|up] [keycode]
150 public MonkeyEvent translateCommand(List<String> command) {
151 if (command.size() == 3) {
153 String keyName = command.get(2);
155 keyCode = Integer.parseInt(keyName);
156 } catch (NumberFormatException e) {
157 // Ok, it wasn't a number, see if we have a
158 // keycode name for it
159 keyCode = MonkeySourceRandom.getKeyCode(keyName);
161 // OK, one last ditch effort to find a match.
162 // Build the KEYCODE_STRING from the string
163 // we've been given and see if that key
164 // exists. This would allow you to do "key
165 // down menu", for example.
166 keyCode = MonkeySourceRandom.getKeyCode("KEYCODE_" + keyName.toUpperCase());
168 // Ok, you gave us something bad.
169 Log.e(TAG, "Can't find keyname: " + keyName);
174 Log.d(TAG, "keycode: " + keyCode);
176 if ("down".equals(command.get(1))) {
177 action = KeyEvent.ACTION_DOWN;
178 } else if ("up".equals(command.get(1))) {
179 action = KeyEvent.ACTION_UP;
182 Log.e(TAG, "got unknown action.");
185 return new MonkeyKeyEvent(action, keyCode);
192 * Command to put the Monkey to sleep.
194 private static class SleepCommand implements MonkeyCommand {
196 public MonkeyEvent translateCommand(List<String> command) {
197 if (command.size() == 2) {
199 String sleepStr = command.get(1);
201 sleep = Integer.parseInt(sleepStr);
202 } catch (NumberFormatException e) {
203 Log.e(TAG, "Not a number: " + sleepStr, e);
205 return new MonkeyThrottleEvent(sleep);
212 * Command to wake the device up
214 private static class WakeCommand implements MonkeyCommand {
216 public MonkeyEvent translateCommand(List<String> command) {
220 return new MonkeyNoopEvent();
225 * Force the device to wake up.
227 * @return true if woken up OK.
229 private static final boolean wake() {
231 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
233 pm.userActivityWithForce(SystemClock.uptimeMillis(), true, true);
234 } catch (RemoteException e) {
235 Log.e(TAG, "Got remote exception", e);
241 // This maps from command names to command implementations.
242 private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>();
245 // Add in all the commands we support
246 COMMAND_MAP.put("flip", new FlipCommand());
247 COMMAND_MAP.put("touch", new TouchCommand());
248 COMMAND_MAP.put("trackball", new TrackballCommand());
249 COMMAND_MAP.put("key", new KeyCommand());
250 COMMAND_MAP.put("sleep", new SleepCommand());
251 COMMAND_MAP.put("wake", new WakeCommand());
255 private static final String QUIT = "quit";
257 // command response strings
258 private static final String OK = "OK";
259 private static final String ERROR = "ERROR";
262 private final int port;
263 private BufferedReader input;
264 private PrintWriter output;
265 private boolean started = false;
267 public MonkeySourceNetwork(int port) {
272 * Start a network server listening on the specified port. The
273 * network protocol is a line oriented protocol, where each line
274 * is a different command that can be run.
276 * @param port the port to listen on
278 private void startServer() throws IOException {
279 // Only bind this to local host. This means that you can only
280 // talk to the monkey locally, or though adb port forwarding.
281 ServerSocket server = new ServerSocket(port,
282 0, // default backlog
283 InetAddress.getLocalHost());
284 Socket s = server.accept();
285 // At this point, we have a client connected. Wake the device
286 // up in preparation for doing some commands.
289 input = new BufferedReader(new InputStreamReader(s.getInputStream()));
291 output = new PrintWriter(s.getOutputStream(), true);
295 * This function splits the given line into String parts. It obey's quoted
296 * strings and returns them as a single part.
298 * "This is a test" -> returns only one element
299 * This is a test -> returns four elements
301 * @param line the line to parse
302 * @return the List of elements
304 private static List<String> commandLineSplit(String line) {
305 ArrayList<String> result = new ArrayList<String>();
306 StringTokenizer tok = new StringTokenizer(line);
308 boolean insideQuote = false;
309 StringBuffer quotedWord = new StringBuffer();
310 while (tok.hasMoreTokens()) {
311 String cur = tok.nextToken();
312 if (!insideQuote && cur.startsWith("\"")) {
314 quotedWord.append(cur);
316 } else if (insideQuote) {
318 if (cur.endsWith("\"")) {
320 quotedWord.append(cur);
321 String word = quotedWord.toString();
323 // trim off the quotes
324 result.add(word.substring(1, word.length() - 1));
326 quotedWord.append(cur);
336 * Translate the given command line into a MonkeyEvent.
338 * @param commandLine the full command line given.
339 * @returns the MonkeyEvent corresponding to the command, or null
340 * if there was an issue.
342 private MonkeyEvent translateCommand(String commandLine) {
343 Log.d(TAG, "translateCommand: " + commandLine);
344 List<String> parts = commandLineSplit(commandLine);
345 if (parts.size() > 0) {
346 MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
347 if (command != null) {
348 return command.translateCommand(parts);
355 public MonkeyEvent getNextEvent() {
359 } catch (IOException e) {
360 Log.e(TAG, "Got IOException from server", e);
366 // Now, get the next command. This call may block, but that's OK
369 String command = input.readLine();
370 if (command == null) {
371 Log.d(TAG, "Connection dropped.");
374 // Do quit checking here
375 if (QUIT.equals(command)) {
377 Log.d(TAG, "Quit requested");
378 // let the host know the command ran OK
383 // Do comment checking here. Comments aren't a
384 // command, so we don't echo anything back to the
386 if (command.startsWith("#")) {
391 // Translate the command line
392 MonkeyEvent event = translateCommand(command);
394 // let the host know the command ran OK
398 // keep going. maybe the next command will make more sense
399 Log.e(TAG, "Got unknown command! \"" + command + "\"");
400 output.println(ERROR);
402 } catch (IOException e) {
403 Log.e(TAG, "Exception: ", e);
408 public void setVerbose(int verbose) {
409 // We're not particualy verbose
412 public boolean validate() {
413 // we have no pre-conditions to validate