• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.commands.monkey;
18 
19 import android.content.ComponentName;
20 import android.os.SystemClock;
21 import android.view.KeyEvent;
22 import android.view.MotionEvent;
23 
24 import java.io.BufferedReader;
25 import java.io.DataInputStream;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.util.NoSuchElementException;
30 import java.util.Random;
31 import android.util.Log;
32 
33 /**
34  * monkey event queue. It takes a script to produce events sample script format:
35  *
36  * <pre>
37  * type= raw events
38  * count= 10
39  * speed= 1.0
40  * start data &gt;&gt;
41  * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314,0.06666667,0,0.0,0.0,65539,0)
42  * captureDispatchKey(5113146,5113146,0,20,0,0,0,0)
43  * captureDispatchFlip(true)
44  * ...
45  * </pre>
46  */
47 public class MonkeySourceScript implements MonkeyEventSource {
48     private int mEventCountInScript = 0; // total number of events in the file
49 
50     private int mVerbose = 0;
51 
52     private double mSpeed = 1.0;
53 
54     private String mScriptFileName;
55 
56     private MonkeyEventQueue mQ;
57 
58     private static final String HEADER_COUNT = "count=";
59 
60     private static final String HEADER_SPEED = "speed=";
61 
62     private long mLastRecordedDownTimeKey = 0;
63 
64     private long mLastRecordedDownTimeMotion = 0;
65 
66     private long mLastExportDownTimeKey = 0;
67 
68     private long mLastExportDownTimeMotion = 0;
69 
70     private long mLastExportEventTime = -1;
71 
72     private long mLastRecordedEventTime = -1;
73 
74     private static final boolean THIS_DEBUG = false;
75 
76     // a parameter that compensates the difference of real elapsed time and
77     // time in theory
78     private static final long SLEEP_COMPENSATE_DIFF = 16;
79 
80     // maximum number of events that we read at one time
81     private static final int MAX_ONE_TIME_READS = 100;
82 
83     // event key word in the capture log
84     private static final String EVENT_KEYWORD_POINTER = "DispatchPointer";
85 
86     private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball";
87 
88     private static final String EVENT_KEYWORD_KEY = "DispatchKey";
89 
90     private static final String EVENT_KEYWORD_FLIP = "DispatchFlip";
91 
92     private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress";
93 
94     private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity";
95 
96     private static final String EVENT_KEYWORD_INSTRUMENTATION = "LaunchInstrumentation";
97 
98     private static final String EVENT_KEYWORD_WAIT = "UserWait";
99 
100     private static final String EVENT_KEYWORD_LONGPRESS = "LongPress";
101 
102     private static final String EVENT_KEYWORD_POWERLOG = "PowerLog";
103 
104     private static final String EVENT_KEYWORD_WRITEPOWERLOG = "WriteLog";
105 
106     private static final String EVENT_KEYWORD_RUNCMD = "RunCmd";
107 
108     private static final String EVENT_KEYWORD_TAP = "Tap";
109 
110     private static final String EVENT_KEYWORD_PROFILE_WAIT = "ProfileWait";
111 
112     private static final String EVENT_KEYWORD_DEVICE_WAKEUP = "DeviceWakeUp";
113 
114     private static final String EVENT_KEYWORD_INPUT_STRING = "DispatchString";
115 
116     private static final String EVENT_KEYWORD_PRESSANDHOLD = "PressAndHold";
117 
118     private static final String EVENT_KEYWORD_DRAG = "Drag";
119 
120     private static final String EVENT_KEYWORD_PINCH_ZOOM = "PinchZoom";
121 
122     private static final String EVENT_KEYWORD_START_FRAMERATE_CAPTURE = "StartCaptureFramerate";
123 
124     private static final String EVENT_KEYWORD_END_FRAMERATE_CAPTURE = "EndCaptureFramerate";
125 
126     // a line at the end of the header
127     private static final String STARTING_DATA_LINE = "start data >>";
128 
129     private boolean mFileOpened = false;
130 
131     private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long
132 
133     private long mProfileWaitTime = 5000; //Wait time for each user profile
134 
135     private long mDeviceSleepTime = 30000; //Device sleep time
136 
137     FileInputStream mFStream;
138 
139     DataInputStream mInputStream;
140 
141     BufferedReader mBufferedReader;
142 
143     /**
144      * Creates a MonkeySourceScript instance.
145      *
146      * @param filename The filename of the script (on the device).
147      * @param throttle The amount of time in ms to sleep between events.
148      */
MonkeySourceScript(Random random, String filename, long throttle, boolean randomizeThrottle, long profileWaitTime, long deviceSleepTime)149     public MonkeySourceScript(Random random, String filename, long throttle,
150             boolean randomizeThrottle, long profileWaitTime, long deviceSleepTime) {
151         mScriptFileName = filename;
152         mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
153         mProfileWaitTime = profileWaitTime;
154         mDeviceSleepTime = deviceSleepTime;
155     }
156 
157     /**
158      * Resets the globals used to timeshift events.
159      */
resetValue()160     private void resetValue() {
161         mLastRecordedDownTimeKey = 0;
162         mLastRecordedDownTimeMotion = 0;
163         mLastRecordedEventTime = -1;
164         mLastExportDownTimeKey = 0;
165         mLastExportDownTimeMotion = 0;
166         mLastExportEventTime = -1;
167     }
168 
169     /**
170      * Reads the header of the script file.
171      *
172      * @return True if the file header could be parsed, and false otherwise.
173      * @throws IOException If there was an error reading the file.
174      */
readHeader()175     private boolean readHeader() throws IOException {
176         mFileOpened = true;
177 
178         mFStream = new FileInputStream(mScriptFileName);
179         mInputStream = new DataInputStream(mFStream);
180         mBufferedReader = new BufferedReader(new InputStreamReader(mInputStream));
181 
182         String line;
183 
184         while ((line = mBufferedReader.readLine()) != null) {
185             line = line.trim();
186 
187             if (line.indexOf(HEADER_COUNT) >= 0) {
188                 try {
189                     String value = line.substring(HEADER_COUNT.length() + 1).trim();
190                     mEventCountInScript = Integer.parseInt(value);
191                 } catch (NumberFormatException e) {
192                     System.err.println(e);
193                     return false;
194                 }
195             } else if (line.indexOf(HEADER_SPEED) >= 0) {
196                 try {
197                     String value = line.substring(HEADER_COUNT.length() + 1).trim();
198                     mSpeed = Double.parseDouble(value);
199                 } catch (NumberFormatException e) {
200                     System.err.println(e);
201                     return false;
202                 }
203             } else if (line.indexOf(STARTING_DATA_LINE) >= 0) {
204                 return true;
205             }
206         }
207 
208         return false;
209     }
210 
211     /**
212      * Reads a number of lines and passes the lines to be processed.
213      *
214      * @return The number of lines read.
215      * @throws IOException If there was an error reading the file.
216      */
readLines()217     private int readLines() throws IOException {
218         String line;
219         for (int i = 0; i < MAX_ONE_TIME_READS; i++) {
220             line = mBufferedReader.readLine();
221             if (line == null) {
222                 return i;
223             }
224             line.trim();
225             processLine(line);
226         }
227         return MAX_ONE_TIME_READS;
228     }
229 
230 
231 
232 
233     /**
234      * Creates an event and adds it to the event queue. If the parameters are
235      * not understood, they are ignored and no events are added.
236      *
237      * @param s The entire string from the script file.
238      * @param args An array of arguments extracted from the script file line.
239      */
handleEvent(String s, String[] args)240     private void handleEvent(String s, String[] args) {
241         // Handle key event
242         if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) {
243             try {
244                 System.out.println(" old key\n");
245                 long downTime = Long.parseLong(args[0]);
246                 long eventTime = Long.parseLong(args[1]);
247                 int action = Integer.parseInt(args[2]);
248                 int code = Integer.parseInt(args[3]);
249                 int repeat = Integer.parseInt(args[4]);
250                 int metaState = Integer.parseInt(args[5]);
251                 int device = Integer.parseInt(args[6]);
252                 int scancode = Integer.parseInt(args[7]);
253 
254                 MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime, action, code, repeat,
255                         metaState, device, scancode);
256                 System.out.println(" Key code " + code + "\n");
257 
258                 mQ.addLast(e);
259                 System.out.println("Added key up \n");
260             } catch (NumberFormatException e) {
261             }
262             return;
263         }
264 
265         // Handle trackball or pointer events
266         if ((s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0)
267                 && args.length == 12) {
268             try {
269                 long downTime = Long.parseLong(args[0]);
270                 long eventTime = Long.parseLong(args[1]);
271                 int action = Integer.parseInt(args[2]);
272                 float x = Float.parseFloat(args[3]);
273                 float y = Float.parseFloat(args[4]);
274                 float pressure = Float.parseFloat(args[5]);
275                 float size = Float.parseFloat(args[6]);
276                 int metaState = Integer.parseInt(args[7]);
277                 float xPrecision = Float.parseFloat(args[8]);
278                 float yPrecision = Float.parseFloat(args[9]);
279                 int device = Integer.parseInt(args[10]);
280                 int edgeFlags = Integer.parseInt(args[11]);
281 
282                 MonkeyMotionEvent e;
283                 if (s.indexOf("Pointer") > 0) {
284                     e = new MonkeyTouchEvent(action);
285                 } else {
286                     e = new MonkeyTrackballEvent(action);
287                 }
288 
289                 e.setDownTime(downTime)
290                         .setEventTime(eventTime)
291                         .setMetaState(metaState)
292                         .setPrecision(xPrecision, yPrecision)
293                         .setDeviceId(device)
294                         .setEdgeFlags(edgeFlags)
295                         .addPointer(0, x, y, pressure, size);
296                 mQ.addLast(e);
297             } catch (NumberFormatException e) {
298             }
299             return;
300         }
301 
302         // Handle tap event
303         if ((s.indexOf(EVENT_KEYWORD_TAP) >= 0) && args.length >= 2) {
304             try {
305                 float x = Float.parseFloat(args[0]);
306                 float y = Float.parseFloat(args[1]);
307                 long tapDuration = 0;
308                 if (args.length == 3) {
309                     tapDuration = Long.parseLong(args[2]);
310                 }
311 
312                 // Set the default parameters
313                 long downTime = SystemClock.uptimeMillis();
314                 MonkeyMotionEvent e1 = new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
315                         .setDownTime(downTime)
316                         .setEventTime(downTime)
317                         .addPointer(0, x, y, 1, 5);
318                 mQ.addLast(e1);
319                 if (tapDuration > 0){
320                     mQ.addLast(new MonkeyWaitEvent(tapDuration));
321                 }
322                 MonkeyMotionEvent e2 = new MonkeyTouchEvent(MotionEvent.ACTION_UP)
323                         .setDownTime(downTime)
324                         .setEventTime(downTime)
325                         .addPointer(0, x, y, 1, 5);
326                 mQ.addLast(e2);
327             } catch (NumberFormatException e) {
328                 System.err.println("// " + e.toString());
329             }
330             return;
331         }
332 
333         //Handle the press and hold
334         if ((s.indexOf(EVENT_KEYWORD_PRESSANDHOLD) >= 0) && args.length == 3) {
335             try {
336                 float x = Float.parseFloat(args[0]);
337                 float y = Float.parseFloat(args[1]);
338                 long pressDuration = Long.parseLong(args[2]);
339 
340                 // Set the default parameters
341                 long downTime = SystemClock.uptimeMillis();
342 
343                 MonkeyMotionEvent e1 = new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
344                         .setDownTime(downTime)
345                         .setEventTime(downTime)
346                         .addPointer(0, x, y, 1, 5);
347                 MonkeyWaitEvent e2 = new MonkeyWaitEvent(pressDuration);
348                 MonkeyMotionEvent e3 = new MonkeyTouchEvent(MotionEvent.ACTION_UP)
349                         .setDownTime(downTime + pressDuration)
350                         .setEventTime(downTime + pressDuration)
351                         .addPointer(0, x, y, 1, 5);
352                 mQ.addLast(e1);
353                 mQ.addLast(e2);
354                 mQ.addLast(e2);
355 
356             } catch (NumberFormatException e) {
357                 System.err.println("// " + e.toString());
358             }
359             return;
360         }
361 
362         // Handle drag event
363         if ((s.indexOf(EVENT_KEYWORD_DRAG) >= 0) && args.length == 5) {
364             float xStart = Float.parseFloat(args[0]);
365             float yStart = Float.parseFloat(args[1]);
366             float xEnd = Float.parseFloat(args[2]);
367             float yEnd = Float.parseFloat(args[3]);
368             int stepCount = Integer.parseInt(args[4]);
369 
370             float x = xStart;
371             float y = yStart;
372             long downTime = SystemClock.uptimeMillis();
373             long eventTime = SystemClock.uptimeMillis();
374 
375             if (stepCount > 0) {
376                 float xStep = (xEnd - xStart) / stepCount;
377                 float yStep = (yEnd - yStart) / stepCount;
378 
379                 MonkeyMotionEvent e =
380                         new MonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downTime)
381                                 .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
382                 mQ.addLast(e);
383 
384                 for (int i = 0; i < stepCount; ++i) {
385                     x += xStep;
386                     y += yStep;
387                     eventTime = SystemClock.uptimeMillis();
388                     e = new MonkeyTouchEvent(MotionEvent.ACTION_MOVE).setDownTime(downTime)
389                         .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
390                     mQ.addLast(e);
391                 }
392 
393                 eventTime = SystemClock.uptimeMillis();
394                 e = new MonkeyTouchEvent(MotionEvent.ACTION_UP).setDownTime(downTime)
395                     .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
396                 mQ.addLast(e);
397             }
398         }
399 
400         // Handle pinch or zoom action
401         if ((s.indexOf(EVENT_KEYWORD_PINCH_ZOOM) >= 0) && args.length == 9) {
402             //Parse the parameters
403             float pt1xStart = Float.parseFloat(args[0]);
404             float pt1yStart = Float.parseFloat(args[1]);
405             float pt1xEnd = Float.parseFloat(args[2]);
406             float pt1yEnd = Float.parseFloat(args[3]);
407 
408             float pt2xStart = Float.parseFloat(args[4]);
409             float pt2yStart = Float.parseFloat(args[5]);
410             float pt2xEnd = Float.parseFloat(args[6]);
411             float pt2yEnd = Float.parseFloat(args[7]);
412 
413             int stepCount = Integer.parseInt(args[8]);
414 
415             float x1 = pt1xStart;
416             float y1 = pt1yStart;
417             float x2 = pt2xStart;
418             float y2 = pt2yStart;
419 
420             long downTime = SystemClock.uptimeMillis();
421             long eventTime = SystemClock.uptimeMillis();
422 
423             if (stepCount > 0) {
424                 float pt1xStep = (pt1xEnd - pt1xStart) / stepCount;
425                 float pt1yStep = (pt1yEnd - pt1yStart) / stepCount;
426 
427                 float pt2xStep = (pt2xEnd - pt2xStart) / stepCount;
428                 float pt2yStep = (pt2yEnd - pt2yStart) / stepCount;
429 
430                 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downTime)
431                         .setEventTime(eventTime).addPointer(0, x1, y1, 1, 5));
432 
433                 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN
434                         | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)).setDownTime(downTime)
435                         .addPointer(0, x1, y1).addPointer(1, x2, y2).setIntermediateNote(true));
436 
437                 for (int i = 0; i < stepCount; ++i) {
438                     x1 += pt1xStep;
439                     y1 += pt1yStep;
440                     x2 += pt2xStep;
441                     y2 += pt2yStep;
442 
443                     eventTime = SystemClock.uptimeMillis();
444                     mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE).setDownTime(downTime)
445                             .setEventTime(eventTime).addPointer(0, x1, y1, 1, 5).addPointer(1, x2,
446                                     y2, 1, 5));
447                 }
448                 eventTime = SystemClock.uptimeMillis();
449                 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_UP)
450                         .setDownTime(downTime).setEventTime(eventTime).addPointer(0, x1, y1)
451                         .addPointer(1, x2, y2));
452             }
453         }
454 
455         // Handle flip events
456         if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0 && args.length == 1) {
457             boolean keyboardOpen = Boolean.parseBoolean(args[0]);
458             MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen);
459             mQ.addLast(e);
460         }
461 
462         // Handle launch events
463         if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0 && args.length >= 2) {
464             String pkg_name = args[0];
465             String cl_name = args[1];
466             long alarmTime = 0;
467 
468             ComponentName mApp = new ComponentName(pkg_name, cl_name);
469 
470             if (args.length > 2) {
471                 try {
472                     alarmTime = Long.parseLong(args[2]);
473                 } catch (NumberFormatException e) {
474                     System.err.println("// " + e.toString());
475                     return;
476                 }
477             }
478 
479             if (args.length == 2) {
480                 MonkeyActivityEvent e = new MonkeyActivityEvent(mApp);
481                 mQ.addLast(e);
482             } else {
483                 MonkeyActivityEvent e = new MonkeyActivityEvent(mApp, alarmTime);
484                 mQ.addLast(e);
485             }
486             return;
487         }
488 
489         //Handle the device wake up event
490         if (s.indexOf(EVENT_KEYWORD_DEVICE_WAKEUP) >= 0){
491             String pkg_name = "com.google.android.powerutil";
492             String cl_name = "com.google.android.powerutil.WakeUpScreen";
493             long deviceSleepTime = mDeviceSleepTime;
494 
495             ComponentName mApp = new ComponentName(pkg_name, cl_name);
496             MonkeyActivityEvent e1 = new MonkeyActivityEvent(mApp, deviceSleepTime);
497             mQ.addLast(e1);
498 
499             //Add the wait event after the device sleep event so that the monkey
500             //can continue after the device wake up.
501             MonkeyWaitEvent e2 = new MonkeyWaitEvent(deviceSleepTime + 3000);
502             mQ.addLast(e2);
503             return;
504         }
505 
506        // Handle launch instrumentation events
507         if (s.indexOf(EVENT_KEYWORD_INSTRUMENTATION) >= 0 && args.length == 2) {
508             String test_name = args[0];
509             String runner_name = args[1];
510             MonkeyInstrumentationEvent e = new MonkeyInstrumentationEvent(test_name, runner_name);
511             mQ.addLast(e);
512             return;
513         }
514 
515         // Handle wait events
516         if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0 && args.length == 1) {
517             try {
518                 long sleeptime = Integer.parseInt(args[0]);
519                 MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime);
520                 mQ.addLast(e);
521             } catch (NumberFormatException e) {
522             }
523             return;
524         }
525 
526 
527         // Handle the profile wait time
528         if (s.indexOf(EVENT_KEYWORD_PROFILE_WAIT) >= 0) {
529             MonkeyWaitEvent e = new MonkeyWaitEvent(mProfileWaitTime);
530             mQ.addLast(e);
531             return;
532         }
533 
534         // Handle keypress events
535         if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0 && args.length == 1) {
536             String key_name = args[0];
537             int keyCode = MonkeySourceRandom.getKeyCode(key_name);
538             MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode);
539             mQ.addLast(e);
540             e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode);
541             mQ.addLast(e);
542             return;
543         }
544 
545         // Handle longpress events
546         if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) {
547             MonkeyKeyEvent e;
548             e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
549             mQ.addLast(e);
550             MonkeyWaitEvent we = new MonkeyWaitEvent(LONGPRESS_WAIT_TIME);
551             mQ.addLast(we);
552             e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
553             mQ.addLast(e);
554         }
555 
556         //The power log event is mainly for the automated power framework
557         if (s.indexOf(EVENT_KEYWORD_POWERLOG) >= 0 && args.length > 0) {
558             String power_log_type = args[0];
559             String test_case_status;
560 
561             if (args.length == 1){
562                 MonkeyPowerEvent e = new MonkeyPowerEvent(power_log_type);
563                 mQ.addLast(e);
564             } else if (args.length == 2){
565                 test_case_status = args[1];
566                 MonkeyPowerEvent e = new MonkeyPowerEvent(power_log_type, test_case_status);
567                 mQ.addLast(e);
568             }
569         }
570 
571         //Write power log to sdcard
572         if (s.indexOf(EVENT_KEYWORD_WRITEPOWERLOG) >= 0) {
573             MonkeyPowerEvent e = new MonkeyPowerEvent();
574             mQ.addLast(e);
575         }
576 
577       //Run the shell command
578         if (s.indexOf(EVENT_KEYWORD_RUNCMD) >= 0 && args.length == 1) {
579             String cmd = args[0];
580             MonkeyCommandEvent e = new MonkeyCommandEvent(cmd);
581             mQ.addLast(e);
582         }
583 
584         //Input the string through the shell command
585         if (s.indexOf(EVENT_KEYWORD_INPUT_STRING) >= 0 && args.length == 1) {
586             String input = args[0];
587             String cmd = "input text " + input;
588             MonkeyCommandEvent e = new MonkeyCommandEvent(cmd);
589             mQ.addLast(e);
590             return;
591         }
592 
593         if (s.indexOf(EVENT_KEYWORD_START_FRAMERATE_CAPTURE) >= 0) {
594             MonkeyGetFrameRateEvent e = new MonkeyGetFrameRateEvent("start");
595             mQ.addLast(e);
596             return;
597         }
598 
599         if (s.indexOf(EVENT_KEYWORD_END_FRAMERATE_CAPTURE) >= 0 && args.length == 1) {
600             String input = args[0];
601             MonkeyGetFrameRateEvent e = new MonkeyGetFrameRateEvent("end", input);
602             mQ.addLast(e);
603             return;
604         }
605 
606 
607 
608     }
609 
610     /**
611      * Extracts an event and a list of arguments from a line. If the line does
612      * not match the format required, it is ignored.
613      *
614      * @param line A string in the form {@code cmd(arg1,arg2,arg3)}.
615      */
processLine(String line)616     private void processLine(String line) {
617         int index1 = line.indexOf('(');
618         int index2 = line.indexOf(')');
619 
620         if (index1 < 0 || index2 < 0) {
621             return;
622         }
623 
624         String[] args = line.substring(index1 + 1, index2).split(",");
625 
626         for (int i = 0; i < args.length; i++) {
627             args[i] = args[i].trim();
628         }
629 
630         handleEvent(line, args);
631     }
632 
633     /**
634      * Closes the script file.
635      *
636      * @throws IOException If there was an error closing the file.
637      */
closeFile()638     private void closeFile() throws IOException {
639         mFileOpened = false;
640 
641         try {
642             mFStream.close();
643             mInputStream.close();
644         } catch (NullPointerException e) {
645             // File was never opened so it can't be closed.
646         }
647     }
648 
649     /**
650      * Read next batch of events from the script file into the event queue.
651      * Checks if the script is open and then reads the next MAX_ONE_TIME_READS
652      * events or reads until the end of the file. If no events are read, then
653      * the script is closed.
654      *
655      * @throws IOException If there was an error reading the file.
656      */
readNextBatch()657     private void readNextBatch() throws IOException {
658         int linesRead = 0;
659 
660         if (THIS_DEBUG) {
661             System.out.println("readNextBatch(): reading next batch of events");
662         }
663 
664         if (!mFileOpened) {
665             resetValue();
666             readHeader();
667         }
668 
669         linesRead = readLines();
670 
671         if (linesRead == 0) {
672             closeFile();
673         }
674     }
675 
676     /**
677      * Sleep for a period of given time. Used to introduce latency between
678      * events.
679      *
680      * @param time The amount of time to sleep in ms
681      */
needSleep(long time)682     private void needSleep(long time) {
683         if (time < 1) {
684             return;
685         }
686         try {
687             Thread.sleep(time);
688         } catch (InterruptedException e) {
689         }
690     }
691 
692     /**
693      * Checks if the file can be opened and if the header is valid.
694      *
695      * @return True if the file exists and the header is valid, false otherwise.
696      */
validate()697     public boolean validate() {
698         boolean validHeader;
699         try {
700             validHeader = readHeader();
701             closeFile();
702         } catch (IOException e) {
703             return false;
704         }
705 
706         if (mVerbose > 0) {
707             System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed);
708         }
709         return validHeader;
710     }
711 
setVerbose(int verbose)712     public void setVerbose(int verbose) {
713         mVerbose = verbose;
714     }
715 
716     /**
717      * Adjust key downtime and eventtime according to both recorded values and
718      * current system time.
719      *
720      * @param e A KeyEvent
721      */
adjustKeyEventTime(MonkeyKeyEvent e)722     private void adjustKeyEventTime(MonkeyKeyEvent e) {
723         if (e.getEventTime() < 0) {
724             return;
725         }
726         long thisDownTime = 0;
727         long thisEventTime = 0;
728         long expectedDelay = 0;
729 
730         if (mLastRecordedEventTime <= 0) {
731             // first time event
732             thisDownTime = SystemClock.uptimeMillis();
733             thisEventTime = thisDownTime;
734         } else {
735             if (e.getDownTime() != mLastRecordedDownTimeKey) {
736                 thisDownTime = e.getDownTime();
737             } else {
738                 thisDownTime = mLastExportDownTimeKey;
739             }
740             expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed);
741             thisEventTime = mLastExportEventTime + expectedDelay;
742             // add sleep to simulate everything in recording
743             needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
744         }
745         mLastRecordedDownTimeKey = e.getDownTime();
746         mLastRecordedEventTime = e.getEventTime();
747         e.setDownTime(thisDownTime);
748         e.setEventTime(thisEventTime);
749         mLastExportDownTimeKey = thisDownTime;
750         mLastExportEventTime = thisEventTime;
751     }
752 
753     /**
754      * Adjust motion downtime and eventtime according to current system time.
755      *
756      * @param e A KeyEvent
757      */
adjustMotionEventTime(MonkeyMotionEvent e)758     private void adjustMotionEventTime(MonkeyMotionEvent e) {
759         long updatedDownTime = 0;
760 
761         if (e.getEventTime() < 0) {
762             return;
763         }
764         updatedDownTime = SystemClock.uptimeMillis();
765         e.setDownTime(updatedDownTime);
766         e.setEventTime(updatedDownTime);
767     }
768 
769     /**
770      * Gets the next event to be injected from the script. If the event queue is
771      * empty, reads the next n events from the script into the queue, where n is
772      * the lesser of the number of remaining events and the value specified by
773      * MAX_ONE_TIME_READS. If the end of the file is reached, no events are
774      * added to the queue and null is returned.
775      *
776      * @return The first event in the event queue or null if the end of the file
777      *         is reached or if an error is encountered reading the file.
778      */
getNextEvent()779     public MonkeyEvent getNextEvent() {
780         long recordedEventTime = -1;
781         MonkeyEvent ev;
782 
783         if (mQ.isEmpty()) {
784             try {
785                 readNextBatch();
786             } catch (IOException e) {
787                 return null;
788             }
789         }
790 
791         try {
792             ev = mQ.getFirst();
793             mQ.removeFirst();
794         } catch (NoSuchElementException e) {
795             return null;
796         }
797 
798         if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
799             adjustKeyEventTime((MonkeyKeyEvent) ev);
800         } else if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_TOUCH
801                 || ev.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
802             adjustMotionEventTime((MonkeyMotionEvent) ev);
803         }
804         return ev;
805     }
806 }
807