blob: 0eebf7c51b67d70e2aae7f968222faaaceafae52 [file] [log] [blame]
Bill Napiera68dbdb2009-08-07 11:34:12 -07001/*
2 * Copyright 2009, 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 */
16package com.android.commands.monkey;
17
Bill Napierad904292009-08-10 13:22:32 -070018import android.content.Context;
19import android.os.IPowerManager;
20import android.os.RemoteException;
21import android.os.ServiceManager;
22import android.os.SystemClock;
Bill Napiera68dbdb2009-08-07 11:34:12 -070023import android.util.Log;
Bill Napier3faef172009-08-11 16:07:38 -070024import android.view.KeyCharacterMap;
Bill Napiera68dbdb2009-08-07 11:34:12 -070025import android.view.KeyEvent;
26import android.view.MotionEvent;
27
28import java.io.BufferedReader;
29import java.io.IOException;
30import java.io.InputStreamReader;
31import java.io.PrintWriter;
32import java.lang.Integer;
33import java.lang.NumberFormatException;
34import java.net.InetAddress;
35import java.net.ServerSocket;
36import java.net.Socket;
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.Map;
Bill Napierbdc22202009-08-10 20:50:30 -070040import java.util.LinkedList;
Bill Napiera68dbdb2009-08-07 11:34:12 -070041import java.util.List;
Bill Napierbdc22202009-08-10 20:50:30 -070042import java.util.Queue;
Bill Napiera68dbdb2009-08-07 11:34:12 -070043import java.util.StringTokenizer;
44
45/**
46 * An Event source for getting Monkey Network Script commands from
47 * over the network.
48 */
49public class MonkeySourceNetwork implements MonkeyEventSource {
50 private static final String TAG = "MonkeyStub";
Michael Wrightb36bed52011-06-14 16:44:41 -070051 /* The version of the monkey network protocol */
52 public static final int MONKEY_NETWORK_VERSION = 2;
Michael Wrightc6c89b92011-07-18 18:35:45 -070053 private static DeferredReturn deferredReturn;
Bill Napiera68dbdb2009-08-07 11:34:12 -070054
Bill Napierf9481602009-08-17 11:20:15 -070055 /**
56 * ReturnValue from the MonkeyCommand that indicates whether the
57 * command was sucessful or not.
58 */
59 public static class MonkeyCommandReturn {
60 private final boolean success;
61 private final String message;
62
63 public MonkeyCommandReturn(boolean success) {
64 this.success = success;
65 this.message = null;
66 }
67
68 public MonkeyCommandReturn(boolean success,
69 String message) {
70 this.success = success;
71 this.message = message;
72 }
73
74 boolean hasMessage() {
75 return message != null;
76 }
77
78 String getMessage() {
79 return message;
80 }
81
82 boolean wasSuccessful() {
83 return success;
84 }
85 }
86
87 public final static MonkeyCommandReturn OK = new MonkeyCommandReturn(true);
88 public final static MonkeyCommandReturn ERROR = new MonkeyCommandReturn(false);
89 public final static MonkeyCommandReturn EARG = new MonkeyCommandReturn(false,
90 "Invalid Argument");
91
92 /**
93 * Interface that MonkeyCommands must implement.
94 */
95 public interface MonkeyCommand {
96 /**
97 * Translate the command line into a sequence of MonkeyEvents.
98 *
99 * @param command the command line.
100 * @param queue the command queue.
Michael Wrightb36bed52011-06-14 16:44:41 -0700101 * @return MonkeyCommandReturn indicating what happened.
Bill Napierf9481602009-08-17 11:20:15 -0700102 */
103 MonkeyCommandReturn translateCommand(List<String> command, CommandQueue queue);
Bill Napiera68dbdb2009-08-07 11:34:12 -0700104 }
105
106 /**
107 * Command to simulate closing and opening the keyboard.
108 */
109 private static class FlipCommand implements MonkeyCommand {
110 // flip open
111 // flip closed
Bill Napierf9481602009-08-17 11:20:15 -0700112 public MonkeyCommandReturn translateCommand(List<String> command,
113 CommandQueue queue) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700114 if (command.size() > 1) {
115 String direction = command.get(1);
116 if ("open".equals(direction)) {
Bill Napierf9481602009-08-17 11:20:15 -0700117 queue.enqueueEvent(new MonkeyFlipEvent(true));
118 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700119 } else if ("close".equals(direction)) {
Bill Napierf9481602009-08-17 11:20:15 -0700120 queue.enqueueEvent(new MonkeyFlipEvent(false));
121 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700122 }
123 }
Bill Napierf9481602009-08-17 11:20:15 -0700124 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700125 }
126 }
127
128 /**
129 * Command to send touch events to the input system.
130 */
131 private static class TouchCommand implements MonkeyCommand {
132 // touch [down|up|move] [x] [y]
133 // touch down 120 120
134 // touch move 140 140
135 // touch up 140 140
Bill Napierf9481602009-08-17 11:20:15 -0700136 public MonkeyCommandReturn translateCommand(List<String> command,
137 CommandQueue queue) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700138 if (command.size() == 4) {
139 String actionName = command.get(1);
140 int x = 0;
141 int y = 0;
142 try {
143 x = Integer.parseInt(command.get(2));
144 y = Integer.parseInt(command.get(3));
145 } catch (NumberFormatException e) {
146 // Ok, it wasn't a number
147 Log.e(TAG, "Got something that wasn't a number", e);
Bill Napierf9481602009-08-17 11:20:15 -0700148 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700149 }
150
151 // figure out the action
152 int action = -1;
153 if ("down".equals(actionName)) {
154 action = MotionEvent.ACTION_DOWN;
155 } else if ("up".equals(actionName)) {
156 action = MotionEvent.ACTION_UP;
157 } else if ("move".equals(actionName)) {
158 action = MotionEvent.ACTION_MOVE;
159 }
160 if (action == -1) {
161 Log.e(TAG, "Got a bad action: " + actionName);
Bill Napierf9481602009-08-17 11:20:15 -0700162 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700163 }
164
Jeff Brown585ab962010-10-04 20:58:18 -0700165 queue.enqueueEvent(new MonkeyTouchEvent(action)
166 .addPointer(0, x, y));
Bill Napierf9481602009-08-17 11:20:15 -0700167 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700168 }
Bill Napierf9481602009-08-17 11:20:15 -0700169 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700170 }
171 }
172
173 /**
174 * Command to send Trackball events to the input system.
175 */
176 private static class TrackballCommand implements MonkeyCommand {
177 // trackball [dx] [dy]
178 // trackball 1 0 -- move right
179 // trackball -1 0 -- move left
Bill Napierf9481602009-08-17 11:20:15 -0700180 public MonkeyCommandReturn translateCommand(List<String> command,
181 CommandQueue queue) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700182 if (command.size() == 3) {
183 int dx = 0;
184 int dy = 0;
185 try {
186 dx = Integer.parseInt(command.get(1));
187 dy = Integer.parseInt(command.get(2));
188 } catch (NumberFormatException e) {
189 // Ok, it wasn't a number
190 Log.e(TAG, "Got something that wasn't a number", e);
Bill Napierf9481602009-08-17 11:20:15 -0700191 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700192 }
Jeff Brown585ab962010-10-04 20:58:18 -0700193 queue.enqueueEvent(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE)
194 .addPointer(0, dx, dy));
Bill Napierf9481602009-08-17 11:20:15 -0700195 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700196
197 }
Bill Napierf9481602009-08-17 11:20:15 -0700198 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700199 }
200 }
201
202 /**
203 * Command to send Key events to the input system.
204 */
205 private static class KeyCommand implements MonkeyCommand {
206 // key [down|up] [keycode]
207 // key down 82
208 // key up 82
Bill Napierf9481602009-08-17 11:20:15 -0700209 public MonkeyCommandReturn translateCommand(List<String> command,
210 CommandQueue queue) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700211 if (command.size() == 3) {
Bill Napierbdc22202009-08-10 20:50:30 -0700212 int keyCode = getKeyCode(command.get(2));
213 if (keyCode < 0) {
214 // Ok, you gave us something bad.
215 Log.e(TAG, "Can't find keyname: " + command.get(2));
Bill Napierf9481602009-08-17 11:20:15 -0700216 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700217 }
218 Log.d(TAG, "keycode: " + keyCode);
219 int action = -1;
220 if ("down".equals(command.get(1))) {
221 action = KeyEvent.ACTION_DOWN;
222 } else if ("up".equals(command.get(1))) {
223 action = KeyEvent.ACTION_UP;
224 }
225 if (action == -1) {
226 Log.e(TAG, "got unknown action.");
Bill Napierf9481602009-08-17 11:20:15 -0700227 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700228 }
Bill Napierf9481602009-08-17 11:20:15 -0700229 queue.enqueueEvent(new MonkeyKeyEvent(action, keyCode));
230 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700231 }
Bill Napierf9481602009-08-17 11:20:15 -0700232 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700233 }
234 }
235
236 /**
Bill Napierbdc22202009-08-10 20:50:30 -0700237 * Get an integer keycode value from a given keyname.
238 *
239 * @param keyName the key name to get the code for
Michael Wrightb36bed52011-06-14 16:44:41 -0700240 * @return the integer keycode value, or -1 on error.
Bill Napierbdc22202009-08-10 20:50:30 -0700241 */
242 private static int getKeyCode(String keyName) {
243 int keyCode = -1;
244 try {
245 keyCode = Integer.parseInt(keyName);
246 } catch (NumberFormatException e) {
247 // Ok, it wasn't a number, see if we have a
248 // keycode name for it
249 keyCode = MonkeySourceRandom.getKeyCode(keyName);
Bill Napier3d3d6422012-02-16 16:58:19 -0800250 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
Bill Napierbdc22202009-08-10 20:50:30 -0700251 // OK, one last ditch effort to find a match.
252 // Build the KEYCODE_STRING from the string
253 // we've been given and see if that key
254 // exists. This would allow you to do "key
255 // down menu", for example.
256 keyCode = MonkeySourceRandom.getKeyCode("KEYCODE_" + keyName.toUpperCase());
Bill Napier3d3d6422012-02-16 16:58:19 -0800257 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
258 // Still unknown
259 return -1;
260 }
Bill Napierbdc22202009-08-10 20:50:30 -0700261 }
262 }
263 return keyCode;
264 }
265
266 /**
Bill Napiera68dbdb2009-08-07 11:34:12 -0700267 * Command to put the Monkey to sleep.
268 */
269 private static class SleepCommand implements MonkeyCommand {
270 // sleep 2000
Bill Napierf9481602009-08-17 11:20:15 -0700271 public MonkeyCommandReturn translateCommand(List<String> command,
272 CommandQueue queue) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700273 if (command.size() == 2) {
274 int sleep = -1;
275 String sleepStr = command.get(1);
276 try {
277 sleep = Integer.parseInt(sleepStr);
278 } catch (NumberFormatException e) {
Bill Napierf9481602009-08-17 11:20:15 -0700279 Log.e(TAG, "Not a number: " + sleepStr, e);
280 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700281 }
Bill Napierf9481602009-08-17 11:20:15 -0700282 queue.enqueueEvent(new MonkeyThrottleEvent(sleep));
283 return OK;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700284 }
Bill Napierf9481602009-08-17 11:20:15 -0700285 return EARG;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700286 }
287 }
288
Bill Napierad904292009-08-10 13:22:32 -0700289 /**
Bill Napier3faef172009-08-11 16:07:38 -0700290 * Command to type a string
291 */
292 private static class TypeCommand implements MonkeyCommand {
293 // wake
Bill Napierf9481602009-08-17 11:20:15 -0700294 public MonkeyCommandReturn translateCommand(List<String> command,
295 CommandQueue queue) {
Bill Napier3faef172009-08-11 16:07:38 -0700296 if (command.size() == 2) {
297 String str = command.get(1);
298
299 char[] chars = str.toString().toCharArray();
300
301 // Convert the string to an array of KeyEvent's for
302 // the built in keymap.
303 KeyCharacterMap keyCharacterMap = KeyCharacterMap.
Jeff Brownf083bd42010-11-02 15:53:10 -0700304 load(KeyCharacterMap.VIRTUAL_KEYBOARD);
Bill Napier3faef172009-08-11 16:07:38 -0700305 KeyEvent[] events = keyCharacterMap.getEvents(chars);
306
307 // enqueue all the events we just got.
308 for (KeyEvent event : events) {
309 queue.enqueueEvent(new MonkeyKeyEvent(event));
310 }
Bill Napierf9481602009-08-17 11:20:15 -0700311 return OK;
Bill Napier3faef172009-08-11 16:07:38 -0700312 }
Bill Napierf9481602009-08-17 11:20:15 -0700313 return EARG;
Bill Napier3faef172009-08-11 16:07:38 -0700314 }
315 }
316
317 /**
Bill Napierad904292009-08-10 13:22:32 -0700318 * Command to wake the device up
319 */
320 private static class WakeCommand implements MonkeyCommand {
321 // wake
Bill Napierf9481602009-08-17 11:20:15 -0700322 public MonkeyCommandReturn translateCommand(List<String> command,
323 CommandQueue queue) {
Bill Napierbdc22202009-08-10 20:50:30 -0700324 if (!wake()) {
Bill Napierf9481602009-08-17 11:20:15 -0700325 return ERROR;
Bill Napierad904292009-08-10 13:22:32 -0700326 }
Bill Napierf9481602009-08-17 11:20:15 -0700327 return OK;
Bill Napierad904292009-08-10 13:22:32 -0700328 }
329 }
330
331 /**
Bill Napierbdc22202009-08-10 20:50:30 -0700332 * Command to "tap" at a location (Sends a down and up touch
333 * event).
334 */
335 private static class TapCommand implements MonkeyCommand {
336 // tap x y
Bill Napierf9481602009-08-17 11:20:15 -0700337 public MonkeyCommandReturn translateCommand(List<String> command,
338 CommandQueue queue) {
Bill Napierbdc22202009-08-10 20:50:30 -0700339 if (command.size() == 3) {
340 int x = 0;
341 int y = 0;
342 try {
343 x = Integer.parseInt(command.get(1));
344 y = Integer.parseInt(command.get(2));
345 } catch (NumberFormatException e) {
346 // Ok, it wasn't a number
347 Log.e(TAG, "Got something that wasn't a number", e);
Bill Napierf9481602009-08-17 11:20:15 -0700348 return EARG;
Bill Napierbdc22202009-08-10 20:50:30 -0700349 }
350
Jeff Brown585ab962010-10-04 20:58:18 -0700351 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
352 .addPointer(0, x, y));
353 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
354 .addPointer(0, x, y));
Bill Napierf9481602009-08-17 11:20:15 -0700355 return OK;
Bill Napierbdc22202009-08-10 20:50:30 -0700356 }
Bill Napierf9481602009-08-17 11:20:15 -0700357 return EARG;
Bill Napierbdc22202009-08-10 20:50:30 -0700358 }
359 }
360
361 /**
362 * Command to "press" a buttons (Sends an up and down key event.)
363 */
364 private static class PressCommand implements MonkeyCommand {
365 // press keycode
Bill Napierf9481602009-08-17 11:20:15 -0700366 public MonkeyCommandReturn translateCommand(List<String> command,
367 CommandQueue queue) {
Bill Napierbdc22202009-08-10 20:50:30 -0700368 if (command.size() == 2) {
369 int keyCode = getKeyCode(command.get(1));
370 if (keyCode < 0) {
371 // Ok, you gave us something bad.
372 Log.e(TAG, "Can't find keyname: " + command.get(1));
Bill Napierf9481602009-08-17 11:20:15 -0700373 return EARG;
Bill Napierbdc22202009-08-10 20:50:30 -0700374 }
375
376 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode));
377 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode));
Bill Napierf9481602009-08-17 11:20:15 -0700378 return OK;
Bill Napierbdc22202009-08-10 20:50:30 -0700379
380 }
Bill Napierf9481602009-08-17 11:20:15 -0700381 return EARG;
Bill Napierbdc22202009-08-10 20:50:30 -0700382 }
383 }
384
385 /**
Michael Wrightc6c89b92011-07-18 18:35:45 -0700386 * Command to defer the return of another command until the given event occurs.
387 * deferreturn takes three arguments. It takes an event to wait for (e.g. waiting for the
388 * device to display a different activity would the "screenchange" event), a
389 * timeout, which is the number of microseconds to wait for the event to occur, and it takes
390 * a command. The command can be any other Monkey command that can be issued over the network
391 * (e.g. press KEYCODE_HOME). deferreturn will then run this command, return an OK, wait for
392 * the event to occur and return the deferred return value when either the event occurs or
393 * when the timeout is reached (whichever occurs first). Note that there is no difference
394 * between an event occurring and the timeout being reached; the client will have to verify
395 * that the change actually occured.
396 *
397 * Example:
398 * deferreturn screenchange 1000 press KEYCODE_HOME
399 * This command will press the home key on the device and then wait for the screen to change
400 * for up to one second. Either the screen will change, and the results fo the key press will
401 * be returned to the client, or the timeout will be reached, and the results for the key
402 * press will be returned to the client.
403 */
404 private static class DeferReturnCommand implements MonkeyCommand {
405 // deferreturn [event] [timeout (ms)] [command]
406 // deferreturn screenchange 100 tap 10 10
407 public MonkeyCommandReturn translateCommand(List<String> command,
408 CommandQueue queue) {
409 if (command.size() > 3) {
410 String event = command.get(1);
411 int eventId;
412 if (event.equals("screenchange")) {
413 eventId = DeferredReturn.ON_WINDOW_STATE_CHANGE;
414 } else {
415 return EARG;
416 }
417 long timeout = Long.parseLong(command.get(2));
418 MonkeyCommand deferredCommand = COMMAND_MAP.get(command.get(3));
419 if (deferredCommand != null) {
420 List<String> parts = command.subList(3, command.size());
421 MonkeyCommandReturn ret = deferredCommand.translateCommand(parts, queue);
422 deferredReturn = new DeferredReturn(eventId, ret, timeout);
423 return OK;
424 }
425 }
426 return EARG;
427 }
428 }
429
430
431 /**
Bill Napierad904292009-08-10 13:22:32 -0700432 * Force the device to wake up.
433 *
434 * @return true if woken up OK.
435 */
436 private static final boolean wake() {
437 IPowerManager pm =
438 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
439 try {
Dianne Hackbornd574af02015-07-14 17:57:51 -0700440 pm.wakeUp(SystemClock.uptimeMillis(), "Monkey", null);
Bill Napierad904292009-08-10 13:22:32 -0700441 } catch (RemoteException e) {
442 Log.e(TAG, "Got remote exception", e);
443 return false;
444 }
445 return true;
446 }
447
Bill Napiera68dbdb2009-08-07 11:34:12 -0700448 // This maps from command names to command implementations.
449 private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>();
450
451 static {
452 // Add in all the commands we support
453 COMMAND_MAP.put("flip", new FlipCommand());
454 COMMAND_MAP.put("touch", new TouchCommand());
455 COMMAND_MAP.put("trackball", new TrackballCommand());
456 COMMAND_MAP.put("key", new KeyCommand());
457 COMMAND_MAP.put("sleep", new SleepCommand());
Bill Napierad904292009-08-10 13:22:32 -0700458 COMMAND_MAP.put("wake", new WakeCommand());
Bill Napierbdc22202009-08-10 20:50:30 -0700459 COMMAND_MAP.put("tap", new TapCommand());
460 COMMAND_MAP.put("press", new PressCommand());
Bill Napier3faef172009-08-11 16:07:38 -0700461 COMMAND_MAP.put("type", new TypeCommand());
Bill Napierf9481602009-08-17 11:20:15 -0700462 COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand());
463 COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand());
Michael Wrightb36bed52011-06-14 16:44:41 -0700464 COMMAND_MAP.put("listviews", new MonkeySourceNetworkViews.ListViewsCommand());
465 COMMAND_MAP.put("queryview", new MonkeySourceNetworkViews.QueryViewCommand());
466 COMMAND_MAP.put("getrootview", new MonkeySourceNetworkViews.GetRootViewCommand());
467 COMMAND_MAP.put("getviewswithtext",
468 new MonkeySourceNetworkViews.GetViewsWithTextCommand());
Michael Wrightc6c89b92011-07-18 18:35:45 -0700469 COMMAND_MAP.put("deferreturn", new DeferReturnCommand());
Bill Napiera68dbdb2009-08-07 11:34:12 -0700470 }
471
472 // QUIT command
473 private static final String QUIT = "quit";
Bill Napier9d8557c2009-08-11 16:36:43 -0700474 // DONE command
475 private static final String DONE = "done";
Bill Napiera68dbdb2009-08-07 11:34:12 -0700476
477 // command response strings
Bill Napierf9481602009-08-17 11:20:15 -0700478 private static final String OK_STR = "OK";
479 private static final String ERROR_STR = "ERROR";
Bill Napiera68dbdb2009-08-07 11:34:12 -0700480
Bill Napierf9481602009-08-17 11:20:15 -0700481 public static interface CommandQueue {
Bill Napierbdc22202009-08-10 20:50:30 -0700482 /**
483 * Enqueue an event to be returned later. This allows a
484 * command to return multiple events. Commands using the
485 * command queue still have to return a valid event from their
486 * translateCommand method. The returned command will be
487 * executed before anything put into the queue.
488 *
489 * @param e the event to be enqueued.
490 */
491 public void enqueueEvent(MonkeyEvent e);
492 };
493
494 // Queue of Events to be processed. This allows commands to push
495 // multiple events into the queue to be processed.
496 private static class CommandQueueImpl implements CommandQueue{
497 private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>();
498
499 public void enqueueEvent(MonkeyEvent e) {
500 queuedEvents.offer(e);
501 }
502
503 /**
504 * Get the next queued event to excecute.
505 *
Michael Wrightb36bed52011-06-14 16:44:41 -0700506 * @return the next event, or null if there aren't any more.
Bill Napierbdc22202009-08-10 20:50:30 -0700507 */
508 public MonkeyEvent getNextQueuedEvent() {
509 return queuedEvents.poll();
510 }
511 };
512
Michael Wrightc6c89b92011-07-18 18:35:45 -0700513 // A holder class for a deferred return value. This allows us to defer returning the success of
514 // a call until a given event has occurred.
515 private static class DeferredReturn {
516 public static final int ON_WINDOW_STATE_CHANGE = 1;
517
518 private int event;
519 private MonkeyCommandReturn deferredReturn;
520 private long timeout;
521
522 public DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout) {
523 this.event = event;
524 this.deferredReturn = deferredReturn;
525 this.timeout = timeout;
526 }
527
528 /**
529 * Wait until the given event has occurred before returning the value.
530 * @return The MonkeyCommandReturn from the command that was deferred.
531 */
532 public MonkeyCommandReturn waitForEvent() {
533 switch(event) {
534 case ON_WINDOW_STATE_CHANGE:
535 try {
Svetoslav Ganovbdf03ad2011-11-30 17:48:42 -0800536 synchronized(MonkeySourceNetworkViews.class) {
537 MonkeySourceNetworkViews.class.wait(timeout);
Michael Wrightc6c89b92011-07-18 18:35:45 -0700538 }
539 } catch(InterruptedException e) {
540 Log.d(TAG, "Deferral interrupted: " + e.getMessage());
541 }
542 }
543 return deferredReturn;
544 }
545 };
546
Bill Napierbdc22202009-08-10 20:50:30 -0700547 private final CommandQueueImpl commandQueue = new CommandQueueImpl();
Bill Napiera68dbdb2009-08-07 11:34:12 -0700548
Bill Napiera68dbdb2009-08-07 11:34:12 -0700549 private BufferedReader input;
550 private PrintWriter output;
551 private boolean started = false;
552
Bill Napier9d8557c2009-08-11 16:36:43 -0700553 private ServerSocket serverSocket;
554 private Socket clientSocket;
555
556 public MonkeySourceNetwork(int port) throws IOException {
557 // Only bind this to local host. This means that you can only
558 // talk to the monkey locally, or though adb port forwarding.
559 serverSocket = new ServerSocket(port,
560 0, // default backlog
561 InetAddress.getLocalHost());
Bill Napiera68dbdb2009-08-07 11:34:12 -0700562 }
563
564 /**
565 * Start a network server listening on the specified port. The
566 * network protocol is a line oriented protocol, where each line
567 * is a different command that can be run.
568 *
569 * @param port the port to listen on
570 */
571 private void startServer() throws IOException {
Bill Napier9d8557c2009-08-11 16:36:43 -0700572 clientSocket = serverSocket.accept();
Michael Wrightb36bed52011-06-14 16:44:41 -0700573 // At this point, we have a client connected.
574 // Attach the accessibility listeners so that we can start receiving
575 // view events. Do this before wake so we can catch the wake event
576 // if possible.
577 MonkeySourceNetworkViews.setup();
578 // Wake the device up in preparation for doing some commands.
Bill Napierad904292009-08-10 13:22:32 -0700579 wake();
580
Bill Napier9d8557c2009-08-11 16:36:43 -0700581 input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
Bill Napiera68dbdb2009-08-07 11:34:12 -0700582 // auto-flush
Bill Napier9d8557c2009-08-11 16:36:43 -0700583 output = new PrintWriter(clientSocket.getOutputStream(), true);
584 }
585
586 /**
587 * Stop the server from running so it can reconnect a new client.
588 */
589 private void stopServer() throws IOException {
590 clientSocket.close();
Svetoslav Ganov00fc93c2013-01-02 10:13:10 -0800591 MonkeySourceNetworkViews.teardown();
Bill Napier9d8557c2009-08-11 16:36:43 -0700592 input.close();
593 output.close();
594 started = false;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700595 }
596
597 /**
Bill Napier3faef172009-08-11 16:07:38 -0700598 * Helper function for commandLineSplit that replaces quoted
599 * charaters with their real values.
600 *
601 * @param input the string to do replacement on.
Michael Wrightb36bed52011-06-14 16:44:41 -0700602 * @return the results with the characters replaced.
Bill Napier3faef172009-08-11 16:07:38 -0700603 */
604 private static String replaceQuotedChars(String input) {
605 return input.replace("\\\"", "\"");
606 }
607
608 /**
Bill Napiera68dbdb2009-08-07 11:34:12 -0700609 * This function splits the given line into String parts. It obey's quoted
610 * strings and returns them as a single part.
611 *
612 * "This is a test" -> returns only one element
613 * This is a test -> returns four elements
614 *
615 * @param line the line to parse
616 * @return the List of elements
617 */
618 private static List<String> commandLineSplit(String line) {
619 ArrayList<String> result = new ArrayList<String>();
620 StringTokenizer tok = new StringTokenizer(line);
621
622 boolean insideQuote = false;
623 StringBuffer quotedWord = new StringBuffer();
624 while (tok.hasMoreTokens()) {
625 String cur = tok.nextToken();
626 if (!insideQuote && cur.startsWith("\"")) {
627 // begin quote
Bill Napier3faef172009-08-11 16:07:38 -0700628 quotedWord.append(replaceQuotedChars(cur));
Bill Napiera68dbdb2009-08-07 11:34:12 -0700629 insideQuote = true;
630 } else if (insideQuote) {
631 // end quote
632 if (cur.endsWith("\"")) {
633 insideQuote = false;
Bill Napier3faef172009-08-11 16:07:38 -0700634 quotedWord.append(" ").append(replaceQuotedChars(cur));
Bill Napiera68dbdb2009-08-07 11:34:12 -0700635 String word = quotedWord.toString();
636
637 // trim off the quotes
638 result.add(word.substring(1, word.length() - 1));
639 } else {
Bill Napier3faef172009-08-11 16:07:38 -0700640 quotedWord.append(" ").append(replaceQuotedChars(cur));
Bill Napiera68dbdb2009-08-07 11:34:12 -0700641 }
642 } else {
Bill Napier3faef172009-08-11 16:07:38 -0700643 result.add(replaceQuotedChars(cur));
Bill Napiera68dbdb2009-08-07 11:34:12 -0700644 }
645 }
646 return result;
647 }
648
649 /**
650 * Translate the given command line into a MonkeyEvent.
651 *
652 * @param commandLine the full command line given.
Bill Napiera68dbdb2009-08-07 11:34:12 -0700653 */
Bill Napierf9481602009-08-17 11:20:15 -0700654 private void translateCommand(String commandLine) {
Bill Napiera68dbdb2009-08-07 11:34:12 -0700655 Log.d(TAG, "translateCommand: " + commandLine);
656 List<String> parts = commandLineSplit(commandLine);
657 if (parts.size() > 0) {
658 MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
659 if (command != null) {
Michael Wrightb36bed52011-06-14 16:44:41 -0700660 MonkeyCommandReturn ret = command.translateCommand(parts, commandQueue);
Michael Wrightc6c89b92011-07-18 18:35:45 -0700661 handleReturn(ret);
Bill Napiera68dbdb2009-08-07 11:34:12 -0700662 }
Bill Napiera68dbdb2009-08-07 11:34:12 -0700663 }
Bill Napiera68dbdb2009-08-07 11:34:12 -0700664 }
665
Michael Wrightc6c89b92011-07-18 18:35:45 -0700666 private void handleReturn(MonkeyCommandReturn ret) {
667 if (ret.wasSuccessful()) {
668 if (ret.hasMessage()) {
669 returnOk(ret.getMessage());
670 } else {
671 returnOk();
672 }
673 } else {
674 if (ret.hasMessage()) {
675 returnError(ret.getMessage());
676 } else {
677 returnError();
678 }
679 }
680 }
681
682
Bill Napiera68dbdb2009-08-07 11:34:12 -0700683 public MonkeyEvent getNextEvent() {
684 if (!started) {
685 try {
686 startServer();
687 } catch (IOException e) {
688 Log.e(TAG, "Got IOException from server", e);
689 return null;
690 }
691 started = true;
692 }
693
694 // Now, get the next command. This call may block, but that's OK
695 try {
696 while (true) {
Bill Napierbdc22202009-08-10 20:50:30 -0700697 // Check to see if we have any events queued up. If
698 // we do, use those until we have no more. Then get
699 // more input from the user.
700 MonkeyEvent queuedEvent = commandQueue.getNextQueuedEvent();
701 if (queuedEvent != null) {
702 // dispatch the event
703 return queuedEvent;
704 }
705
Michael Wrightc6c89b92011-07-18 18:35:45 -0700706 // Check to see if we have any returns that have been deferred. If so, now that
707 // we've run the queued commands, wait for the given event to happen (or the timeout
708 // to be reached), and handle the deferred MonkeyCommandReturn.
709 if (deferredReturn != null) {
710 Log.d(TAG, "Waiting for event");
711 MonkeyCommandReturn ret = deferredReturn.waitForEvent();
712 deferredReturn = null;
713 handleReturn(ret);
714 }
715
Bill Napiera68dbdb2009-08-07 11:34:12 -0700716 String command = input.readLine();
717 if (command == null) {
718 Log.d(TAG, "Connection dropped.");
Bill Napier9d8557c2009-08-11 16:36:43 -0700719 // Treat this exactly the same as if the user had
720 // ended the session cleanly with a done commant.
721 command = DONE;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700722 }
Bill Napier9d8557c2009-08-11 16:36:43 -0700723
724 if (DONE.equals(command)) {
Bill Napierf9481602009-08-17 11:20:15 -0700725 // stop the server so it can accept new connections
Bill Napier9d8557c2009-08-11 16:36:43 -0700726 try {
727 stopServer();
728 } catch (IOException e) {
729 Log.e(TAG, "Got IOException shutting down!", e);
730 return null;
731 }
Bill Napierf9481602009-08-17 11:20:15 -0700732 // return a noop event so we keep executing the main
733 // loop
734 return new MonkeyNoopEvent();
Bill Napier9d8557c2009-08-11 16:36:43 -0700735 }
736
Bill Napiera68dbdb2009-08-07 11:34:12 -0700737 // Do quit checking here
738 if (QUIT.equals(command)) {
739 // then we're done
740 Log.d(TAG, "Quit requested");
741 // let the host know the command ran OK
Bill Napierf9481602009-08-17 11:20:15 -0700742 returnOk();
Bill Napiera68dbdb2009-08-07 11:34:12 -0700743 return null;
744 }
745
746 // Do comment checking here. Comments aren't a
747 // command, so we don't echo anything back to the
748 // user.
749 if (command.startsWith("#")) {
Bill Napierf9481602009-08-17 11:20:15 -0700750 // keep going
751 continue;
Bill Napiera68dbdb2009-08-07 11:34:12 -0700752 }
753
Bill Napierf9481602009-08-17 11:20:15 -0700754 // Translate the command line. This will handle returning error/ok to the user
755 translateCommand(command);
Bill Napiera68dbdb2009-08-07 11:34:12 -0700756 }
757 } catch (IOException e) {
758 Log.e(TAG, "Exception: ", e);
759 return null;
760 }
761 }
762
Bill Napierf9481602009-08-17 11:20:15 -0700763 /**
764 * Returns ERROR to the user.
765 */
766 private void returnError() {
767 output.println(ERROR_STR);
768 }
769
770 /**
771 * Returns ERROR to the user.
772 *
773 * @param msg the error message to include
774 */
775 private void returnError(String msg) {
776 output.print(ERROR_STR);
777 output.print(":");
778 output.println(msg);
779 }
780
781 /**
782 * Returns OK to the user.
783 */
784 private void returnOk() {
785 output.println(OK_STR);
786 }
787
788 /**
789 * Returns OK to the user.
790 *
791 * @param returnValue the value to return from this command.
792 */
793 private void returnOk(String returnValue) {
794 output.print(OK_STR);
795 output.print(":");
796 output.println(returnValue);
797 }
798
Bill Napiera68dbdb2009-08-07 11:34:12 -0700799 public void setVerbose(int verbose) {
800 // We're not particualy verbose
801 }
802
803 public boolean validate() {
804 // we have no pre-conditions to validate
805 return true;
806 }
807}