Home | History | Annotate | Download | only in monkey
      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 
     63         public MonkeyCommandReturn(boolean success) {
     64             this.success = success;
     65             this.message = null;
     66         }
     67 
     68         public MonkeyCommandReturn(boolean success,
     69                                    String message) {
     70             this.success = success;
     71             this.message = message;
     72         }
     73 
     74         boolean hasMessage() {
     75             return message != null;
     76         }
     77 
     78         String getMessage() {
     79             return message;
     80         }
     81 
     82         boolean wasSuccessful() {
     83             return success;
     84         }
     85     }
     86 
     87     public final static MonkeyCommandReturn OK = new MonkeyCommandReturn(true);
     88     public final static MonkeyCommandReturn ERROR = new MonkeyCommandReturn(false);
     89     public final static MonkeyCommandReturn EARG = new MonkeyCommandReturn(false,
     90                                                                             "Invalid Argument");
     91 
     92     /**
     93      * Interface that MonkeyCommands must implement.
     94      */
     95     public interface MonkeyCommand {
     96         /**
     97          * Translate the command line into a sequence of MonkeyEvents.
     98          *
     99          * @param command the command line.
    100          * @param queue the command queue.
    101          * @return MonkeyCommandReturn indicating what happened.
    102          */
    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
    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
    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
    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
    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      */
    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
    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
    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
    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
    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
    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
    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      */
    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          */
    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 
    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          */
    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 
    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          */
    528         public MonkeyCommandReturn waitForEvent() {
    529             switch(event) {
    530                 case ON_WINDOW_STATE_CHANGE:
    531                     try {
    532                         synchronized(MonkeySourceNetworkViews.class) {
    533                             MonkeySourceNetworkViews.class.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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    788     private void returnOk(String returnValue) {
    789         output.print(OK_STR);
    790         output.print(":");
    791         output.println(returnValue);
    792     }
    793 
    794     public void setVerbose(int verbose) {
    795         // We're not particualy verbose
    796     }
    797 
    798     public boolean validate() {
    799         // we have no pre-conditions to validate
    800         return true;
    801     }
    802 }
    803