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 23 import java.io.BufferedReader; 24 import java.io.DataInputStream; 25 import java.io.FileInputStream; 26 import java.io.FileNotFoundException; 27 import java.io.IOException; 28 import java.io.InputStreamReader; 29 import java.util.LinkedList; 30 import java.util.StringTokenizer; 31 32 import android.view.KeyEvent; 33 /** 34 * monkey event queue. It takes a script to produce events 35 * 36 * sample script format: 37 * type= raw events 38 * count= 10 39 * speed= 1.0 40 * start data >> 41 * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314, 42 * 0.06666667,0,0.0,0.0,65539,0) 43 * captureDispatchKey(5113146,5113146,0,20,0,0,0,0) 44 * captureDispatchFlip(true) 45 * ... 46 */ 47 public class MonkeySourceScript implements MonkeyEventSource { 48 private int mEventCountInScript = 0; //total number of events in the file 49 private int mVerbose = 0; 50 private double mSpeed = 1.0; 51 private String mScriptFileName; 52 private MonkeyEventQueue mQ; 53 54 private static final String HEADER_TYPE = "type="; 55 private static final String HEADER_COUNT = "count="; 56 private static final String HEADER_SPEED = "speed="; 57 // New script type 58 private static final String USER_EVENT_TYPE = "user"; 59 60 private long mLastRecordedDownTimeKey = 0; 61 private long mLastRecordedDownTimeMotion = 0; 62 private long mLastExportDownTimeKey = 0; 63 private long mLastExportDownTimeMotion = 0; 64 private long mLastExportEventTime = -1; 65 private long mLastRecordedEventTime = -1; 66 private String mScriptType = USER_EVENT_TYPE; 67 68 private static final boolean THIS_DEBUG = false; 69 // a parameter that compensates the difference of real elapsed time and 70 // time in theory 71 private static final long SLEEP_COMPENSATE_DIFF = 16; 72 73 // maximum number of events that we read at one time 74 private static final int MAX_ONE_TIME_READS = 100; 75 76 // number of additional events added to the script 77 // add HOME_KEY down and up events to make start UI consistent in each round 78 private static final int POLICY_ADDITIONAL_EVENT_COUNT = 2; 79 80 // event key word in the capture log 81 private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; 82 private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball"; 83 private static final String EVENT_KEYWORD_KEY = "DispatchKey"; 84 private static final String EVENT_KEYWORD_FLIP = "DispatchFlip"; 85 private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress"; 86 private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity"; 87 private static final String EVENT_KEYWORD_WAIT = "UserWait"; 88 private static final String EVENT_KEYWORD_LONGPRESS = "LongPress"; 89 90 // a line at the end of the header 91 private static final String STARTING_DATA_LINE = "start data >>"; 92 private boolean mFileOpened = false; 93 private static int LONGPRESS_WAIT_TIME = 2000; // wait time for the long press 94 95 FileInputStream mFStream; 96 DataInputStream mInputStream; 97 BufferedReader mBufferReader; 98 MonkeySourceScript(String filename, long throttle)99 public MonkeySourceScript(String filename, long throttle) { 100 mScriptFileName = filename; 101 mQ = new MonkeyEventQueue(throttle); 102 } 103 104 /** 105 * 106 * @return the number of total events that will be generated in a round 107 */ getOneRoundEventCount()108 public int getOneRoundEventCount() { 109 //plus one home key down and up event 110 return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT; 111 } 112 resetValue()113 private void resetValue() { 114 mLastRecordedDownTimeKey = 0; 115 mLastRecordedDownTimeMotion = 0; 116 mLastExportDownTimeKey = 0; 117 mLastExportDownTimeMotion = 0; 118 mLastRecordedEventTime = -1; 119 mLastExportEventTime = -1; 120 } 121 readScriptHeader()122 private boolean readScriptHeader() { 123 mEventCountInScript = -1; 124 mFileOpened = false; 125 try { 126 if (THIS_DEBUG) { 127 System.out.println("reading script header"); 128 } 129 130 mFStream = new FileInputStream(mScriptFileName); 131 mInputStream = new DataInputStream(mFStream); 132 mBufferReader = new BufferedReader( 133 new InputStreamReader(mInputStream)); 134 String sLine; 135 while ((sLine = mBufferReader.readLine()) != null) { 136 sLine = sLine.trim(); 137 if (sLine.indexOf(HEADER_TYPE) >= 0) { 138 mScriptType = sLine.substring(HEADER_TYPE.length() + 1).trim(); 139 } else if (sLine.indexOf(HEADER_COUNT) >= 0) { 140 try { 141 mEventCountInScript = Integer.parseInt(sLine.substring( 142 HEADER_COUNT.length() + 1).trim()); 143 } catch (NumberFormatException e) { 144 System.err.println(e); 145 } 146 } else if (sLine.indexOf(HEADER_SPEED) >= 0) { 147 try { 148 mSpeed = Double.parseDouble(sLine.substring( 149 HEADER_SPEED.length() + 1).trim()); 150 151 } catch (NumberFormatException e) { 152 System.err.println(e); 153 } 154 } else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) { 155 // header ends until we read the start data mark 156 mFileOpened = true; 157 if (THIS_DEBUG) { 158 System.out.println("read script header success"); 159 } 160 return true; 161 } 162 } 163 } catch (FileNotFoundException e) { 164 System.err.println(e); 165 } catch (IOException e) { 166 System.err.println(e); 167 } 168 169 if (THIS_DEBUG) { 170 System.out.println("Error in reading script header"); 171 } 172 return false; 173 } 174 handleRawEvent(String s, StringTokenizer st)175 private void handleRawEvent(String s, StringTokenizer st) { 176 if (s.indexOf(EVENT_KEYWORD_KEY) >= 0) { 177 // key events 178 try { 179 System.out.println(" old key\n"); 180 long downTime = Long.parseLong(st.nextToken()); 181 long eventTime = Long.parseLong(st.nextToken()); 182 int action = Integer.parseInt(st.nextToken()); 183 int code = Integer.parseInt(st.nextToken()); 184 int repeat = Integer.parseInt(st.nextToken()); 185 int metaState = Integer.parseInt(st.nextToken()); 186 int device = Integer.parseInt(st.nextToken()); 187 int scancode = Integer.parseInt(st.nextToken()); 188 189 MonkeyKeyEvent e = 190 new MonkeyKeyEvent(downTime, eventTime, action, code, repeat, metaState, 191 device, scancode); 192 System.out.println(" Key code " + code + "\n"); 193 194 mQ.addLast(e); 195 System.out.println("Added key up \n"); 196 197 } catch (NumberFormatException e) { 198 // something wrong with this line in the script 199 } 200 } else if (s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || 201 s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0) { 202 // trackball/pointer event 203 try { 204 long downTime = Long.parseLong(st.nextToken()); 205 long eventTime = Long.parseLong(st.nextToken()); 206 int action = Integer.parseInt(st.nextToken()); 207 float x = Float.parseFloat(st.nextToken()); 208 float y = Float.parseFloat(st.nextToken()); 209 float pressure = Float.parseFloat(st.nextToken()); 210 float size = Float.parseFloat(st.nextToken()); 211 int metaState = Integer.parseInt(st.nextToken()); 212 float xPrecision = Float.parseFloat(st.nextToken()); 213 float yPrecision = Float.parseFloat(st.nextToken()); 214 int device = Integer.parseInt(st.nextToken()); 215 int edgeFlags = Integer.parseInt(st.nextToken()); 216 217 int type = MonkeyEvent.EVENT_TYPE_TRACKBALL; 218 if (s.indexOf("Pointer") > 0) { 219 type = MonkeyEvent.EVENT_TYPE_POINTER; 220 } 221 MonkeyMotionEvent e = 222 new MonkeyMotionEvent(type, downTime, eventTime, action, x, y, pressure, 223 size, metaState, xPrecision, yPrecision, device, edgeFlags); 224 mQ.addLast(e); 225 } catch (NumberFormatException e) { 226 // we ignore this event 227 } 228 } else if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0) { 229 boolean keyboardOpen = Boolean.parseBoolean(st.nextToken()); 230 MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen); 231 mQ.addLast(e); 232 } 233 234 } 235 handleUserEvent(String s, StringTokenizer st)236 private void handleUserEvent(String s, StringTokenizer st) { 237 if (s.indexOf(EVENT_KEYWORD_ACTIVITY) >= 0) { 238 String pkg_name = st.nextToken(); 239 String cl_name = st.nextToken(); 240 ComponentName mApp = new ComponentName(pkg_name, cl_name); 241 MonkeyActivityEvent e = new MonkeyActivityEvent(mApp); 242 mQ.addLast(e); 243 244 } else if (s.indexOf(EVENT_KEYWORD_WAIT) >= 0) { 245 long sleeptime = Integer.parseInt(st.nextToken()); 246 MonkeyWaitEvent e = new MonkeyWaitEvent(sleeptime); 247 mQ.addLast(e); 248 249 } else if (s.indexOf(EVENT_KEYWORD_KEYPRESS) >= 0) { 250 String key_name = st.nextToken(); 251 int keyCode = MonkeySourceRandom.getKeyCode(key_name); 252 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode); 253 mQ.addLast(e); 254 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode); 255 mQ.addLast(e); 256 } else if (s.indexOf(EVENT_KEYWORD_LONGPRESS) >= 0) { 257 // handle the long press 258 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, 259 KeyEvent.KEYCODE_DPAD_CENTER); 260 mQ.addLast(e); 261 MonkeyWaitEvent we = new MonkeyWaitEvent(LONGPRESS_WAIT_TIME); 262 mQ.addLast(we); 263 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, 264 KeyEvent.KEYCODE_DPAD_CENTER); 265 mQ.addLast(e); 266 } 267 } 268 processLine(String s)269 private void processLine(String s) { 270 int index1 = s.indexOf('('); 271 int index2 = s.indexOf(')'); 272 273 if (index1 < 0 || index2 < 0) { 274 return; 275 } 276 277 StringTokenizer st = new StringTokenizer( 278 s.substring(index1 + 1, index2), ","); 279 if (mScriptType.compareTo(USER_EVENT_TYPE) == 0) { 280 // User event type 281 handleUserEvent(s, st); 282 } else { 283 // Raw type 284 handleRawEvent(s,st); 285 } 286 } 287 closeFile()288 private void closeFile() { 289 mFileOpened = false; 290 if (THIS_DEBUG) { 291 System.out.println("closing script file"); 292 } 293 294 try { 295 mFStream.close(); 296 mInputStream.close(); 297 } catch (IOException e) { 298 System.out.println(e); 299 } 300 } 301 302 /** 303 * add home key press/release event to the queue 304 */ addHomeKeyEvent()305 private void addHomeKeyEvent() { 306 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, 307 KeyEvent.KEYCODE_HOME); 308 mQ.addLast(e); 309 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME); 310 mQ.addLast(e); 311 } 312 313 /** 314 * read next batch of events from the provided script file 315 * @return true if success 316 */ readNextBatch()317 private boolean readNextBatch() { 318 /* 319 * The script should restore the original state when it run multiple 320 * times. 321 */ 322 String sLine = null; 323 int readCount = 0; 324 325 if (THIS_DEBUG) { 326 System.out.println("readNextBatch(): reading next batch of events"); 327 } 328 329 if (!mFileOpened) { 330 if (!readScriptHeader()) { 331 closeFile(); 332 return false; 333 } 334 resetValue(); 335 } 336 337 try { 338 while (readCount++ < MAX_ONE_TIME_READS && 339 (sLine = mBufferReader.readLine()) != null) { 340 sLine = sLine.trim(); 341 processLine(sLine); 342 } 343 } catch (IOException e) { 344 System.err.println(e); 345 return false; 346 } 347 348 if (sLine == null) { 349 // to the end of the file 350 if (THIS_DEBUG) { 351 System.out.println("readNextBatch(): to the end of file"); 352 } 353 closeFile(); 354 } 355 return true; 356 } 357 358 /** 359 * sleep for a period of given time, introducing latency among events 360 * @param time to sleep in millisecond 361 */ needSleep(long time)362 private void needSleep(long time) { 363 if (time < 1) { 364 return; 365 } 366 try { 367 Thread.sleep(time); 368 } catch (InterruptedException e) { 369 } 370 } 371 372 /** 373 * check whether we can successfully read the header of the script file 374 */ validate()375 public boolean validate() { 376 boolean b = readNextBatch(); 377 if (mVerbose > 0) { 378 System.out.println("Replaying " + mEventCountInScript + 379 " events with speed " + mSpeed); 380 } 381 return b; 382 } 383 setVerbose(int verbose)384 public void setVerbose(int verbose) { 385 mVerbose = verbose; 386 } 387 388 /** 389 * adjust key downtime and eventtime according to both 390 * recorded values and current system time 391 * @param e KeyEvent 392 */ adjustKeyEventTime(MonkeyKeyEvent e)393 private void adjustKeyEventTime(MonkeyKeyEvent e) { 394 if (e.getEventTime() < 0) { 395 return; 396 } 397 long thisDownTime = 0; 398 long thisEventTime = 0; 399 long expectedDelay = 0; 400 401 if (mLastRecordedEventTime <= 0) { 402 // first time event 403 thisDownTime = SystemClock.uptimeMillis(); 404 thisEventTime = thisDownTime; 405 } else { 406 if (e.getDownTime() != mLastRecordedDownTimeKey) { 407 thisDownTime = e.getDownTime(); 408 } else { 409 thisDownTime = mLastExportDownTimeKey; 410 } 411 expectedDelay = (long) ((e.getEventTime() - 412 mLastRecordedEventTime) * mSpeed); 413 thisEventTime = mLastExportEventTime + expectedDelay; 414 // add sleep to simulate everything in recording 415 needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); 416 } 417 mLastRecordedDownTimeKey = e.getDownTime(); 418 mLastRecordedEventTime = e.getEventTime(); 419 e.setDownTime(thisDownTime); 420 e.setEventTime(thisEventTime); 421 mLastExportDownTimeKey = thisDownTime; 422 mLastExportEventTime = thisEventTime; 423 } 424 425 /** 426 * adjust motion downtime and eventtime according to both 427 * recorded values and current system time 428 * @param e KeyEvent 429 */ adjustMotionEventTime(MonkeyMotionEvent e)430 private void adjustMotionEventTime(MonkeyMotionEvent e) { 431 if (e.getEventTime() < 0) { 432 return; 433 } 434 long thisDownTime = 0; 435 long thisEventTime = 0; 436 long expectedDelay = 0; 437 438 if (mLastRecordedEventTime <= 0) { 439 // first time event 440 thisDownTime = SystemClock.uptimeMillis(); 441 thisEventTime = thisDownTime; 442 } else { 443 if (e.getDownTime() != mLastRecordedDownTimeMotion) { 444 thisDownTime = e.getDownTime(); 445 } else { 446 thisDownTime = mLastExportDownTimeMotion; 447 } 448 expectedDelay = (long) ((e.getEventTime() - 449 mLastRecordedEventTime) * mSpeed); 450 thisEventTime = mLastExportEventTime + expectedDelay; 451 // add sleep to simulate everything in recording 452 needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); 453 } 454 455 mLastRecordedDownTimeMotion = e.getDownTime(); 456 mLastRecordedEventTime = e.getEventTime(); 457 e.setDownTime(thisDownTime); 458 e.setEventTime(thisEventTime); 459 mLastExportDownTimeMotion = thisDownTime; 460 mLastExportEventTime = thisEventTime; 461 } 462 463 /** 464 * if the queue is empty, we generate events first 465 * @return the first event in the queue, if null, indicating the system crashes 466 */ getNextEvent()467 public MonkeyEvent getNextEvent() { 468 long recordedEventTime = -1; 469 470 if (mQ.isEmpty()) { 471 readNextBatch(); 472 } 473 MonkeyEvent e = mQ.getFirst(); 474 mQ.removeFirst(); 475 if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) { 476 adjustKeyEventTime((MonkeyKeyEvent) e); 477 } else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER || 478 e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) { 479 adjustMotionEventTime((MonkeyMotionEvent) e); 480 } 481 return e; 482 } 483 } 484