• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 
17 package com.android.monkeyrunner;
18 
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.IDevice;
21 import com.android.ddmlib.Log;
22 import com.android.ddmlib.NullOutputReceiver;
23 import com.android.ddmlib.RawImage;
24 import com.android.ddmlib.Log.ILogOutput;
25 import com.android.ddmlib.Log.LogLevel;
26 
27 import java.awt.image.BufferedImage;
28 
29 import java.io.BufferedReader;
30 import java.io.BufferedWriter;
31 import java.io.File;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.OutputStreamWriter;
36 import java.net.InetAddress;
37 import java.net.Socket;
38 import java.net.UnknownHostException;
39 import java.util.logging.Level;
40 import java.util.logging.Logger;
41 
42 import javax.imageio.ImageIO;
43 
44 /**
45  *  MonkeyRunner is a host side application to control a monkey instance on a
46  *  device. MonkeyRunner provides some useful helper functions to control the
47  *  device as well as various other methods to help script tests.
48  */
49 public class MonkeyRunner {
50 
51   static String monkeyServer = "127.0.0.1";
52   static int monkeyPort = 1080;
53   static Socket monkeySocket = null;
54 
55   static IDevice monkeyDevice;
56 
57   static BufferedReader monkeyReader;
58   static BufferedWriter monkeyWriter;
59   static String monkeyResponse;
60 
61   static MonkeyRecorder monkeyRecorder;
62 
63   static String scriptName = null;
64 
65   // Obtain a suitable logger.
66   private static Logger logger = Logger.getLogger("com.android.monkeyrunner");
67 
68   // delay between key events
69   final static int KEY_INPUT_DELAY = 1000;
70 
71   // version of monkey runner
72   final static String monkeyRunnerVersion = "0.4";
73 
74   // TODO: interface cmd; class xml tags; fix logger; test class/script
75 
main(String[] args)76   public static void main(String[] args) throws IOException {
77 
78     // haven't figure out how to get below INFO...bad parent.  Pass -v INFO to turn on logging
79     logger.setLevel(Level.parse("WARNING"));
80     processOptions(args);
81 
82     logger.info("initAdb");
83     initAdbConnection();
84     logger.info("openMonkeyConnection");
85     openMonkeyConnection();
86 
87     logger.info("start_script");
88     start_script();
89 
90     logger.info("ScriptRunner.run");
91     ScriptRunner.run(scriptName);
92 
93     logger.info("end_script");
94     end_script();
95     logger.info("closeMonkeyConnection");
96     closeMonkeyConnection();
97   }
98 
99   /**
100    *  Initialize an adb session with a device connected to the host
101    *
102    */
initAdbConnection()103   public static void initAdbConnection() {
104     String adbLocation = "adb";
105     boolean device = false;
106     boolean emulator = false;
107     String serial = null;
108 
109     AndroidDebugBridge.init(false /* debugger support */);
110 
111     try {
112       AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
113           adbLocation, true /* forceNewBridge */);
114 
115       // we can't just ask for the device list right away, as the internal thread getting
116       // them from ADB may not be done getting the first list.
117       // Since we don't really want getDevices() to be blocking, we wait here manually.
118       int count = 0;
119       while (bridge.hasInitialDeviceList() == false) {
120         try {
121           Thread.sleep(100);
122           count++;
123         } catch (InterruptedException e) {
124           // pass
125         }
126 
127         // let's not wait > 10 sec.
128         if (count > 100) {
129           System.err.println("Timeout getting device list!");
130           return;
131         }
132       }
133 
134       // now get the devices
135       IDevice[] devices = bridge.getDevices();
136 
137       if (devices.length == 0) {
138         printAndExit("No devices found!", true /* terminate */);
139       }
140 
141       monkeyDevice = null;
142 
143       if (emulator || device) {
144         for (IDevice d : devices) {
145           // this test works because emulator and device can't both be true at the same
146           // time.
147           if (d.isEmulator() == emulator) {
148             // if we already found a valid target, we print an error and return.
149             if (monkeyDevice != null) {
150               if (emulator) {
151                 printAndExit("Error: more than one emulator launched!",
152                     true /* terminate */);
153               } else {
154                 printAndExit("Error: more than one device connected!",true /* terminate */);
155               }
156             }
157             monkeyDevice = d;
158           }
159         }
160       } else if (serial != null) {
161         for (IDevice d : devices) {
162           if (serial.equals(d.getSerialNumber())) {
163             monkeyDevice = d;
164             break;
165           }
166         }
167       } else {
168         if (devices.length > 1) {
169           printAndExit("Error: more than one emulator or device available!",
170               true /* terminate */);
171         }
172         monkeyDevice = devices[0];
173       }
174 
175       monkeyDevice.createForward(monkeyPort, monkeyPort);
176       String command = "monkey --port " + monkeyPort;
177       monkeyDevice.executeShellCommand(command, new NullOutputReceiver());
178 
179     } catch(IOException e) {
180       e.printStackTrace();
181     }
182   }
183 
184   /**
185    * Open a tcp session over adb with the device to communicate monkey commands
186    */
openMonkeyConnection()187   public static void openMonkeyConnection() {
188     try {
189       InetAddress addr = InetAddress.getByName(monkeyServer);
190       monkeySocket = new Socket(addr, monkeyPort);
191       monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
192       monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
193     } catch (UnknownHostException e) {
194       e.printStackTrace();
195     } catch(IOException e) {
196       e.printStackTrace();
197     }
198   }
199 
200   /**
201    * Close tcp session with the monkey on the device
202    *
203    */
closeMonkeyConnection()204   public static void closeMonkeyConnection() {
205     try {
206       monkeyReader.close();
207       monkeyWriter.close();
208       monkeySocket.close();
209       AndroidDebugBridge.terminate();
210     } catch(IOException e) {
211       e.printStackTrace();
212     }
213   }
214 
215   /**
216    * This is a house cleaning routine to run before starting a script. Puts
217    * the device in a known state and starts recording interesting info.
218    */
start_script()219   public static void start_script() throws IOException {
220     press("menu", false);
221     press("menu", false);
222     press("home", false);
223 
224     // Start recording the script output, might want md5 signature of file for completeness
225     monkeyRecorder = new MonkeyRecorder(scriptName, monkeyRunnerVersion);
226 
227     // Record what device we are running on
228     addDeviceVars();
229     monkeyRecorder.addComment("Script commands");
230   }
231 
232   /**
233    * This is a house cleaning routine to run after finishing a script.
234    * Puts the monkey server in a known state and closes the recording.
235    */
end_script()236   public static void end_script() throws IOException {
237     String command = "done";
238     sendMonkeyEvent(command, false, false);
239 
240     // Stop the recording and zip up the results
241     monkeyRecorder.close();
242   }
243 
244   /** This is a method for scripts to launch an activity on the device
245    *
246    * @param name The name of the activity to launch
247    */
launch_activity(String name)248   public static void launch_activity(String name) throws IOException {
249     System.out.println("Launching: " + name);
250     recordCommand("Launching: " + name);
251     monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n "
252         + name, new NullOutputReceiver());
253     // void return, so no response given, just close the command element in the xml file.
254     monkeyRecorder.endCommand();
255    }
256 
257   /**
258    * Grabs the current state of the screen stores it as a png
259    *
260    * @param tag filename or tag descriptor of the screenshot
261    */
grabscreen(String tag)262   public static void grabscreen(String tag) throws IOException {
263     tag += ".png";
264 
265     try {
266       Thread.sleep(1000);
267       getDeviceImage(monkeyDevice, tag, false);
268     } catch (InterruptedException e) {
269     }
270   }
271 
272   /**
273    * Sleeper method for script to call
274    *
275    * @param msec msecs to sleep for
276    */
sleep(int msec)277   public static void sleep(int msec) throws IOException {
278     try {
279       recordCommand("sleep: " + msec);
280       Thread.sleep(msec);
281       recordResponse("OK");
282     } catch (InterruptedException e) {
283       e.printStackTrace();
284     }
285   }
286 
287   /**
288    * Tap function for scripts to call at a particular x and y location
289    *
290    * @param x x-coordinate
291    * @param y y-coordinate
292    */
tap(int x, int y)293   public static boolean tap(int x, int y) throws IOException {
294     String command = "tap " + x + " " + y;
295     boolean result = sendMonkeyEvent(command);
296     return result;
297   }
298 
299   /**
300    * Press function for scripts to call on a particular button or key
301    *
302    * @param key key to press
303    */
press(String key)304   public static boolean press(String key) throws IOException {
305     return press(key, true);
306   }
307 
308   /**
309    * Press function for scripts to call on a particular button or key
310    *
311    * @param key key to press
312    * @param print whether to send output to user
313    */
press(String key, boolean print)314   private static boolean press(String key, boolean print) throws IOException {
315     String command = "press " + key;
316     boolean result = sendMonkeyEvent(command, print, true);
317     return result;
318   }
319 
320   /**
321    * dpad down function
322    */
down()323   public static boolean down() throws IOException {
324     return press("dpad_down");
325   }
326 
327   /**
328    * dpad up function
329    */
up()330   public static boolean up() throws IOException {
331     return press("dpad_up");
332   }
333 
334   /**
335    * Function to type text on the device
336    *
337    * @param text text to type
338    */
type(String text)339   public static boolean type(String text) throws IOException {
340     boolean result = false;
341     // text might have line ends, which signal new monkey command, so we have to eat and reissue
342     String[] lines = text.split("[\\r\\n]+");
343     for (String line: lines) {
344       result = sendMonkeyEvent("type " + line + "\n");
345     }
346     // return last result.  Should never fail..?
347     return result;
348   }
349 
350   /**
351    * Function to get a static variable from the device
352    *
353    * @param name name of static variable to get
354    */
getvar(String name)355   public static boolean getvar(String name) throws IOException {
356     return sendMonkeyEvent("getvar " + name + "\n");
357   }
358 
359   /**
360    * Function to get the list of static variables from the device
361    */
listvar()362   public static boolean listvar() throws IOException {
363     return sendMonkeyEvent("listvar \n");
364   }
365 
366   /**
367    * This function is the communication bridge between the host and the device.
368    * It sends monkey events and waits for responses over the adb tcp socket.
369    * This version if for all scripted events so that they get recorded and reported to user.
370    *
371    * @param command the monkey command to send to the device
372    */
sendMonkeyEvent(String command)373   private static boolean sendMonkeyEvent(String command) throws IOException {
374     return sendMonkeyEvent(command, true, true);
375   }
376 
377   /**
378    * This function allows the communication bridge between the host and the device
379    * to be invisible to the script for internal needs.
380    * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
381    * Returns on an error, else continues and sets up last response.
382    *
383    * @param command the monkey command to send to the device
384    * @param print whether to print out the responses to the user
385    * @param record whether to put the command in the xml file that stores test outputs
386    */
sendMonkeyEvent(String command, Boolean print, Boolean record)387   private static boolean sendMonkeyEvent(String command, Boolean print, Boolean record) throws IOException {
388     command = command.trim();
389     if (print)
390       System.out.println("MonkeyCommand: " + command);
391     if (record)
392       recordCommand(command);
393     logger.info("Monkey Command: " + command + ".");
394 
395     // send a single command and get the response
396     monkeyWriter.write(command + "\n");
397     monkeyWriter.flush();
398     monkeyResponse = monkeyReader.readLine();
399 
400     if(monkeyResponse != null) {
401       // if a command returns with a response
402       if (print)
403         System.out.println("MonkeyServer: " + monkeyResponse);
404       if (record)
405         recordResponse(monkeyResponse);
406       logger.info("Monkey Response: " + monkeyResponse + ".");
407 
408       // return on error
409       if (monkeyResponse.startsWith("ERROR"))
410         return false;
411 
412       // return on ok
413       if(monkeyResponse.startsWith("OK"))
414         return true;
415 
416       // return on something else?
417       return false;
418     }
419     // didn't get a response...
420     if (print)
421       System.out.println("MonkeyServer: ??no response");
422     if (record)
423       recordResponse("??no response");
424     logger.info("Monkey Response: ??no response.");
425 
426     //return on no response
427     return false;
428   }
429 
430   /**
431    * Record the command in the xml file
432    *
433    * @param command the command sent to the monkey server
434    */
recordCommand(String command)435   private static void recordCommand(String command) throws IOException {
436     if (monkeyRecorder != null) {                       // don't record setup junk
437       monkeyRecorder.startCommand();
438       monkeyRecorder.addInput(command);
439     }
440   }
441 
442   /**
443    * Record the response in the xml file
444    *
445    * @param response the response sent by the monkey server
446    */
recordResponse(String response)447   private static void recordResponse(String response) throws IOException {
448     recordResponse(response, "");
449   }
450 
451   /**
452    * Record the response and the filename in the xml file, store the file (to be zipped up later)
453    *
454    * @param response the response sent by the monkey server
455    * @param filename the filename of a file to be time stamped, recorded in the xml file and stored
456    */
recordResponse(String response, String filename)457   private static void recordResponse(String response, String filename) throws IOException {
458     if (monkeyRecorder != null) {                    // don't record setup junk
459       monkeyRecorder.addResult(response, filename);  // ignores file if filename empty
460       monkeyRecorder.endCommand();
461     }
462   }
463 
464   /**
465    * Add the device variables to the xml file in monkeyRecorder.
466    * The results get added as device_var tags in the script_run tag
467    */
addDeviceVars()468   private static void addDeviceVars() throws IOException {
469     monkeyRecorder.addComment("Device specific variables");
470     sendMonkeyEvent("listvar \n", false, false);
471     if (monkeyResponse.startsWith("OK:")) {
472       // peel off "OK:" string and get the individual var names
473       String[] varNames = monkeyResponse.substring(3).split("\\s+");
474       // grab all the individual var values
475       for (String name: varNames) {
476         sendMonkeyEvent("getvar " + name, false, false);
477         if(monkeyResponse != null) {
478           if (monkeyResponse.startsWith("OK") ) {
479             if (monkeyResponse.length() > 2) {
480               monkeyRecorder.addDeviceVar(name, monkeyResponse.substring(3));
481             } else {
482               // only got OK - good variable but no value
483               monkeyRecorder.addDeviceVar(name, "null");
484             }
485           } else {
486             // error returned - couldn't get var value for name... include error return
487             monkeyRecorder.addDeviceVar(name, monkeyResponse);
488           }
489         } else {
490           // no monkeyResponse - bad variable with no value
491           monkeyRecorder.addDeviceVar(name, "null");
492         }
493       }
494     } else {
495       // it's an error, can't find variable names...
496       monkeyRecorder.addAttribute("listvar", monkeyResponse);
497     }
498   }
499 
500   /**
501    * Process the command-line options
502    *
503    * @return Returns true if options were parsed with no apparent errors.
504    */
processOptions(String[] args)505   private static void processOptions(String[] args) {
506     // parse command line parameters.
507     int index = 0;
508 
509     do {
510       String argument = args[index++];
511 
512       if ("-s".equals(argument)) {
513         if(index == args.length) {
514           printUsageAndQuit("Missing Server after -s");
515         }
516 
517         monkeyServer = args[index++];
518 
519       } else if ("-p".equals(argument)) {
520         // quick check on the next argument.
521         if (index == args.length) {
522           printUsageAndQuit("Missing Server port after -p");
523         }
524 
525         monkeyPort = Integer.parseInt(args[index++]);
526 
527       } else if ("-v".equals(argument)) {
528         // quick check on the next argument.
529         if (index == args.length) {
530           printUsageAndQuit("Missing Log Level after -v");
531         }
532 
533         Level level = Level.parse(args[index++]);
534         logger.setLevel(level);
535         level = logger.getLevel();
536         System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
537         System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
538 
539       } else if (argument.startsWith("-")) {
540         // we have an unrecognized argument.
541         printUsageAndQuit("Unrecognized argument: " + argument + ".");
542 
543         monkeyPort = Integer.parseInt(args[index++]);
544 
545       } else {
546         // get the filepath of the script to run.  This will be the last undashed argument.
547         scriptName = argument;
548       }
549     } while (index < args.length);
550   }
551 
552   /*
553    * Grab an image from an ADB-connected device.
554    */
getDeviceImage(IDevice device, String filepath, boolean landscape)555   private static void getDeviceImage(IDevice device, String filepath, boolean landscape)
556   throws IOException {
557     RawImage rawImage;
558     recordCommand("grabscreen");
559     System.out.println("Grabbing Screeshot: " + filepath + ".");
560 
561     try {
562       rawImage = device.getScreenshot();
563     }
564     catch (IOException ioe) {
565       recordResponse("No frame buffer", "");
566       printAndExit("Unable to get frame buffer: " + ioe.getMessage(), true /* terminate */);
567       return;
568     }
569 
570     // device/adb not available?
571     if (rawImage == null) {
572       recordResponse("No image", "");
573       return;
574     }
575 
576     assert rawImage.bpp == 16;
577 
578     BufferedImage image;
579 
580     logger.info("Raw Image - height: " + rawImage.height + ", width: " + rawImage.width);
581 
582     if (landscape) {
583       // convert raw data to an Image
584       image = new BufferedImage(rawImage.height, rawImage.width,
585           BufferedImage.TYPE_INT_ARGB);
586 
587       byte[] buffer = rawImage.data;
588       int index = 0;
589       for (int y = 0 ; y < rawImage.height ; y++) {
590         for (int x = 0 ; x < rawImage.width ; x++) {
591 
592           int value = buffer[index++] & 0x00FF;
593           value |= (buffer[index++] << 8) & 0x0FF00;
594 
595           int r = ((value >> 11) & 0x01F) << 3;
596           int g = ((value >> 5) & 0x03F) << 2;
597           int b = ((value >> 0) & 0x01F) << 3;
598 
599           value = 0xFF << 24 | r << 16 | g << 8 | b;
600 
601           image.setRGB(y, rawImage.width - x - 1, value);
602         }
603       }
604     } else {
605       // convert raw data to an Image
606       image = new BufferedImage(rawImage.width, rawImage.height,
607           BufferedImage.TYPE_INT_ARGB);
608 
609       byte[] buffer = rawImage.data;
610       int index = 0;
611       for (int y = 0 ; y < rawImage.height ; y++) {
612         for (int x = 0 ; x < rawImage.width ; x++) {
613 
614           int value = buffer[index++] & 0x00FF;
615           value |= (buffer[index++] << 8) & 0x0FF00;
616 
617           int r = ((value >> 11) & 0x01F) << 3;
618           int g = ((value >> 5) & 0x03F) << 2;
619           int b = ((value >> 0) & 0x01F) << 3;
620 
621           value = 0xFF << 24 | r << 16 | g << 8 | b;
622 
623           image.setRGB(x, y, value);
624         }
625       }
626     }
627 
628     if (!ImageIO.write(image, "png", new File(filepath))) {
629       recordResponse("No png writer", "");
630       throw new IOException("Failed to find png writer");
631     }
632     recordResponse("OK", filepath);
633   }
634 
printUsageAndQuit(String message)635   private static void printUsageAndQuit(String message) {
636     // 80 cols marker:  01234567890123456789012345678901234567890123456789012345678901234567890123456789
637     System.out.println(message);
638     System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
639     System.out.println("");
640     System.out.println("    -s      MonkeyServer IP Address.");
641     System.out.println("    -p      MonkeyServer TCP Port.");
642     System.out.println("    -v      MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
643     System.out.println("");
644     System.out.println("");
645 
646     System.exit(1);
647   }
648 
printAndExit(String message, boolean terminate)649   private static void printAndExit(String message, boolean terminate) {
650     System.out.println(message);
651     if (terminate) {
652       AndroidDebugBridge.terminate();
653     }
654     System.exit(1);
655   }
656 }
657