• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.commands.monkey;
17 
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.KeyCharacterMap;
25 import android.view.KeyEvent;
26 import android.view.MotionEvent;
27 
28 import java.io.BufferedReader;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.io.PrintWriter;
32 import java.lang.Integer;
33 import java.lang.NumberFormatException;
34 import java.net.InetAddress;
35 import java.net.ServerSocket;
36 import java.net.Socket;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Map;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Queue;
43 import java.util.StringTokenizer;
44 
45 /**
46  * An Event source for getting Monkey Network Script commands from
47  * over the network.
48  */
49 public class MonkeySourceNetwork implements MonkeyEventSource {
50     private static final String TAG = "MonkeyStub";
51     /* The version of the monkey network protocol */
52     public static final int MONKEY_NETWORK_VERSION = 2;
53     private static DeferredReturn deferredReturn;
54 
55     /**
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 
MonkeyCommandReturn(boolean success)63         public MonkeyCommandReturn(boolean success) {
64             this.success = success;
65             this.message = null;
66         }
67 
MonkeyCommandReturn(boolean success, String message)68         public MonkeyCommandReturn(boolean success,
69                                    String message) {
70             this.success = success;
71             this.message = message;
72         }
73 
hasMessage()74         boolean hasMessage() {
75             return message != null;
76         }
77 
getMessage()78         String getMessage() {
79             return message;
80         }
81 
wasSuccessful()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.
101          * @return MonkeyCommandReturn indicating what happened.
102          */
translateCommand(List<String> command, CommandQueue queue)103         MonkeyCommandReturn translateCommand(List<String> command, CommandQueue queue);
104     }
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
translateCommand(List<String> command, CommandQueue queue)112         public MonkeyCommandReturn translateCommand(List<String> command,
113                                                     CommandQueue queue) {
114             if (command.size() > 1) {
115                 String direction = command.get(1);
116                 if ("open".equals(direction)) {
117                     queue.enqueueEvent(new MonkeyFlipEvent(true));
118                     return OK;
119                 } else if ("close".equals(direction)) {
120                     queue.enqueueEvent(new MonkeyFlipEvent(false));
121                     return OK;
122                 }
123             }
124             return EARG;
125         }
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
translateCommand(List<String> command, CommandQueue queue)136         public MonkeyCommandReturn translateCommand(List<String> command,
137                                                     CommandQueue queue) {
138             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);
148                     return EARG;
149                 }
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);
162                     return EARG;
163                 }
164 
165                 queue.enqueueEvent(new MonkeyTouchEvent(action)
166                         .addPointer(0, x, y));
167                 return OK;
168             }
169             return EARG;
170         }
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
translateCommand(List<String> command, CommandQueue queue)180         public MonkeyCommandReturn translateCommand(List<String> command,
181                                                     CommandQueue queue) {
182             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);
191                     return EARG;
192                 }
193                 queue.enqueueEvent(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE)
194                         .addPointer(0, dx, dy));
195                 return OK;
196 
197             }
198             return EARG;
199         }
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
translateCommand(List<String> command, CommandQueue queue)209         public MonkeyCommandReturn translateCommand(List<String> command,
210                                                     CommandQueue queue) {
211             if (command.size() == 3) {
212                 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));
216                     return EARG;
217                 }
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.");
227                     return EARG;
228                 }
229                 queue.enqueueEvent(new MonkeyKeyEvent(action, keyCode));
230                 return OK;
231             }
232             return EARG;
233         }
234     }
235 
236     /**
237      * Get an integer keycode value from a given keyname.
238      *
239      * @param keyName the key name to get the code for
240      * @return the integer keycode value, or -1 on error.
241      */
getKeyCode(String keyName)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);
250             if (keyCode == -1) {
251                 // 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());
257             }
258         }
259         return keyCode;
260     }
261 
262     /**
263      * Command to put the Monkey to sleep.
264      */
265     private static class SleepCommand implements MonkeyCommand {
266         // sleep 2000
translateCommand(List<String> command, CommandQueue queue)267         public MonkeyCommandReturn translateCommand(List<String> command,
268                                                     CommandQueue queue) {
269             if (command.size() == 2) {
270                 int sleep = -1;
271                 String sleepStr = command.get(1);
272                 try {
273                     sleep = Integer.parseInt(sleepStr);
274                 } catch (NumberFormatException e) {
275                     Log.e(TAG, "Not a number: " + sleepStr, e);
276                     return EARG;
277                 }
278                 queue.enqueueEvent(new MonkeyThrottleEvent(sleep));
279                 return OK;
280             }
281             return EARG;
282         }
283     }
284 
285     /**
286      * Command to type a string
287      */
288     private static class TypeCommand implements MonkeyCommand {
289         // wake
translateCommand(List<String> command, CommandQueue queue)290         public MonkeyCommandReturn translateCommand(List<String> command,
291                                                     CommandQueue queue) {
292             if (command.size() == 2) {
293                 String str = command.get(1);
294 
295                 char[] chars = str.toString().toCharArray();
296 
297                 // Convert the string to an array of KeyEvent's for
298                 // the built in keymap.
299                 KeyCharacterMap keyCharacterMap = KeyCharacterMap.
300                         load(KeyCharacterMap.VIRTUAL_KEYBOARD);
301                 KeyEvent[] events = keyCharacterMap.getEvents(chars);
302 
303                 // enqueue all the events we just got.
304                 for (KeyEvent event : events) {
305                     queue.enqueueEvent(new MonkeyKeyEvent(event));
306                 }
307                 return OK;
308             }
309             return EARG;
310         }
311     }
312 
313     /**
314      * Command to wake the device up
315      */
316     private static class WakeCommand implements MonkeyCommand {
317         // wake
translateCommand(List<String> command, CommandQueue queue)318         public MonkeyCommandReturn translateCommand(List<String> command,
319                                                     CommandQueue queue) {
320             if (!wake()) {
321                 return ERROR;
322             }
323             return OK;
324         }
325     }
326 
327     /**
328      * Command to "tap" at a location (Sends a down and up touch
329      * event).
330      */
331     private static class TapCommand implements MonkeyCommand {
332         // tap x y
translateCommand(List<String> command, CommandQueue queue)333         public MonkeyCommandReturn translateCommand(List<String> command,
334                                                     CommandQueue queue) {
335             if (command.size() == 3) {
336                 int x = 0;
337                 int y = 0;
338                 try {
339                     x = Integer.parseInt(command.get(1));
340                     y = Integer.parseInt(command.get(2));
341                 } catch (NumberFormatException e) {
342                     // Ok, it wasn't a number
343                     Log.e(TAG, "Got something that wasn't a number", e);
344                     return EARG;
345                 }
346 
347                 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
348                         .addPointer(0, x, y));
349                 queue.enqueueEvent(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
350                         .addPointer(0, x, y));
351                 return OK;
352             }
353             return EARG;
354         }
355     }
356 
357     /**
358      * Command to "press" a buttons (Sends an up and down key event.)
359      */
360     private static class PressCommand implements MonkeyCommand {
361         // press keycode
translateCommand(List<String> command, CommandQueue queue)362         public MonkeyCommandReturn translateCommand(List<String> command,
363                                                     CommandQueue queue) {
364             if (command.size() == 2) {
365                 int keyCode = getKeyCode(command.get(1));
366                 if (keyCode < 0) {
367                     // Ok, you gave us something bad.
368                     Log.e(TAG, "Can't find keyname: " + command.get(1));
369                     return EARG;
370                 }
371 
372                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode));
373                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode));
374                 return OK;
375 
376             }
377             return EARG;
378         }
379     }
380 
381     /**
382      * Command to defer the return of another command until the given event occurs.
383      * deferreturn takes three arguments. It takes an event to wait for (e.g. waiting for the
384      * device to display a different activity would the "screenchange" event), a
385      * timeout, which is the number of microseconds to wait for the event to occur, and it takes
386      * a command. The command can be any other Monkey command that can be issued over the network
387      * (e.g. press KEYCODE_HOME). deferreturn will then run this command, return an OK, wait for
388      * the event to occur and return the deferred return value when either the event occurs or
389      * when the timeout is reached (whichever occurs first). Note that there is no difference
390      * between an event occurring and the timeout being reached; the client will have to verify
391      * that the change actually occured.
392      *
393      * Example:
394      *     deferreturn screenchange 1000 press KEYCODE_HOME
395      * This command will press the home key on the device and then wait for the screen to change
396      * for up to one second. Either the screen will change, and the results fo the key press will
397      * be returned to the client, or the timeout will be reached, and the results for the key
398      * press will be returned to the client.
399      */
400     private static class DeferReturnCommand implements MonkeyCommand {
401         // deferreturn [event] [timeout (ms)] [command]
402         // deferreturn screenchange 100 tap 10 10
translateCommand(List<String> command, CommandQueue queue)403         public MonkeyCommandReturn translateCommand(List<String> command,
404                                                     CommandQueue queue) {
405             if (command.size() > 3) {
406                 String event = command.get(1);
407                 int eventId;
408                 if (event.equals("screenchange")) {
409                     eventId = DeferredReturn.ON_WINDOW_STATE_CHANGE;
410                 } else {
411                     return EARG;
412                 }
413                 long timeout = Long.parseLong(command.get(2));
414                 MonkeyCommand deferredCommand = COMMAND_MAP.get(command.get(3));
415                 if (deferredCommand != null) {
416                     List<String> parts = command.subList(3, command.size());
417                     MonkeyCommandReturn ret = deferredCommand.translateCommand(parts, queue);
418                     deferredReturn = new DeferredReturn(eventId, ret, timeout);
419                     return OK;
420                 }
421             }
422             return EARG;
423         }
424     }
425 
426 
427     /**
428      * Force the device to wake up.
429      *
430      * @return true if woken up OK.
431      */
wake()432     private static final boolean wake() {
433         IPowerManager pm =
434                 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
435         try {
436             pm.userActivityWithForce(SystemClock.uptimeMillis(), true, true);
437         } catch (RemoteException e) {
438             Log.e(TAG, "Got remote exception", e);
439             return false;
440         }
441         return true;
442     }
443 
444     // This maps from command names to command implementations.
445     private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>();
446 
447     static {
448         // Add in all the commands we support
449         COMMAND_MAP.put("flip", new FlipCommand());
450         COMMAND_MAP.put("touch", new TouchCommand());
451         COMMAND_MAP.put("trackball", new TrackballCommand());
452         COMMAND_MAP.put("key", new KeyCommand());
453         COMMAND_MAP.put("sleep", new SleepCommand());
454         COMMAND_MAP.put("wake", new WakeCommand());
455         COMMAND_MAP.put("tap", new TapCommand());
456         COMMAND_MAP.put("press", new PressCommand());
457         COMMAND_MAP.put("type", new TypeCommand());
458         COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand());
459         COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand());
460         COMMAND_MAP.put("listviews", new MonkeySourceNetworkViews.ListViewsCommand());
461         COMMAND_MAP.put("queryview", new MonkeySourceNetworkViews.QueryViewCommand());
462         COMMAND_MAP.put("getrootview", new MonkeySourceNetworkViews.GetRootViewCommand());
463         COMMAND_MAP.put("getviewswithtext",
464                         new MonkeySourceNetworkViews.GetViewsWithTextCommand());
465         COMMAND_MAP.put("deferreturn", new DeferReturnCommand());
466     }
467 
468     // QUIT command
469     private static final String QUIT = "quit";
470     // DONE command
471     private static final String DONE = "done";
472 
473     // command response strings
474     private static final String OK_STR = "OK";
475     private static final String ERROR_STR = "ERROR";
476 
477     public static interface CommandQueue {
478         /**
479          * Enqueue an event to be returned later.  This allows a
480          * command to return multiple events.  Commands using the
481          * command queue still have to return a valid event from their
482          * translateCommand method.  The returned command will be
483          * executed before anything put into the queue.
484          *
485          * @param e the event to be enqueued.
486          */
enqueueEvent(MonkeyEvent e)487         public void enqueueEvent(MonkeyEvent e);
488     };
489 
490     // Queue of Events to be processed.  This allows commands to push
491     // multiple events into the queue to be processed.
492     private static class CommandQueueImpl implements CommandQueue{
493         private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>();
494 
enqueueEvent(MonkeyEvent e)495         public void enqueueEvent(MonkeyEvent e) {
496             queuedEvents.offer(e);
497         }
498 
499         /**
500          * Get the next queued event to excecute.
501          *
502          * @return the next event, or null if there aren't any more.
503          */
getNextQueuedEvent()504         public MonkeyEvent getNextQueuedEvent() {
505             return queuedEvents.poll();
506         }
507     };
508 
509     // A holder class for a deferred return value. This allows us to defer returning the success of
510     // a call until a given event has occurred.
511     private static class DeferredReturn {
512         public static final int ON_WINDOW_STATE_CHANGE = 1;
513 
514         private int event;
515         private MonkeyCommandReturn deferredReturn;
516         private long timeout;
517 
DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout)518         public DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout) {
519             this.event = event;
520             this.deferredReturn = deferredReturn;
521             this.timeout = timeout;
522         }
523 
524         /**
525          * Wait until the given event has occurred before returning the value.
526          * @return The MonkeyCommandReturn from the command that was deferred.
527          */
waitForEvent()528         public MonkeyCommandReturn waitForEvent() {
529             switch(event) {
530                 case ON_WINDOW_STATE_CHANGE:
531                     try {
532                         synchronized(MonkeySourceNetworkViews.sConnection) {
533                             MonkeySourceNetworkViews.sConnection.wait(timeout);
534                         }
535                     } catch(InterruptedException e) {
536                         Log.d(TAG, "Deferral interrupted: " + e.getMessage());
537                     }
538             }
539             return deferredReturn;
540         }
541     };
542 
543     private final CommandQueueImpl commandQueue = new CommandQueueImpl();
544 
545     private BufferedReader input;
546     private PrintWriter output;
547     private boolean started = false;
548 
549     private ServerSocket serverSocket;
550     private Socket clientSocket;
551 
MonkeySourceNetwork(int port)552     public MonkeySourceNetwork(int port) throws IOException {
553         // Only bind this to local host.  This means that you can only
554         // talk to the monkey locally, or though adb port forwarding.
555         serverSocket = new ServerSocket(port,
556                                         0, // default backlog
557                                         InetAddress.getLocalHost());
558     }
559 
560     /**
561      * Start a network server listening on the specified port.  The
562      * network protocol is a line oriented protocol, where each line
563      * is a different command that can be run.
564      *
565      * @param port the port to listen on
566      */
startServer()567     private void startServer() throws IOException {
568         clientSocket = serverSocket.accept();
569         // At this point, we have a client connected.
570         // Attach the accessibility listeners so that we can start receiving
571         // view events. Do this before wake so we can catch the wake event
572         // if possible.
573         MonkeySourceNetworkViews.setup();
574         // Wake the device up in preparation for doing some commands.
575         wake();
576 
577         input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
578         // auto-flush
579         output = new PrintWriter(clientSocket.getOutputStream(), true);
580     }
581 
582     /**
583      * Stop the server from running so it can reconnect a new client.
584      */
stopServer()585     private void stopServer() throws IOException {
586         clientSocket.close();
587         input.close();
588         output.close();
589         started = false;
590     }
591 
592     /**
593      * Helper function for commandLineSplit that replaces quoted
594      * charaters with their real values.
595      *
596      * @param input the string to do replacement on.
597      * @return the results with the characters replaced.
598      */
replaceQuotedChars(String input)599     private static String replaceQuotedChars(String input) {
600         return input.replace("\\\"", "\"");
601     }
602 
603     /**
604      * This function splits the given line into String parts.  It obey's quoted
605      * strings and returns them as a single part.
606      *
607      * "This is a test" -> returns only one element
608      * This is a test -> returns four elements
609      *
610      * @param line the line to parse
611      * @return the List of elements
612      */
commandLineSplit(String line)613     private static List<String> commandLineSplit(String line) {
614         ArrayList<String> result = new ArrayList<String>();
615         StringTokenizer tok = new StringTokenizer(line);
616 
617         boolean insideQuote = false;
618         StringBuffer quotedWord = new StringBuffer();
619         while (tok.hasMoreTokens()) {
620             String cur = tok.nextToken();
621             if (!insideQuote && cur.startsWith("\"")) {
622                 // begin quote
623                 quotedWord.append(replaceQuotedChars(cur));
624                 insideQuote = true;
625             } else if (insideQuote) {
626                 // end quote
627                 if (cur.endsWith("\"")) {
628                     insideQuote = false;
629                     quotedWord.append(" ").append(replaceQuotedChars(cur));
630                     String word = quotedWord.toString();
631 
632                     // trim off the quotes
633                     result.add(word.substring(1, word.length() - 1));
634                 } else {
635                     quotedWord.append(" ").append(replaceQuotedChars(cur));
636                 }
637             } else {
638                 result.add(replaceQuotedChars(cur));
639             }
640         }
641         return result;
642     }
643 
644     /**
645      * Translate the given command line into a MonkeyEvent.
646      *
647      * @param commandLine the full command line given.
648      */
translateCommand(String commandLine)649     private void translateCommand(String commandLine) {
650         Log.d(TAG, "translateCommand: " + commandLine);
651         List<String> parts = commandLineSplit(commandLine);
652         if (parts.size() > 0) {
653             MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
654             if (command != null) {
655                 MonkeyCommandReturn ret = command.translateCommand(parts, commandQueue);
656                 handleReturn(ret);
657             }
658         }
659     }
660 
handleReturn(MonkeyCommandReturn ret)661     private void handleReturn(MonkeyCommandReturn ret) {
662         if (ret.wasSuccessful()) {
663             if (ret.hasMessage()) {
664                 returnOk(ret.getMessage());
665             } else {
666                 returnOk();
667             }
668         } else {
669             if (ret.hasMessage()) {
670                 returnError(ret.getMessage());
671             } else {
672                 returnError();
673             }
674         }
675     }
676 
677 
getNextEvent()678     public MonkeyEvent getNextEvent() {
679         if (!started) {
680             try {
681                 startServer();
682             } catch (IOException e) {
683                 Log.e(TAG, "Got IOException from server", e);
684                 return null;
685             }
686             started = true;
687         }
688 
689         // Now, get the next command.  This call may block, but that's OK
690         try {
691             while (true) {
692                 // Check to see if we have any events queued up.  If
693                 // we do, use those until we have no more.  Then get
694                 // more input from the user.
695                 MonkeyEvent queuedEvent = commandQueue.getNextQueuedEvent();
696                 if (queuedEvent != null) {
697                     // dispatch the event
698                     return queuedEvent;
699                 }
700 
701                 // Check to see if we have any returns that have been deferred. If so, now that
702                 // we've run the queued commands, wait for the given event to happen (or the timeout
703                 // to be reached), and handle the deferred MonkeyCommandReturn.
704                 if (deferredReturn != null) {
705                     Log.d(TAG, "Waiting for event");
706                     MonkeyCommandReturn ret = deferredReturn.waitForEvent();
707                     deferredReturn = null;
708                     handleReturn(ret);
709                 }
710 
711                 String command = input.readLine();
712                 if (command == null) {
713                     Log.d(TAG, "Connection dropped.");
714                     // Treat this exactly the same as if the user had
715                     // ended the session cleanly with a done commant.
716                     command = DONE;
717                 }
718 
719                 if (DONE.equals(command)) {
720                     // stop the server so it can accept new connections
721                     try {
722                         stopServer();
723                     } catch (IOException e) {
724                         Log.e(TAG, "Got IOException shutting down!", e);
725                         return null;
726                     }
727                     // return a noop event so we keep executing the main
728                     // loop
729                     return new MonkeyNoopEvent();
730                 }
731 
732                 // Do quit checking here
733                 if (QUIT.equals(command)) {
734                     // then we're done
735                     Log.d(TAG, "Quit requested");
736                     // let the host know the command ran OK
737                     returnOk();
738                     return null;
739                 }
740 
741                 // Do comment checking here.  Comments aren't a
742                 // command, so we don't echo anything back to the
743                 // user.
744                 if (command.startsWith("#")) {
745                     // keep going
746                     continue;
747                 }
748 
749                 // Translate the command line.  This will handle returning error/ok to the user
750                 translateCommand(command);
751             }
752         } catch (IOException e) {
753             Log.e(TAG, "Exception: ", e);
754             return null;
755         }
756     }
757 
758     /**
759      * Returns ERROR to the user.
760      */
returnError()761     private void returnError() {
762         output.println(ERROR_STR);
763     }
764 
765     /**
766      * Returns ERROR to the user.
767      *
768      * @param msg the error message to include
769      */
returnError(String msg)770     private void returnError(String msg) {
771         output.print(ERROR_STR);
772         output.print(":");
773         output.println(msg);
774     }
775 
776     /**
777      * Returns OK to the user.
778      */
returnOk()779     private void returnOk() {
780         output.println(OK_STR);
781     }
782 
783     /**
784      * Returns OK to the user.
785      *
786      * @param returnValue the value to return from this command.
787      */
returnOk(String returnValue)788     private void returnOk(String returnValue) {
789         output.print(OK_STR);
790         output.print(":");
791         output.println(returnValue);
792     }
793 
setVerbose(int verbose)794     public void setVerbose(int verbose) {
795         // We're not particualy verbose
796     }
797 
validate()798     public boolean validate() {
799         // we have no pre-conditions to validate
800         return true;
801     }
802 }
803