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 == KeyEvent.KEYCODE_UNKNOWN) {
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                 if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
258                     // Still unknown
259                     return -1;
260                 }
261             }
262         }
263         return keyCode;
264     }
265 
266     /**
267      * Command to put the Monkey to sleep.
268      */
269     private static class SleepCommand implements MonkeyCommand {
270         // sleep 2000
translateCommand(List<String> command, CommandQueue queue)271         public MonkeyCommandReturn translateCommand(List<String> command,
272                                                     CommandQueue queue) {
273             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) {
279                     Log.e(TAG, "Not a number: " + sleepStr, e);
280                     return EARG;
281                 }
282                 queue.enqueueEvent(new MonkeyThrottleEvent(sleep));
283                 return OK;
284             }
285             return EARG;
286         }
287     }
288 
289     /**
290      * Command to type a string
291      */
292     private static class TypeCommand implements MonkeyCommand {
293         // wake
translateCommand(List<String> command, CommandQueue queue)294         public MonkeyCommandReturn translateCommand(List<String> command,
295                                                     CommandQueue queue) {
296             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.
304                         load(KeyCharacterMap.VIRTUAL_KEYBOARD);
305                 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                 }
311                 return OK;
312             }
313             return EARG;
314         }
315     }
316 
317     /**
318      * Command to wake the device up
319      */
320     private static class WakeCommand implements MonkeyCommand {
321         // wake
translateCommand(List<String> command, CommandQueue queue)322         public MonkeyCommandReturn translateCommand(List<String> command,
323                                                     CommandQueue queue) {
324             if (!wake()) {
325                 return ERROR;
326             }
327             return OK;
328         }
329     }
330 
331     /**
332      * 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
translateCommand(List<String> command, CommandQueue queue)337         public MonkeyCommandReturn translateCommand(List<String> command,
338                                                     CommandQueue queue) {
339             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);
348                     return EARG;
349                 }
350 
351                 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));
355                 return OK;
356             }
357             return EARG;
358         }
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
translateCommand(List<String> command, CommandQueue queue)366         public MonkeyCommandReturn translateCommand(List<String> command,
367                                                     CommandQueue queue) {
368             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));
373                     return EARG;
374                 }
375 
376                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode));
377                 queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode));
378                 return OK;
379 
380             }
381             return EARG;
382         }
383     }
384 
385     /**
386      * 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
translateCommand(List<String> command, CommandQueue queue)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     /**
432      * Force the device to wake up.
433      *
434      * @return true if woken up OK.
435      */
wake()436     private static final boolean wake() {
437         IPowerManager pm =
438                 IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
439         try {
440             pm.wakeUp(SystemClock.uptimeMillis(), "Monkey", null);
441         } catch (RemoteException e) {
442             Log.e(TAG, "Got remote exception", e);
443             return false;
444         }
445         return true;
446     }
447 
448     // 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());
458         COMMAND_MAP.put("wake", new WakeCommand());
459         COMMAND_MAP.put("tap", new TapCommand());
460         COMMAND_MAP.put("press", new PressCommand());
461         COMMAND_MAP.put("type", new TypeCommand());
462         COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand());
463         COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand());
464         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());
469         COMMAND_MAP.put("deferreturn", new DeferReturnCommand());
470     }
471 
472     // QUIT command
473     private static final String QUIT = "quit";
474     // DONE command
475     private static final String DONE = "done";
476 
477     // command response strings
478     private static final String OK_STR = "OK";
479     private static final String ERROR_STR = "ERROR";
480 
481     public static interface CommandQueue {
482         /**
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          */
enqueueEvent(MonkeyEvent e)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 
enqueueEvent(MonkeyEvent e)499         public void enqueueEvent(MonkeyEvent e) {
500             queuedEvents.offer(e);
501         }
502 
503         /**
504          * Get the next queued event to excecute.
505          *
506          * @return the next event, or null if there aren't any more.
507          */
getNextQueuedEvent()508         public MonkeyEvent getNextQueuedEvent() {
509             return queuedEvents.poll();
510         }
511     };
512 
513     // 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 
DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout)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          */
waitForEvent()532         public MonkeyCommandReturn waitForEvent() {
533             switch(event) {
534                 case ON_WINDOW_STATE_CHANGE:
535                     try {
536                         synchronized(MonkeySourceNetworkViews.class) {
537                             MonkeySourceNetworkViews.class.wait(timeout);
538                         }
539                     } catch(InterruptedException e) {
540                         Log.d(TAG, "Deferral interrupted: " + e.getMessage());
541                     }
542             }
543             return deferredReturn;
544         }
545     };
546 
547     private final CommandQueueImpl commandQueue = new CommandQueueImpl();
548 
549     private BufferedReader input;
550     private PrintWriter output;
551     private boolean started = false;
552 
553     private ServerSocket serverSocket;
554     private Socket clientSocket;
555 
MonkeySourceNetwork(int port)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());
562     }
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      */
startServer()571     private void startServer() throws IOException {
572         clientSocket = serverSocket.accept();
573         // 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.
579         wake();
580 
581         input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
582         // auto-flush
583         output = new PrintWriter(clientSocket.getOutputStream(), true);
584     }
585 
586     /**
587      * Stop the server from running so it can reconnect a new client.
588      */
stopServer()589     private void stopServer() throws IOException {
590         clientSocket.close();
591         MonkeySourceNetworkViews.teardown();
592         input.close();
593         output.close();
594         started = false;
595     }
596 
597     /**
598      * Helper function for commandLineSplit that replaces quoted
599      * charaters with their real values.
600      *
601      * @param input the string to do replacement on.
602      * @return the results with the characters replaced.
603      */
replaceQuotedChars(String input)604     private static String replaceQuotedChars(String input) {
605         return input.replace("\\\"", "\"");
606     }
607 
608     /**
609      * 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      */
commandLineSplit(String line)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
628                 quotedWord.append(replaceQuotedChars(cur));
629                 insideQuote = true;
630             } else if (insideQuote) {
631                 // end quote
632                 if (cur.endsWith("\"")) {
633                     insideQuote = false;
634                     quotedWord.append(" ").append(replaceQuotedChars(cur));
635                     String word = quotedWord.toString();
636 
637                     // trim off the quotes
638                     result.add(word.substring(1, word.length() - 1));
639                 } else {
640                     quotedWord.append(" ").append(replaceQuotedChars(cur));
641                 }
642             } else {
643                 result.add(replaceQuotedChars(cur));
644             }
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.
653      */
translateCommand(String commandLine)654     private void translateCommand(String commandLine) {
655         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) {
660                 MonkeyCommandReturn ret = command.translateCommand(parts, commandQueue);
661                 handleReturn(ret);
662             }
663         }
664     }
665 
handleReturn(MonkeyCommandReturn ret)666     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 
getNextEvent()683     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) {
697                 // 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 
706                 // 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 
716                 String command = input.readLine();
717                 if (command == null) {
718                     Log.d(TAG, "Connection dropped.");
719                     // Treat this exactly the same as if the user had
720                     // ended the session cleanly with a done commant.
721                     command = DONE;
722                 }
723 
724                 if (DONE.equals(command)) {
725                     // stop the server so it can accept new connections
726                     try {
727                         stopServer();
728                     } catch (IOException e) {
729                         Log.e(TAG, "Got IOException shutting down!", e);
730                         return null;
731                     }
732                     // return a noop event so we keep executing the main
733                     // loop
734                     return new MonkeyNoopEvent();
735                 }
736 
737                 // 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
742                     returnOk();
743                     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("#")) {
750                     // keep going
751                     continue;
752                 }
753 
754                 // Translate the command line.  This will handle returning error/ok to the user
755                 translateCommand(command);
756             }
757         } catch (IOException e) {
758             Log.e(TAG, "Exception: ", e);
759             return null;
760         }
761     }
762 
763     /**
764      * Returns ERROR to the user.
765      */
returnError()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      */
returnError(String msg)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      */
returnOk()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      */
returnOk(String returnValue)793     private void returnOk(String returnValue) {
794         output.print(OK_STR);
795         output.print(":");
796         output.println(returnValue);
797     }
798 
setVerbose(int verbose)799     public void setVerbose(int verbose) {
800         // We're not particualy verbose
801     }
802 
validate()803     public boolean validate() {
804         // we have no pre-conditions to validate
805         return true;
806     }
807 }
808