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.Display; 22 import android.view.KeyCharacterMap; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 import android.view.WindowManagerImpl; 26 27 import java.util.ArrayList; 28 import java.util.Random; 29 30 /** 31 * monkey event queue 32 */ 33 public class MonkeySourceRandom implements MonkeyEventSource { 34 /** Key events that move around the UI. */ 35 private static final int[] NAV_KEYS = { 36 KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, 37 KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, 38 }; 39 /** 40 * Key events that perform major navigation options (so shouldn't be sent 41 * as much). 42 */ 43 private static final int[] MAJOR_NAV_KEYS = { 44 KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/ 45 KeyEvent.KEYCODE_DPAD_CENTER, 46 }; 47 /** Key events that perform system operations. */ 48 private static final int[] SYS_KEYS = { 49 KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK, 50 KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL, 51 KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, 52 KeyEvent.KEYCODE_MUTE, 53 }; 54 /** If a physical key exists? */ 55 private static final boolean[] PHYSICAL_KEY_EXISTS = new boolean[KeyEvent.getMaxKeyCode() + 1]; 56 static { 57 for (int i = 0; i < PHYSICAL_KEY_EXISTS.length; ++i) { 58 PHYSICAL_KEY_EXISTS[i] = true; 59 } 60 // Only examine SYS_KEYS 61 for (int i = 0; i < SYS_KEYS.length; ++i) { 62 PHYSICAL_KEY_EXISTS[SYS_KEYS[i]] = KeyCharacterMap.deviceHasKey(SYS_KEYS[i]); 63 } 64 } 65 66 /** Nice names for all key events. */ 67 private static final String[] KEY_NAMES = { 68 "KEYCODE_UNKNOWN", 69 "KEYCODE_SOFT_LEFT", 70 "KEYCODE_SOFT_RIGHT", 71 "KEYCODE_HOME", 72 "KEYCODE_BACK", 73 "KEYCODE_CALL", 74 "KEYCODE_ENDCALL", 75 "KEYCODE_0", 76 "KEYCODE_1", 77 "KEYCODE_2", 78 "KEYCODE_3", 79 "KEYCODE_4", 80 "KEYCODE_5", 81 "KEYCODE_6", 82 "KEYCODE_7", 83 "KEYCODE_8", 84 "KEYCODE_9", 85 "KEYCODE_STAR", 86 "KEYCODE_POUND", 87 "KEYCODE_DPAD_UP", 88 "KEYCODE_DPAD_DOWN", 89 "KEYCODE_DPAD_LEFT", 90 "KEYCODE_DPAD_RIGHT", 91 "KEYCODE_DPAD_CENTER", 92 "KEYCODE_VOLUME_UP", 93 "KEYCODE_VOLUME_DOWN", 94 "KEYCODE_POWER", 95 "KEYCODE_CAMERA", 96 "KEYCODE_CLEAR", 97 "KEYCODE_A", 98 "KEYCODE_B", 99 "KEYCODE_C", 100 "KEYCODE_D", 101 "KEYCODE_E", 102 "KEYCODE_F", 103 "KEYCODE_G", 104 "KEYCODE_H", 105 "KEYCODE_I", 106 "KEYCODE_J", 107 "KEYCODE_K", 108 "KEYCODE_L", 109 "KEYCODE_M", 110 "KEYCODE_N", 111 "KEYCODE_O", 112 "KEYCODE_P", 113 "KEYCODE_Q", 114 "KEYCODE_R", 115 "KEYCODE_S", 116 "KEYCODE_T", 117 "KEYCODE_U", 118 "KEYCODE_V", 119 "KEYCODE_W", 120 "KEYCODE_X", 121 "KEYCODE_Y", 122 "KEYCODE_Z", 123 "KEYCODE_COMMA", 124 "KEYCODE_PERIOD", 125 "KEYCODE_ALT_LEFT", 126 "KEYCODE_ALT_RIGHT", 127 "KEYCODE_SHIFT_LEFT", 128 "KEYCODE_SHIFT_RIGHT", 129 "KEYCODE_TAB", 130 "KEYCODE_SPACE", 131 "KEYCODE_SYM", 132 "KEYCODE_EXPLORER", 133 "KEYCODE_ENVELOPE", 134 "KEYCODE_ENTER", 135 "KEYCODE_DEL", 136 "KEYCODE_GRAVE", 137 "KEYCODE_MINUS", 138 "KEYCODE_EQUALS", 139 "KEYCODE_LEFT_BRACKET", 140 "KEYCODE_RIGHT_BRACKET", 141 "KEYCODE_BACKSLASH", 142 "KEYCODE_SEMICOLON", 143 "KEYCODE_APOSTROPHE", 144 "KEYCODE_SLASH", 145 "KEYCODE_AT", 146 "KEYCODE_NUM", 147 "KEYCODE_HEADSETHOOK", 148 "KEYCODE_FOCUS", 149 "KEYCODE_PLUS", 150 "KEYCODE_MENU", 151 "KEYCODE_NOTIFICATION", 152 "KEYCODE_SEARCH", 153 "KEYCODE_PLAYPAUSE", 154 "KEYCODE_STOP", 155 "KEYCODE_NEXTSONG", 156 "KEYCODE_PREVIOUSSONG", 157 "KEYCODE_REWIND", 158 "KEYCODE_FORWARD", 159 "KEYCODE_MUTE", 160 "KEYCODE_PAGE_UP", 161 "KEYCODE_PAGE_DOWN", 162 "KEYCODE_PICTSYMBOLS", 163 "KEYCODE_SWITCH_CHARSET", 164 "KEYCODE_BUTTON_A", 165 "KEYCODE_BUTTON_B", 166 "KEYCODE_BUTTON_C", 167 "KEYCODE_BUTTON_X", 168 "KEYCODE_BUTTON_Y", 169 "KEYCODE_BUTTON_Z", 170 "KEYCODE_BUTTON_L1", 171 "KEYCODE_BUTTON_R1", 172 "KEYCODE_BUTTON_L2", 173 "KEYCODE_BUTTON_R2", 174 "KEYCODE_BUTTON_THUMBL", 175 "KEYCODE_BUTTON_THUMBR", 176 "KEYCODE_BUTTON_START", 177 "KEYCODE_BUTTON_SELECT", 178 "KEYCODE_BUTTON_MODE", 179 180 "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync 181 }; 182 183 public static final int FACTOR_TOUCH = 0; 184 public static final int FACTOR_MOTION = 1; 185 public static final int FACTOR_TRACKBALL = 2; 186 public static final int FACTOR_NAV = 3; 187 public static final int FACTOR_MAJORNAV = 4; 188 public static final int FACTOR_SYSOPS = 5; 189 public static final int FACTOR_APPSWITCH = 6; 190 public static final int FACTOR_FLIP = 7; 191 public static final int FACTOR_ANYTHING = 8; 192 public static final int FACTORZ_COUNT = 9; // should be last+1 193 194 195 /** percentages for each type of event. These will be remapped to working 196 * values after we read any optional values. 197 **/ 198 private float[] mFactors = new float[FACTORZ_COUNT]; 199 private ArrayList<ComponentName> mMainApps; 200 private int mEventCount = 0; //total number of events generated so far 201 private MonkeyEventQueue mQ; 202 private Random mRandom; 203 private int mVerbose = 0; 204 private long mThrottle = 0; 205 206 private boolean mKeyboardOpen = false; 207 208 /** 209 * @return the last name in the key list 210 */ 211 public static String getLastKeyName() { 212 return KEY_NAMES[KeyEvent.getMaxKeyCode() + 1]; 213 } 214 215 public static String getKeyName(int keycode) { 216 return KEY_NAMES[keycode]; 217 } 218 219 /** 220 * Looks up the keyCode from a given KEYCODE_NAME. NOTE: This may 221 * be an expensive operation. 222 * 223 * @param keyName the name of the KEYCODE_VALUE to lookup. 224 * @returns the intenger keyCode value, or -1 if not found 225 */ 226 public static int getKeyCode(String keyName) { 227 for (int x = 0; x < KEY_NAMES.length; x++) { 228 if (KEY_NAMES[x].equals(keyName)) { 229 return x; 230 } 231 } 232 return -1; 233 } 234 235 public MonkeySourceRandom(Random random, ArrayList<ComponentName> MainApps, 236 long throttle, boolean randomizeThrottle) { 237 // default values for random distributions 238 // note, these are straight percentages, to match user input (cmd line args) 239 // but they will be converted to 0..1 values before the main loop runs. 240 mFactors[FACTOR_TOUCH] = 15.0f; 241 mFactors[FACTOR_MOTION] = 10.0f; 242 mFactors[FACTOR_TRACKBALL] = 15.0f; 243 mFactors[FACTOR_NAV] = 25.0f; 244 mFactors[FACTOR_MAJORNAV] = 15.0f; 245 mFactors[FACTOR_SYSOPS] = 2.0f; 246 mFactors[FACTOR_APPSWITCH] = 2.0f; 247 mFactors[FACTOR_FLIP] = 1.0f; 248 mFactors[FACTOR_ANYTHING] = 15.0f; 249 250 mRandom = random; 251 mMainApps = MainApps; 252 mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle); 253 } 254 255 /** 256 * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale. 257 */ 258 private boolean adjustEventFactors() { 259 // go through all values and compute totals for user & default values 260 float userSum = 0.0f; 261 float defaultSum = 0.0f; 262 int defaultCount = 0; 263 for (int i = 0; i < FACTORZ_COUNT; ++i) { 264 if (mFactors[i] <= 0.0f) { // user values are zero or negative 265 userSum -= mFactors[i]; 266 } else { 267 defaultSum += mFactors[i]; 268 ++defaultCount; 269 } 270 } 271 272 // if the user request was > 100%, reject it 273 if (userSum > 100.0f) { 274 System.err.println("** Event weights > 100%"); 275 return false; 276 } 277 278 // if the user specified all of the weights, then they need to be 100% 279 if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) { 280 System.err.println("** Event weights != 100%"); 281 return false; 282 } 283 284 // compute the adjustment necessary 285 float defaultsTarget = (100.0f - userSum); 286 float defaultsAdjustment = defaultsTarget / defaultSum; 287 288 // fix all values, by adjusting defaults, or flipping user values back to >0 289 for (int i = 0; i < FACTORZ_COUNT; ++i) { 290 if (mFactors[i] <= 0.0f) { // user values are zero or negative 291 mFactors[i] = -mFactors[i]; 292 } else { 293 mFactors[i] *= defaultsAdjustment; 294 } 295 } 296 297 // if verbose, show factors 298 if (mVerbose > 0) { 299 System.out.println("// Event percentages:"); 300 for (int i = 0; i < FACTORZ_COUNT; ++i) { 301 System.out.println("// " + i + ": " + mFactors[i] + "%"); 302 } 303 } 304 305 if (!validateKeys()) { 306 return false; 307 } 308 309 // finally, normalize and convert to running sum 310 float sum = 0.0f; 311 for (int i = 0; i < FACTORZ_COUNT; ++i) { 312 sum += mFactors[i] / 100.0f; 313 mFactors[i] = sum; 314 } 315 return true; 316 } 317 318 private static boolean validateKeyCategory(String catName, int[] keys, float factor) { 319 if (factor < 0.1f) { 320 return true; 321 } 322 for (int i = 0; i < keys.length; ++i) { 323 if (PHYSICAL_KEY_EXISTS[keys[i]]) { 324 return true; 325 } 326 } 327 System.err.println("** " + catName + " has no physical keys but with factor " + factor + "%."); 328 return false; 329 } 330 331 /** 332 * See if any key exists for non-zero factors. 333 */ 334 private boolean validateKeys() { 335 return validateKeyCategory("NAV_KEYS", NAV_KEYS, mFactors[FACTOR_NAV]) 336 && validateKeyCategory("MAJOR_NAV_KEYS", MAJOR_NAV_KEYS, mFactors[FACTOR_MAJORNAV]) 337 && validateKeyCategory("SYS_KEYS", SYS_KEYS, mFactors[FACTOR_SYSOPS]); 338 } 339 340 /** 341 * set the factors 342 * 343 * @param factors percentages for each type of event 344 */ 345 public void setFactors(float factors[]) { 346 int c = FACTORZ_COUNT; 347 if (factors.length < c) { 348 c = factors.length; 349 } 350 for (int i = 0; i < c; i++) 351 mFactors[i] = factors[i]; 352 } 353 354 public void setFactors(int index, float v) { 355 mFactors[index] = v; 356 } 357 358 /** 359 * Generates a random motion event. This method counts a down, move, and up as multiple events. 360 * 361 * TODO: Test & fix the selectors when non-zero percentages 362 * TODO: Longpress. 363 * TODO: Fling. 364 * TODO: Meta state 365 * TODO: More useful than the random walk here would be to pick a single random direction 366 * and distance, and divvy it up into a random number of segments. (This would serve to 367 * generate fling gestures, which are important). 368 * 369 * @param random Random number source for positioning 370 * @param motionEvent If false, touch/release. If true, touch/move/release. 371 * 372 */ 373 private void generateMotionEvent(Random random, boolean motionEvent){ 374 375 Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); 376 377 float x = Math.abs(random.nextInt() % display.getWidth()); 378 float y = Math.abs(random.nextInt() % display.getHeight()); 379 long downAt = SystemClock.uptimeMillis(); 380 long eventTime = SystemClock.uptimeMillis(); 381 if (downAt == -1) { 382 downAt = eventTime; 383 } 384 385 MonkeyMotionEvent e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, 386 downAt, MotionEvent.ACTION_DOWN, x, y, 0); 387 e.setIntermediateNote(false); 388 mQ.addLast(e); 389 390 // sometimes we'll move during the touch 391 if (motionEvent) { 392 int count = random.nextInt(10); 393 for (int i = 0; i < count; i++) { 394 // generate some slop in the up event 395 x = (x + (random.nextInt() % 10)) % display.getWidth(); 396 y = (y + (random.nextInt() % 10)) % display.getHeight(); 397 398 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, 399 downAt, MotionEvent.ACTION_MOVE, x, y, 0); 400 e.setIntermediateNote(true); 401 mQ.addLast(e); 402 } 403 } 404 405 // TODO generate some slop in the up event 406 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, 407 downAt, MotionEvent.ACTION_UP, x, y, 0); 408 e.setIntermediateNote(false); 409 mQ.addLast(e); 410 } 411 412 /** 413 * Generates a random trackball event. This consists of a sequence of small moves, followed by 414 * an optional single click. 415 * 416 * TODO: Longpress. 417 * TODO: Meta state 418 * TODO: Parameterize the % clicked 419 * TODO: More useful than the random walk here would be to pick a single random direction 420 * and distance, and divvy it up into a random number of segments. (This would serve to 421 * generate fling gestures, which are important). 422 * 423 * @param random Random number source for positioning 424 * 425 */ 426 private void generateTrackballEvent(Random random) { 427 Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); 428 429 boolean drop = false; 430 int count = random.nextInt(10); 431 MonkeyMotionEvent e; 432 for (int i = 0; i < 10; ++i) { 433 // generate a small random step 434 int dX = random.nextInt(10) - 5; 435 int dY = random.nextInt(10) - 5; 436 437 438 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1, 439 MotionEvent.ACTION_MOVE, dX, dY, 0); 440 e.setIntermediateNote(i > 0); 441 mQ.addLast(e); 442 } 443 444 // 10% of trackball moves end with a click 445 if (0 == random.nextInt(10)) { 446 long downAt = SystemClock.uptimeMillis(); 447 448 449 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt, 450 MotionEvent.ACTION_DOWN, 0, 0, 0); 451 e.setIntermediateNote(true); 452 mQ.addLast(e); 453 454 455 e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt, 456 MotionEvent.ACTION_UP, 0, 0, 0); 457 e.setIntermediateNote(false); 458 mQ.addLast(e); 459 } 460 } 461 462 /** 463 * generate a random event based on mFactor 464 */ 465 private void generateEvents() { 466 float cls = mRandom.nextFloat(); 467 int lastKey = 0; 468 469 boolean touchEvent = cls < mFactors[FACTOR_TOUCH]; 470 boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]); 471 if (touchEvent || motionEvent) { 472 generateMotionEvent(mRandom, motionEvent); 473 return; 474 } 475 476 if (cls < mFactors[FACTOR_TRACKBALL]) { 477 generateTrackballEvent(mRandom); 478 return; 479 } 480 481 // The remaining event categories are injected as key events 482 do { 483 if (cls < mFactors[FACTOR_NAV]) { 484 lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)]; 485 } else if (cls < mFactors[FACTOR_MAJORNAV]) { 486 lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)]; 487 } else if (cls < mFactors[FACTOR_SYSOPS]) { 488 lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)]; 489 } else if (cls < mFactors[FACTOR_APPSWITCH]) { 490 MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get( 491 mRandom.nextInt(mMainApps.size()))); 492 mQ.addLast(e); 493 return; 494 } else if (cls < mFactors[FACTOR_FLIP]) { 495 MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen); 496 mKeyboardOpen = !mKeyboardOpen; 497 mQ.addLast(e); 498 return; 499 } else { 500 lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1); 501 } 502 } while (!PHYSICAL_KEY_EXISTS[lastKey]); 503 504 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey); 505 mQ.addLast(e); 506 507 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey); 508 mQ.addLast(e); 509 } 510 511 public boolean validate() { 512 //check factors 513 return adjustEventFactors(); 514 } 515 516 public void setVerbose(int verbose) { 517 mVerbose = verbose; 518 } 519 520 /** 521 * generate an activity event 522 */ 523 public void generateActivity() { 524 MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get( 525 mRandom.nextInt(mMainApps.size()))); 526 mQ.addLast(e); 527 } 528 529 /** 530 * if the queue is empty, we generate events first 531 * @return the first event in the queue 532 */ 533 public MonkeyEvent getNextEvent() { 534 if (mQ.isEmpty()) { 535 generateEvents(); 536 } 537 mEventCount++; 538 MonkeyEvent e = mQ.getFirst(); 539 mQ.removeFirst(); 540 return e; 541 } 542 } 543