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