/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.commands.monkey; import android.content.ComponentName; import android.os.SystemClock; import android.view.KeyEvent; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.StringTokenizer; import android.view.KeyEvent; /** * monkey event queue. It takes a script to produce events * * sample script format: * type= raw events * count= 10 * speed= 1.0 * start data >> * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314, * 0.06666667,0,0.0,0.0,65539,0) * captureDispatchKey(5113146,5113146,0,20,0,0,0,0) * captureDispatchFlip(true) * ... */ public class MonkeySourceScript implements MonkeyEventSource { private int mEventCountInScript = 0; //total number of events in the file private int mVerbose = 0; private double mSpeed = 1.0; private String mScriptFileName; private MonkeyEventQueue mQ; private static final String HEADER_TYPE = "type="; private static final String HEADER_COUNT = "count="; private static final String HEADER_SPEED = "speed="; // New script type private static final String USER_EVENT_TYPE = "user"; private long mLastRecordedDownTimeKey = 0; private long mLastRecordedDownTimeMotion = 0; private long mLastExportDownTimeKey = 0; private long mLastExportDownTimeMotion = 0; private long mLastExportEventTime = -1; private long mLastRecordedEventTime = -1; private String mScriptType = USER_EVENT_TYPE; private static final boolean THIS_DEBUG = false; // a parameter that compensates the difference of real elapsed time and // time in theory private static final long SLEEP_COMPENSATE_DIFF = 16; // maximum number of events that we read at one time private static final int MAX_ONE_TIME_READS = 100; // number of additional events added to the script // add HOME_KEY down and up events to make start UI consistent in each round private static final int POLICY_ADDITIONAL_EVENT_COUNT = 2; // event key word in the capture log private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball"; private static final String EVENT_KEYWORD_KEY = "DispatchKey"; private static final String EVENT_KEYWORD_FLIP = "DispatchFlip"; private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress"; private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity"; private static final String EVENT_KEYWORD_WAIT = "UserWait"; private static final String EVENT_KEYWORD_LONGPRESS = "LongPress"; // a line at the end of the header private static final String STARTING_DATA_LINE = "start data >>"; private boolean mFileOpened = false; private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long press FileInputStream mFStream; DataInputStream mInputStream; BufferedReader mBufferReader; public MonkeySourceScript(String filename, long throttle) { mScriptFileName = filename; mQ = new MonkeyEventQueue(throttle); } /** * * @return the number of total events that will be generated in a round */ public int getOneRoundEventCount() { //plus one home key down and up event return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT; } private void resetValue() { mLastRecordedDownTimeKey = 0; mLastRecordedDownTimeMotion = 0; mLastExportDownTimeKey = 0; mLastExportDownTimeMotion = 0; mLastRecordedEventTime = -1; mLastExportEventTime = -1; } private boolean readScriptHeader() { mEventCountInScript = -1; mFileOpened = false; try { if (THIS_DEBUG) { System.out.println("reading script header"); } mFStream = new FileInputStream(mScriptFileName); mInputStream = new DataInputStream(mFStream); mBufferReader = new BufferedReader( new InputStreamReader(mInputStream)); String sLine; while ((sLine = mBufferReader.readLine()) != null) { sLine = sLine.trim(); if (sLine.indexOf(HEADER_TYPE) >= 0) { mScriptType = sLine.substring(HEADER_TYPE.length() + 1).trim(); } else if (sLine.indexOf(HEADER_COUNT) >= 0) { try { mEventCountInScript = Integer.parseInt(sLine.substring( HEADER_COUNT.length() + 1).trim()); } catch (NumberFormatException e) { System.err.println(e); } } else if (sLine.indexOf(HEADER_SPEED) >= 0) { try { mSpeed = Double.parseDouble(sLine.substring( HEADER_SPEED.length() + 1).trim()); } catch (NumberFormatException e) { System.err.println(e); } } else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) { // header ends until we read the start data mark mFileOpened = true; if (THIS_DEBUG) { System.out.println("read script header success"); } return true; } } } catch (FileNotFoundException e) { System.err.println(e); } catch (IOException e) { System.err.println(e); } if (THIS_DEBUG) { System.out.println("Error in reading script header"); } return false; } private void handleRawEvent(String s, StringTokenizer st) { if (s.indexOf(EVENT_KEYWORD_KEY) >= 0) { // key events try { System.out.println(" old key\n"); long downTime = Long.parseLong(st.nextToken()); long eventTime = Long.parseLong(st.nextToken()); int action = Integer.parseInt(st.nextToken()); int code = Integer.parseInt(st.nextToken()); int repeat = Integer.parseInt(st.nextToken()); int metaState = Integer.parseInt(st.nextToken()); int device = Integer.parseInt(st.nextToken()); int scancode = Integer.parseInt(st.nextToken()); MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime, action, code, repeat, metaState, device, scancode); System.out.println(" Key code " + code + "\n"); mQ.addLast(e); System.out.println("Added key up \n"); } catch (NumberFormatException e) { // something wrong with this line in the script } } else if (s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0) { // trackball/pointer event try { long downTime = Long.parseLong(st.nextToken()); long eventTime = Long.parseLong(st.nextToken()); int action = Integer.parseInt(st.nextToken()); float x = Float.parseFloat(st.nextToken()); float y = Float.parseFloat(st.nextToken()); float pressure = Float.parseFloat(st.nextToken()); float size = Float.parseFloat(st.nextToken()); int metaState = Integer.parseInt(st.nextToken()); float xPrecision = Float.parseFloat(st.nextToken()); float yPrecision = Float.parseFloat(st.nextToken()); int device = Integer.parseInt(st.nextToken()); int edgeFlags = Integer.parseInt(st.nextToken()); int type = MonkeyEvent.EVENT_TYPE_TRACKBALL; if (s.indexOf("Pointer") > 0) { type = MonkeyEvent.EVENT_TYPE_POINTER; } MonkeyMotionEvent e = new MonkeyMotionEvent(type, downTime, eventTime, action, x, y, pressure, size, metaState, xPrecision, yPrecision, device, edgeFlags); mQ.addLast(e); } catch (NumberFormatException e) { // we ignore this event } } else if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0) { boolean keyboardOpen = Boolean.parseBoolean(st.nextToken()); MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen); mQ.addLast(e); } } private void handleUserEvent(String s, StringTokenizer st) { if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0) { String pkg_name = st.nextToken(); String cl_name = st.nextToken(); ComponentName mApp = new ComponentName(pkg_name, cl_name); MonkeyActivityEvent e = new MonkeyActivityEvent(mApp); mQ.addLast(e); } else if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0) { long sleeptime = Integer.parseInt(st.nextToken()); MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime); mQ.addLast(e); } else if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0) { String key_name = st.nextToken(); int keyCode = MonkeySourceRandom.getKeyCode(key_name); MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode); mQ.addLast(e); e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode); mQ.addLast(e); } else if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) { // handle the long press MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); mQ.addLast(e); MonkeyWaitEvent we = new MonkeyWaitEvent(LONGPRESS_WAIT_TIME); mQ.addLast(we); e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); mQ.addLast(e); } } private void processLine(String s) { int index1 = s.indexOf('('); int index2 = s.indexOf(')'); if (index1 < 0 || index2 < 0) { return; } StringTokenizer st = new StringTokenizer( s.substring(index1 + 1, index2), ","); if (mScriptType.compareTo(USER_EVENT_TYPE) == 0) { // User event type handleUserEvent(s, st); } else { // Raw type handleRawEvent(s,st); } } private void closeFile() { mFileOpened = false; if (THIS_DEBUG) { System.out.println("closing script file"); } try { mFStream.close(); mInputStream.close(); } catch (IOException e) { System.out.println(e); } } /** * add home key press/release event to the queue */ private void addHomeKeyEvent() { MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HOME); mQ.addLast(e); e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME); mQ.addLast(e); } /** * read next batch of events from the provided script file * @return true if success */ private boolean readNextBatch() { /* * The script should restore the original state when it run multiple * times. */ String sLine = null; int readCount = 0; if (THIS_DEBUG) { System.out.println("readNextBatch(): reading next batch of events"); } if (!mFileOpened) { if (!readScriptHeader()) { closeFile(); return false; } resetValue(); } try { while (readCount++ < MAX_ONE_TIME_READS && (sLine = mBufferReader.readLine()) != null) { sLine = sLine.trim(); processLine(sLine); } } catch (IOException e) { System.err.println(e); return false; } if (sLine == null) { // to the end of the file if (THIS_DEBUG) { System.out.println("readNextBatch(): to the end of file"); } closeFile(); } return true; } /** * sleep for a period of given time, introducing latency among events * @param time to sleep in millisecond */ private void needSleep(long time) { if (time < 1) { return; } try { Thread.sleep(time); } catch (InterruptedException e) { } } /** * check whether we can successfully read the header of the script file */ public boolean validate() { boolean b = readNextBatch(); if (mVerbose > 0) { System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed); } return b; } public void setVerbose(int verbose) { mVerbose = verbose; } /** * adjust key downtime and eventtime according to both * recorded values and current system time * @param e KeyEvent */ private void adjustKeyEventTime(MonkeyKeyEvent e) { if (e.getEventTime() < 0) { return; } long thisDownTime = 0; long thisEventTime = 0; long expectedDelay = 0; if (mLastRecordedEventTime <= 0) { // first time event thisDownTime = SystemClock.uptimeMillis(); thisEventTime = thisDownTime; } else { if (e.getDownTime() != mLastRecordedDownTimeKey) { thisDownTime = e.getDownTime(); } else { thisDownTime = mLastExportDownTimeKey; } expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed); thisEventTime = mLastExportEventTime + expectedDelay; // add sleep to simulate everything in recording needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); } mLastRecordedDownTimeKey = e.getDownTime(); mLastRecordedEventTime = e.getEventTime(); e.setDownTime(thisDownTime); e.setEventTime(thisEventTime); mLastExportDownTimeKey = thisDownTime; mLastExportEventTime = thisEventTime; } /** * adjust motion downtime and eventtime according to both * recorded values and current system time * @param e KeyEvent */ private void adjustMotionEventTime(MonkeyMotionEvent e) { if (e.getEventTime() < 0) { return; } long thisDownTime = 0; long thisEventTime = 0; long expectedDelay = 0; if (mLastRecordedEventTime <= 0) { // first time event thisDownTime = SystemClock.uptimeMillis(); thisEventTime = thisDownTime; } else { if (e.getDownTime() != mLastRecordedDownTimeMotion) { thisDownTime = e.getDownTime(); } else { thisDownTime = mLastExportDownTimeMotion; } expectedDelay = (long) ((e.getEventTime() - mLastRecordedEventTime) * mSpeed); thisEventTime = mLastExportEventTime + expectedDelay; // add sleep to simulate everything in recording needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); } mLastRecordedDownTimeMotion = e.getDownTime(); mLastRecordedEventTime = e.getEventTime(); e.setDownTime(thisDownTime); e.setEventTime(thisEventTime); mLastExportDownTimeMotion = thisDownTime; mLastExportEventTime = thisEventTime; } /** * if the queue is empty, we generate events first * @return the first event in the queue, if null, indicating the system crashes */ public MonkeyEvent getNextEvent() { long recordedEventTime = -1; if (mQ.isEmpty()) { readNextBatch(); } MonkeyEvent e = mQ.getFirst(); mQ.removeFirst(); if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) { adjustKeyEventTime((MonkeyKeyEvent) e); } else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER || e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) { adjustMotionEventTime((MonkeyMotionEvent) e); } return e; } }