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
17package com.android.commands.monkey;
18
19import android.content.ComponentName;
20import android.os.SystemClock;
21import android.view.KeyEvent;
22import android.view.MotionEvent;
23import android.view.Surface;
24
25import java.io.BufferedReader;
26import java.io.DataInputStream;
27import java.io.FileInputStream;
28import java.io.IOException;
29import java.io.InputStreamReader;
30import java.util.NoSuchElementException;
31import java.util.Random;
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 */
47public 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    // process scripts in line-by-line mode (true) or batch processing mode (false)
75    private boolean mReadScriptLineByLine = false;
76
77    private static final boolean THIS_DEBUG = false;
78
79    // a parameter that compensates the difference of real elapsed time and
80    // time in theory
81    private static final long SLEEP_COMPENSATE_DIFF = 16;
82
83    // if this header is present, scripts are read and processed in line-by-line mode
84    private static final String HEADER_LINE_BY_LINE = "linebyline";
85
86    // maximum number of events that we read at one time
87    private static final int MAX_ONE_TIME_READS = 100;
88
89    // event key word in the capture log
90    private static final String EVENT_KEYWORD_POINTER = "DispatchPointer";
91
92    private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball";
93
94    private static final String EVENT_KEYWORD_ROTATION = "RotateScreen";
95
96    private static final String EVENT_KEYWORD_KEY = "DispatchKey";
97
98    private static final String EVENT_KEYWORD_FLIP = "DispatchFlip";
99
100    private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress";
101
102    private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity";
103
104    private static final String EVENT_KEYWORD_INSTRUMENTATION = "LaunchInstrumentation";
105
106    private static final String EVENT_KEYWORD_WAIT = "UserWait";
107
108    private static final String EVENT_KEYWORD_LONGPRESS = "LongPress";
109
110    private static final String EVENT_KEYWORD_POWERLOG = "PowerLog";
111
112    private static final String EVENT_KEYWORD_WRITEPOWERLOG = "WriteLog";
113
114    private static final String EVENT_KEYWORD_RUNCMD = "RunCmd";
115
116    private static final String EVENT_KEYWORD_TAP = "Tap";
117
118    private static final String EVENT_KEYWORD_PROFILE_WAIT = "ProfileWait";
119
120    private static final String EVENT_KEYWORD_DEVICE_WAKEUP = "DeviceWakeUp";
121
122    private static final String EVENT_KEYWORD_INPUT_STRING = "DispatchString";
123
124    private static final String EVENT_KEYWORD_PRESSANDHOLD = "PressAndHold";
125
126    private static final String EVENT_KEYWORD_DRAG = "Drag";
127
128    private static final String EVENT_KEYWORD_PINCH_ZOOM = "PinchZoom";
129
130    private static final String EVENT_KEYWORD_START_FRAMERATE_CAPTURE = "StartCaptureFramerate";
131
132    private static final String EVENT_KEYWORD_END_FRAMERATE_CAPTURE = "EndCaptureFramerate";
133
134    private static final String EVENT_KEYWORD_START_APP_FRAMERATE_CAPTURE =
135            "StartCaptureAppFramerate";
136
137    private static final String EVENT_KEYWORD_END_APP_FRAMERATE_CAPTURE = "EndCaptureAppFramerate";
138
139    // a line at the end of the header
140    private static final String STARTING_DATA_LINE = "start data >>";
141
142    private boolean mFileOpened = false;
143
144    private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long
145
146    private long mProfileWaitTime = 5000; //Wait time for each user profile
147
148    private long mDeviceSleepTime = 30000; //Device sleep time
149
150    FileInputStream mFStream;
151
152    DataInputStream mInputStream;
153
154    BufferedReader mBufferedReader;
155
156    // X and Y coordincates of last touch event. Array Index is the pointerId
157    private float mLastX[] = new float[2];
158
159    private float mLastY[] = new float[2];
160
161    private long mScriptStartTime = -1;
162
163    private long mMonkeyStartTime = -1;
164
165    /**
166     * Creates a MonkeySourceScript instance.
167     *
168     * @param filename The filename of the script (on the device).
169     * @param throttle The amount of time in ms to sleep between events.
170     */
171    public MonkeySourceScript(Random random, String filename, long throttle,
172            boolean randomizeThrottle, long profileWaitTime, long deviceSleepTime) {
173        mScriptFileName = filename;
174        mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
175        mProfileWaitTime = profileWaitTime;
176        mDeviceSleepTime = deviceSleepTime;
177    }
178
179    /**
180     * Resets the globals used to timeshift events.
181     */
182    private void resetValue() {
183        mLastRecordedDownTimeKey = 0;
184        mLastRecordedDownTimeMotion = 0;
185        mLastRecordedEventTime = -1;
186        mLastExportDownTimeKey = 0;
187        mLastExportDownTimeMotion = 0;
188        mLastExportEventTime = -1;
189    }
190
191    /**
192     * Reads the header of the script file.
193     *
194     * @return True if the file header could be parsed, and false otherwise.
195     * @throws IOException If there was an error reading the file.
196     */
197    private boolean readHeader() throws IOException {
198        mFileOpened = true;
199
200        mFStream = new FileInputStream(mScriptFileName);
201        mInputStream = new DataInputStream(mFStream);
202        mBufferedReader = new BufferedReader(new InputStreamReader(mInputStream));
203
204        String line;
205
206        while ((line = mBufferedReader.readLine()) != null) {
207            line = line.trim();
208
209            if (line.indexOf(HEADER_COUNT) >= 0) {
210                try {
211                    String value = line.substring(HEADER_COUNT.length() + 1).trim();
212                    mEventCountInScript = Integer.parseInt(value);
213                } catch (NumberFormatException e) {
214                    System.err.println(e);
215                    return false;
216                }
217            } else if (line.indexOf(HEADER_SPEED) >= 0) {
218                try {
219                    String value = line.substring(HEADER_COUNT.length() + 1).trim();
220                    mSpeed = Double.parseDouble(value);
221                } catch (NumberFormatException e) {
222                    System.err.println(e);
223                    return false;
224                }
225            } else if (line.indexOf(HEADER_LINE_BY_LINE) >= 0) {
226                mReadScriptLineByLine = true;
227            } else if (line.indexOf(STARTING_DATA_LINE) >= 0) {
228                return true;
229            }
230        }
231
232        return false;
233    }
234
235    /**
236     * Reads a number of lines and passes the lines to be processed.
237     *
238     * @return The number of lines read.
239     * @throws IOException If there was an error reading the file.
240     */
241    private int readLines() throws IOException {
242        String line;
243        for (int i = 0; i < MAX_ONE_TIME_READS; i++) {
244            line = mBufferedReader.readLine();
245            if (line == null) {
246                return i;
247            }
248            line.trim();
249            processLine(line);
250        }
251        return MAX_ONE_TIME_READS;
252    }
253
254     /**
255      * Reads one line and processes it.
256      *
257      * @return the number of lines read
258      * @throws IOException If there was an error reading the file.
259      */
260    private int readOneLine() throws IOException {
261        String line = mBufferedReader.readLine();
262        if (line == null) {
263            return 0;
264        }
265        line.trim();
266        processLine(line);
267        return 1;
268    }
269
270
271
272    /**
273     * Creates an event and adds it to the event queue. If the parameters are
274     * not understood, they are ignored and no events are added.
275     *
276     * @param s The entire string from the script file.
277     * @param args An array of arguments extracted from the script file line.
278     */
279    private void handleEvent(String s, String[] args) {
280        // Handle key event
281        if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) {
282            try {
283                System.out.println(" old key\n");
284                long downTime = Long.parseLong(args[0]);
285                long eventTime = Long.parseLong(args[1]);
286                int action = Integer.parseInt(args[2]);
287                int code = Integer.parseInt(args[3]);
288                int repeat = Integer.parseInt(args[4]);
289                int metaState = Integer.parseInt(args[5]);
290                int device = Integer.parseInt(args[6]);
291                int scancode = Integer.parseInt(args[7]);
292
293                MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime, action, code, repeat,
294                        metaState, device, scancode);
295                System.out.println(" Key code " + code + "\n");
296
297                mQ.addLast(e);
298                System.out.println("Added key up \n");
299            } catch (NumberFormatException e) {
300            }
301            return;
302        }
303
304        // Handle trackball or pointer events
305        if ((s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0)
306                && args.length == 12) {
307            try {
308                long downTime = Long.parseLong(args[0]);
309                long eventTime = Long.parseLong(args[1]);
310                int action = Integer.parseInt(args[2]);
311                float x = Float.parseFloat(args[3]);
312                float y = Float.parseFloat(args[4]);
313                float pressure = Float.parseFloat(args[5]);
314                float size = Float.parseFloat(args[6]);
315                int metaState = Integer.parseInt(args[7]);
316                float xPrecision = Float.parseFloat(args[8]);
317                float yPrecision = Float.parseFloat(args[9]);
318                int device = Integer.parseInt(args[10]);
319                int edgeFlags = Integer.parseInt(args[11]);
320
321                MonkeyMotionEvent e;
322                if (s.indexOf("Pointer") > 0) {
323                    e = new MonkeyTouchEvent(action);
324                } else {
325                    e = new MonkeyTrackballEvent(action);
326                }
327
328                e.setDownTime(downTime)
329                        .setEventTime(eventTime)
330                        .setMetaState(metaState)
331                        .setPrecision(xPrecision, yPrecision)
332                        .setDeviceId(device)
333                        .setEdgeFlags(edgeFlags)
334                        .addPointer(0, x, y, pressure, size);
335                mQ.addLast(e);
336            } catch (NumberFormatException e) {
337            }
338            return;
339        }
340
341        // Handle trackball or multi-touch  pointer events. pointer ID is the 13th parameter
342        if ((s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0)
343                && args.length == 13) {
344            try {
345                long downTime = Long.parseLong(args[0]);
346                long eventTime = Long.parseLong(args[1]);
347                int action = Integer.parseInt(args[2]);
348                float x = Float.parseFloat(args[3]);
349                float y = Float.parseFloat(args[4]);
350                float pressure = Float.parseFloat(args[5]);
351                float size = Float.parseFloat(args[6]);
352                int metaState = Integer.parseInt(args[7]);
353                float xPrecision = Float.parseFloat(args[8]);
354                float yPrecision = Float.parseFloat(args[9]);
355                int device = Integer.parseInt(args[10]);
356                int edgeFlags = Integer.parseInt(args[11]);
357                int pointerId = Integer.parseInt(args[12]);
358
359                MonkeyMotionEvent e;
360                if (s.indexOf("Pointer") > 0) {
361                    if (action == MotionEvent.ACTION_POINTER_DOWN) {
362                        e = new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN
363                                | (pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT))
364                                        .setIntermediateNote(true);
365                    } else {
366                        e = new MonkeyTouchEvent(action);
367                    }
368                    if (mScriptStartTime < 0) {
369                        mMonkeyStartTime = SystemClock.uptimeMillis();
370                        mScriptStartTime = eventTime;
371                    }
372                } else {
373                    e = new MonkeyTrackballEvent(action);
374                }
375
376                if (pointerId == 1) {
377                    e.setDownTime(downTime)
378                            .setEventTime(eventTime)
379                            .setMetaState(metaState)
380                            .setPrecision(xPrecision, yPrecision)
381                            .setDeviceId(device)
382                            .setEdgeFlags(edgeFlags)
383                            .addPointer(0, mLastX[0], mLastY[0], pressure, size)
384                            .addPointer(1, x, y, pressure, size);
385                    mLastX[1] = x;
386                    mLastY[1] = y;
387                } else if (pointerId == 0) {
388                    e.setDownTime(downTime)
389                            .setEventTime(eventTime)
390                            .setMetaState(metaState)
391                            .setPrecision(xPrecision, yPrecision)
392                            .setDeviceId(device)
393                            .setEdgeFlags(edgeFlags)
394                            .addPointer(0, x, y, pressure, size);
395                     if(action == MotionEvent.ACTION_POINTER_UP) {
396                         e.addPointer(1, mLastX[1], mLastY[1]);
397                     }
398                     mLastX[0] = x;
399                     mLastY[0] = y;
400                }
401
402                // Dynamically adjust waiting time to ensure that simulated evnets follow
403                // the time tap specified in the script
404                if (mReadScriptLineByLine) {
405                    long curUpTime = SystemClock.uptimeMillis();
406                    long realElapsedTime = curUpTime - mMonkeyStartTime;
407                    long scriptElapsedTime = eventTime - mScriptStartTime;
408                    if (realElapsedTime < scriptElapsedTime) {
409                        long waitDuration = scriptElapsedTime - realElapsedTime;
410                        mQ.addLast(new MonkeyWaitEvent(waitDuration));
411                    }
412                }
413                mQ.addLast(e);
414            } catch (NumberFormatException e) {
415            }
416            return;
417        }
418
419        // Handle screen rotation events
420        if ((s.indexOf(EVENT_KEYWORD_ROTATION) >= 0) && args.length == 2) {
421            try {
422                int rotationDegree = Integer.parseInt(args[0]);
423                int persist = Integer.parseInt(args[1]);
424                if ((rotationDegree == Surface.ROTATION_0) ||
425                    (rotationDegree == Surface.ROTATION_90) ||
426                    (rotationDegree == Surface.ROTATION_180) ||
427                    (rotationDegree == Surface.ROTATION_270)) {
428                    mQ.addLast(new MonkeyRotationEvent(rotationDegree,
429                                                       persist != 0));
430                }
431            } catch (NumberFormatException e) {
432            }
433            return;
434        }
435
436        // Handle tap event
437        if ((s.indexOf(EVENT_KEYWORD_TAP) >= 0) && args.length >= 2) {
438            try {
439                float x = Float.parseFloat(args[0]);
440                float y = Float.parseFloat(args[1]);
441                long tapDuration = 0;
442                if (args.length == 3) {
443                    tapDuration = Long.parseLong(args[2]);
444                }
445
446                // Set the default parameters
447                long downTime = SystemClock.uptimeMillis();
448                MonkeyMotionEvent e1 = new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
449                        .setDownTime(downTime)
450                        .setEventTime(downTime)
451                        .addPointer(0, x, y, 1, 5);
452                mQ.addLast(e1);
453                if (tapDuration > 0){
454                    mQ.addLast(new MonkeyWaitEvent(tapDuration));
455                }
456                MonkeyMotionEvent e2 = new MonkeyTouchEvent(MotionEvent.ACTION_UP)
457                        .setDownTime(downTime)
458                        .setEventTime(downTime)
459                        .addPointer(0, x, y, 1, 5);
460                mQ.addLast(e2);
461            } catch (NumberFormatException e) {
462                System.err.println("// " + e.toString());
463            }
464            return;
465        }
466
467        //Handle the press and hold
468        if ((s.indexOf(EVENT_KEYWORD_PRESSANDHOLD) >= 0) && args.length == 3) {
469            try {
470                float x = Float.parseFloat(args[0]);
471                float y = Float.parseFloat(args[1]);
472                long pressDuration = Long.parseLong(args[2]);
473
474                // Set the default parameters
475                long downTime = SystemClock.uptimeMillis();
476
477                MonkeyMotionEvent e1 = new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
478                        .setDownTime(downTime)
479                        .setEventTime(downTime)
480                        .addPointer(0, x, y, 1, 5);
481                MonkeyWaitEvent e2 = new MonkeyWaitEvent(pressDuration);
482                MonkeyMotionEvent e3 = new MonkeyTouchEvent(MotionEvent.ACTION_UP)
483                        .setDownTime(downTime + pressDuration)
484                        .setEventTime(downTime + pressDuration)
485                        .addPointer(0, x, y, 1, 5);
486                mQ.addLast(e1);
487                mQ.addLast(e2);
488                mQ.addLast(e2);
489
490            } catch (NumberFormatException e) {
491                System.err.println("// " + e.toString());
492            }
493            return;
494        }
495
496        // Handle drag event
497        if ((s.indexOf(EVENT_KEYWORD_DRAG) >= 0) && args.length == 5) {
498            float xStart = Float.parseFloat(args[0]);
499            float yStart = Float.parseFloat(args[1]);
500            float xEnd = Float.parseFloat(args[2]);
501            float yEnd = Float.parseFloat(args[3]);
502            int stepCount = Integer.parseInt(args[4]);
503
504            float x = xStart;
505            float y = yStart;
506            long downTime = SystemClock.uptimeMillis();
507            long eventTime = SystemClock.uptimeMillis();
508
509            if (stepCount > 0) {
510                float xStep = (xEnd - xStart) / stepCount;
511                float yStep = (yEnd - yStart) / stepCount;
512
513                MonkeyMotionEvent e =
514                        new MonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downTime)
515                                .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
516                mQ.addLast(e);
517
518                for (int i = 0; i < stepCount; ++i) {
519                    x += xStep;
520                    y += yStep;
521                    eventTime = SystemClock.uptimeMillis();
522                    e = new MonkeyTouchEvent(MotionEvent.ACTION_MOVE).setDownTime(downTime)
523                        .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
524                    mQ.addLast(e);
525                }
526
527                eventTime = SystemClock.uptimeMillis();
528                e = new MonkeyTouchEvent(MotionEvent.ACTION_UP).setDownTime(downTime)
529                    .setEventTime(eventTime).addPointer(0, x, y, 1, 5);
530                mQ.addLast(e);
531            }
532        }
533
534        // Handle pinch or zoom action
535        if ((s.indexOf(EVENT_KEYWORD_PINCH_ZOOM) >= 0) && args.length == 9) {
536            //Parse the parameters
537            float pt1xStart = Float.parseFloat(args[0]);
538            float pt1yStart = Float.parseFloat(args[1]);
539            float pt1xEnd = Float.parseFloat(args[2]);
540            float pt1yEnd = Float.parseFloat(args[3]);
541
542            float pt2xStart = Float.parseFloat(args[4]);
543            float pt2yStart = Float.parseFloat(args[5]);
544            float pt2xEnd = Float.parseFloat(args[6]);
545            float pt2yEnd = Float.parseFloat(args[7]);
546
547            int stepCount = Integer.parseInt(args[8]);
548
549            float x1 = pt1xStart;
550            float y1 = pt1yStart;
551            float x2 = pt2xStart;
552            float y2 = pt2yStart;
553
554            long downTime = SystemClock.uptimeMillis();
555            long eventTime = SystemClock.uptimeMillis();
556
557            if (stepCount > 0) {
558                float pt1xStep = (pt1xEnd - pt1xStart) / stepCount;
559                float pt1yStep = (pt1yEnd - pt1yStart) / stepCount;
560
561                float pt2xStep = (pt2xEnd - pt2xStart) / stepCount;
562                float pt2yStep = (pt2yEnd - pt2yStart) / stepCount;
563
564                mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downTime)
565                        .setEventTime(eventTime).addPointer(0, x1, y1, 1, 5));
566
567                mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN
568                        | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)).setDownTime(downTime)
569                        .addPointer(0, x1, y1).addPointer(1, x2, y2).setIntermediateNote(true));
570
571                for (int i = 0; i < stepCount; ++i) {
572                    x1 += pt1xStep;
573                    y1 += pt1yStep;
574                    x2 += pt2xStep;
575                    y2 += pt2yStep;
576
577                    eventTime = SystemClock.uptimeMillis();
578                    mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE).setDownTime(downTime)
579                            .setEventTime(eventTime).addPointer(0, x1, y1, 1, 5).addPointer(1, x2,
580                                    y2, 1, 5));
581                }
582                eventTime = SystemClock.uptimeMillis();
583                mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_UP)
584                        .setDownTime(downTime).setEventTime(eventTime).addPointer(0, x1, y1)
585                        .addPointer(1, x2, y2));
586            }
587        }
588
589        // Handle flip events
590        if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0 && args.length == 1) {
591            boolean keyboardOpen = Boolean.parseBoolean(args[0]);
592            MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen);
593            mQ.addLast(e);
594        }
595
596        // Handle launch events
597        if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0 && args.length >= 2) {
598            String pkg_name = args[0];
599            String cl_name = args[1];
600            long alarmTime = 0;
601
602            ComponentName mApp = new ComponentName(pkg_name, cl_name);
603
604            if (args.length > 2) {
605                try {
606                    alarmTime = Long.parseLong(args[2]);
607                } catch (NumberFormatException e) {
608                    System.err.println("// " + e.toString());
609                    return;
610                }
611            }
612
613            if (args.length == 2) {
614                MonkeyActivityEvent e = new MonkeyActivityEvent(mApp);
615                mQ.addLast(e);
616            } else {
617                MonkeyActivityEvent e = new MonkeyActivityEvent(mApp, alarmTime);
618                mQ.addLast(e);
619            }
620            return;
621        }
622
623        //Handle the device wake up event
624        if (s.indexOf(EVENT_KEYWORD_DEVICE_WAKEUP) >= 0){
625            String pkg_name = "com.google.android.powerutil";
626            String cl_name = "com.google.android.powerutil.WakeUpScreen";
627            long deviceSleepTime = mDeviceSleepTime;
628
629            //Start the wakeUpScreen test activity to turn off the screen.
630            ComponentName mApp = new ComponentName(pkg_name, cl_name);
631            mQ.addLast(new MonkeyActivityEvent(mApp, deviceSleepTime));
632
633            //inject the special key for the wakeUpScreen test activity.
634            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0));
635            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0));
636
637            //Add the wait event after the device sleep event so that the monkey
638            //can continue after the device wake up.
639            mQ.addLast(new MonkeyWaitEvent(deviceSleepTime + 3000));
640
641            //Insert the menu key to unlock the screen
642            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
643            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU));
644
645            //Insert the back key to dismiss the test activity
646            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
647            mQ.addLast(new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
648
649            return;
650        }
651
652        // Handle launch instrumentation events
653        if (s.indexOf(EVENT_KEYWORD_INSTRUMENTATION) >= 0 && args.length == 2) {
654            String test_name = args[0];
655            String runner_name = args[1];
656            MonkeyInstrumentationEvent e = new MonkeyInstrumentationEvent(test_name, runner_name);
657            mQ.addLast(e);
658            return;
659        }
660
661        // Handle wait events
662        if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0 && args.length == 1) {
663            try {
664                long sleeptime = Integer.parseInt(args[0]);
665                MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime);
666                mQ.addLast(e);
667            } catch (NumberFormatException e) {
668            }
669            return;
670        }
671
672
673        // Handle the profile wait time
674        if (s.indexOf(EVENT_KEYWORD_PROFILE_WAIT) >= 0) {
675            MonkeyWaitEvent e = new MonkeyWaitEvent(mProfileWaitTime);
676            mQ.addLast(e);
677            return;
678        }
679
680        // Handle keypress events
681        if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0 && args.length == 1) {
682            String key_name = args[0];
683            int keyCode = MonkeySourceRandom.getKeyCode(key_name);
684            if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
685                return;
686            }
687            MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode);
688            mQ.addLast(e);
689            e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode);
690            mQ.addLast(e);
691            return;
692        }
693
694        // Handle longpress events
695        if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) {
696            MonkeyKeyEvent e;
697            e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER);
698            mQ.addLast(e);
699            MonkeyWaitEvent we = new MonkeyWaitEvent(LONGPRESS_WAIT_TIME);
700            mQ.addLast(we);
701            e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER);
702            mQ.addLast(e);
703        }
704
705        //The power log event is mainly for the automated power framework
706        if (s.indexOf(EVENT_KEYWORD_POWERLOG) >= 0 && args.length > 0) {
707            String power_log_type = args[0];
708            String test_case_status;
709
710            if (args.length == 1){
711                MonkeyPowerEvent e = new MonkeyPowerEvent(power_log_type);
712                mQ.addLast(e);
713            } else if (args.length == 2){
714                test_case_status = args[1];
715                MonkeyPowerEvent e = new MonkeyPowerEvent(power_log_type, test_case_status);
716                mQ.addLast(e);
717            }
718        }
719
720        //Write power log to sdcard
721        if (s.indexOf(EVENT_KEYWORD_WRITEPOWERLOG) >= 0) {
722            MonkeyPowerEvent e = new MonkeyPowerEvent();
723            mQ.addLast(e);
724        }
725
726        //Run the shell command
727        if (s.indexOf(EVENT_KEYWORD_RUNCMD) >= 0 && args.length == 1) {
728            String cmd = args[0];
729            MonkeyCommandEvent e = new MonkeyCommandEvent(cmd);
730            mQ.addLast(e);
731        }
732
733        //Input the string through the shell command
734        if (s.indexOf(EVENT_KEYWORD_INPUT_STRING) >= 0 && args.length == 1) {
735            String input = args[0];
736            String cmd = "input text " + input;
737            MonkeyCommandEvent e = new MonkeyCommandEvent(cmd);
738            mQ.addLast(e);
739            return;
740        }
741
742        if (s.indexOf(EVENT_KEYWORD_START_FRAMERATE_CAPTURE) >= 0) {
743            MonkeyGetFrameRateEvent e = new MonkeyGetFrameRateEvent("start");
744            mQ.addLast(e);
745            return;
746        }
747
748        if (s.indexOf(EVENT_KEYWORD_END_FRAMERATE_CAPTURE) >= 0 && args.length == 1) {
749            String input = args[0];
750            MonkeyGetFrameRateEvent e = new MonkeyGetFrameRateEvent("end", input);
751            mQ.addLast(e);
752            return;
753        }
754
755        if (s.indexOf(EVENT_KEYWORD_START_APP_FRAMERATE_CAPTURE) >= 0 && args.length == 1) {
756            String app = args[0];
757            MonkeyGetAppFrameRateEvent e = new MonkeyGetAppFrameRateEvent("start", app);
758            mQ.addLast(e);
759            return;
760        }
761
762        if (s.indexOf(EVENT_KEYWORD_END_APP_FRAMERATE_CAPTURE) >= 0 && args.length == 2) {
763            String app = args[0];
764            String label = args[1];
765            MonkeyGetAppFrameRateEvent e = new MonkeyGetAppFrameRateEvent("end", app, label);
766            mQ.addLast(e);
767            return;
768        }
769
770
771    }
772
773    /**
774     * Extracts an event and a list of arguments from a line. If the line does
775     * not match the format required, it is ignored.
776     *
777     * @param line A string in the form {@code cmd(arg1,arg2,arg3)}.
778     */
779    private void processLine(String line) {
780        int index1 = line.indexOf('(');
781        int index2 = line.indexOf(')');
782
783        if (index1 < 0 || index2 < 0) {
784            return;
785        }
786
787        String[] args = line.substring(index1 + 1, index2).split(",");
788
789        for (int i = 0; i < args.length; i++) {
790            args[i] = args[i].trim();
791        }
792
793        handleEvent(line, args);
794    }
795
796    /**
797     * Closes the script file.
798     *
799     * @throws IOException If there was an error closing the file.
800     */
801    private void closeFile() throws IOException {
802        mFileOpened = false;
803
804        try {
805            mFStream.close();
806            mInputStream.close();
807        } catch (NullPointerException e) {
808            // File was never opened so it can't be closed.
809        }
810    }
811
812    /**
813     * Read next batch of events from the script file into the event queue.
814     * Checks if the script is open and then reads the next MAX_ONE_TIME_READS
815     * events or reads until the end of the file. If no events are read, then
816     * the script is closed.
817     *
818     * @throws IOException If there was an error reading the file.
819     */
820    private void readNextBatch() throws IOException {
821        int linesRead = 0;
822
823        if (THIS_DEBUG) {
824            System.out.println("readNextBatch(): reading next batch of events");
825        }
826
827        if (!mFileOpened) {
828            resetValue();
829            readHeader();
830        }
831
832        if (mReadScriptLineByLine) {
833            linesRead = readOneLine();
834        } else {
835            linesRead = readLines();
836        }
837
838        if (linesRead == 0) {
839            closeFile();
840        }
841    }
842
843    /**
844     * Sleep for a period of given time. Used to introduce latency between
845     * events.
846     *
847     * @param time The amount of time to sleep in ms
848     */
849    private void needSleep(long time) {
850        if (time < 1) {
851            return;
852        }
853        try {
854            Thread.sleep(time);
855        } catch (InterruptedException e) {
856        }
857    }
858
859    /**
860     * Checks if the file can be opened and if the header is valid.
861     *
862     * @return True if the file exists and the header is valid, false otherwise.
863     */
864    @Override
865    public boolean validate() {
866        boolean validHeader;
867        try {
868            validHeader = readHeader();
869            closeFile();
870        } catch (IOException e) {
871            return false;
872        }
873
874        if (mVerbose > 0) {
875            System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed);
876        }
877        return validHeader;
878    }
879
880    @Override
881    public void setVerbose(int verbose) {
882        mVerbose = verbose;
883    }
884
885    /**
886     * Adjust key downtime and eventtime according to both recorded values and
887     * current system time.
888     *
889     * @param e A KeyEvent
890     */
891    private void adjustKeyEventTime(MonkeyKeyEvent e) {
892        if (e.getEventTime() < 0) {
893            return;
894        }
895        long thisDownTime = 0;
896        long thisEventTime = 0;
897        long expectedDelay = 0;
898
899        if (mLastRecordedEventTime <= 0) {
900            // first time event
901            thisDownTime = SystemClock.uptimeMillis();
902            thisEventTime = thisDownTime;
903        } else {
904            if (e.getDownTime() != mLastRecordedDownTimeKey) {
905                thisDownTime = e.getDownTime();
906            } else {
907                thisDownTime = mLastExportDownTimeKey;
908            }
909            expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed);
910            thisEventTime = mLastExportEventTime + expectedDelay;
911            // add sleep to simulate everything in recording
912            needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
913        }
914        mLastRecordedDownTimeKey = e.getDownTime();
915        mLastRecordedEventTime = e.getEventTime();
916        e.setDownTime(thisDownTime);
917        e.setEventTime(thisEventTime);
918        mLastExportDownTimeKey = thisDownTime;
919        mLastExportEventTime = thisEventTime;
920    }
921
922    /**
923     * Adjust motion downtime and eventtime according to current system time.
924     *
925     * @param e A MotionEvent
926     */
927    private void adjustMotionEventTime(MonkeyMotionEvent e) {
928        long thisEventTime = SystemClock.uptimeMillis();
929        long thisDownTime = e.getDownTime();
930
931        if (thisDownTime == mLastRecordedDownTimeMotion) {
932            // this event is the same batch as previous one
933            e.setDownTime(mLastExportDownTimeMotion);
934        } else {
935            // this event is the start of a new batch
936            mLastRecordedDownTimeMotion = thisDownTime;
937            // update down time to match current time
938            e.setDownTime(thisEventTime);
939            mLastExportDownTimeMotion = thisEventTime;
940        }
941        // always refresh event time
942        e.setEventTime(thisEventTime);
943    }
944
945    /**
946     * Gets the next event to be injected from the script. If the event queue is
947     * empty, reads the next n events from the script into the queue, where n is
948     * the lesser of the number of remaining events and the value specified by
949     * MAX_ONE_TIME_READS. If the end of the file is reached, no events are
950     * added to the queue and null is returned.
951     *
952     * @return The first event in the event queue or null if the end of the file
953     *         is reached or if an error is encountered reading the file.
954     */
955    @Override
956    public MonkeyEvent getNextEvent() {
957        long recordedEventTime = -1;
958        MonkeyEvent ev;
959
960        if (mQ.isEmpty()) {
961            try {
962                readNextBatch();
963            } catch (IOException e) {
964                return null;
965            }
966        }
967
968        try {
969            ev = mQ.getFirst();
970            mQ.removeFirst();
971        } catch (NoSuchElementException e) {
972            return null;
973        }
974
975        if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
976            adjustKeyEventTime((MonkeyKeyEvent) ev);
977        } else if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_TOUCH
978                || ev.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
979            adjustMotionEventTime((MonkeyMotionEvent) ev);
980        }
981        return ev;
982    }
983}
984