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 == 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
    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
    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
    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
    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
    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
    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      */
    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          */
    491         public void enqueueEvent(MonkeyEvent e);
    492     };
    493 
    494     // Queue of Events to be processed.  This allows commands to push
    495     // multiple events into the queue to be processed.
    496     private static class CommandQueueImpl implements CommandQueue{
    497         private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>();
    498 
    499         public void enqueueEvent(MonkeyEvent e) {
    500             queuedEvents.offer(e);
    501         }
    502 
    503         /**
    504          * Get the next queued event to excecute.
    505          *
    506          * @return the next event, or null if there aren't any more.
    507          */
    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 
    522         public DeferredReturn(int event, MonkeyCommandReturn deferredReturn, long timeout) {
    523             this.event = event;
    524             this.deferredReturn = deferredReturn;
    525             this.timeout = timeout;
    526         }
    527 
    528         /**
    529          * Wait until the given event has occurred before returning the value.
    530          * @return The MonkeyCommandReturn from the command that was deferred.
    531          */
    532         public MonkeyCommandReturn waitForEvent() {
    533             switch(event) {
    534                 case ON_WINDOW_STATE_CHANGE:
    535                     try {
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    766     private void returnError() {
    767         output.println(ERROR_STR);
    768     }
    769 
    770     /**
    771      * Returns ERROR to the user.
    772      *
    773      * @param msg the error message to include
    774      */
    775     private void returnError(String msg) {
    776         output.print(ERROR_STR);
    777         output.print(":");
    778         output.println(msg);
    779     }
    780 
    781     /**
    782      * Returns OK to the user.
    783      */
    784     private void returnOk() {
    785         output.println(OK_STR);
    786     }
    787 
    788     /**
    789      * Returns OK to the user.
    790      *
    791      * @param returnValue the value to return from this command.
    792      */
    793     private void returnOk(String returnValue) {
    794         output.print(OK_STR);
    795         output.print(":");
    796         output.println(returnValue);
    797     }
    798 
    799     public void setVerbose(int verbose) {
    800         // We're not particualy verbose
    801     }
    802 
    803     public boolean validate() {
    804         // we have no pre-conditions to validate
    805         return true;
    806     }
    807 }
    808