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