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.graphics.PointF; 21 import android.hardware.display.DisplayManagerGlobal; 22 import android.os.SystemClock; 23 import android.view.Display; 24 import android.view.KeyCharacterMap; 25 import android.view.KeyEvent; 26 import android.view.MotionEvent; 27 import android.view.Surface; 28 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.Random; 32 33 /** 34 * monkey event queue 35 */ 36 public class MonkeySourceRandom implements MonkeyEventSource { 37 /** Key events that move around the UI. */ 38 private static final int[] NAV_KEYS = { 39 KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, 40 KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, 41 }; 42 /** 43 * Key events that perform major navigation options (so shouldn't be sent 44 * as much). 45 */ 46 private static final int[] MAJOR_NAV_KEYS = { 47 KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/ 48 KeyEvent.KEYCODE_DPAD_CENTER, 49 }; 50 /** Key events that perform system operations. */ 51 private static final int[] SYS_KEYS = { 52 KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK, 53 KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL, 54 KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE, 55 KeyEvent.KEYCODE_MUTE, 56 }; 57 /** If a physical key exists? */ 58 private static final boolean[] PHYSICAL_KEY_EXISTS = new boolean[KeyEvent.getMaxKeyCode() + 1]; 59 static { 60 for (int i = 0; i < PHYSICAL_KEY_EXISTS.length; ++i) { 61 PHYSICAL_KEY_EXISTS[i] = true; 62 } 63 // Only examine SYS_KEYS 64 for (int i = 0; i < SYS_KEYS.length; ++i) { 65 PHYSICAL_KEY_EXISTS[SYS_KEYS[i]] = KeyCharacterMap.deviceHasKey(SYS_KEYS[i]); 66 } 67 } 68 /** Possible screen rotation degrees **/ 69 private static final int[] SCREEN_ROTATION_DEGREES = { 70 Surface.ROTATION_0, 71 Surface.ROTATION_90, 72 Surface.ROTATION_180, 73 Surface.ROTATION_270, 74 }; 75 76 public static final int FACTOR_TOUCH = 0; 77 public static final int FACTOR_MOTION = 1; 78 public static final int FACTOR_PINCHZOOM = 2; 79 public static final int FACTOR_TRACKBALL = 3; 80 public static final int FACTOR_ROTATION = 4; 81 public static final int FACTOR_PERMISSION = 5; 82 public static final int FACTOR_NAV = 6; 83 public static final int FACTOR_MAJORNAV = 7; 84 public static final int FACTOR_SYSOPS = 8; 85 public static final int FACTOR_APPSWITCH = 9; 86 public static final int FACTOR_FLIP = 10; 87 public static final int FACTOR_ANYTHING = 11; 88 public static final int FACTORZ_COUNT = 12; // should be last+1 89 90 private static final int GESTURE_TAP = 0; 91 private static final int GESTURE_DRAG = 1; 92 private static final int GESTURE_PINCH_OR_ZOOM = 2; 93 94 /** percentages for each type of event. These will be remapped to working 95 * values after we read any optional values. 96 **/ 97 private float[] mFactors = new float[FACTORZ_COUNT]; 98 private HashMap<ComponentName, String> mMainApps; 99 private int mEventCount = 0; //total number of events generated so far 100 private MonkeyEventQueue mQ; 101 private Random mRandom; 102 private int mVerbose = 0; 103 private long mThrottle = 0; 104 private MonkeyPermissionUtil mPermissionUtil; 105 106 private boolean mKeyboardOpen = false; 107 getKeyName(int keycode)108 public static String getKeyName(int keycode) { 109 return KeyEvent.keyCodeToString(keycode); 110 } 111 112 /** 113 * Looks up the keyCode from a given KEYCODE_NAME. NOTE: This may 114 * be an expensive operation. 115 * 116 * @param keyName the name of the KEYCODE_VALUE to lookup. 117 * @returns the intenger keyCode value, or KeyEvent.KEYCODE_UNKNOWN if not found 118 */ getKeyCode(String keyName)119 public static int getKeyCode(String keyName) { 120 return KeyEvent.keyCodeFromString(keyName); 121 } 122 MonkeySourceRandom(Random random, HashMap<ComponentName, String> MainApps, long throttle, boolean randomizeThrottle, boolean permissionTargetSystem)123 public MonkeySourceRandom(Random random, HashMap<ComponentName, String> MainApps, 124 long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) { 125 // default values for random distributions 126 // note, these are straight percentages, to match user input (cmd line args) 127 // but they will be converted to 0..1 values before the main loop runs. 128 mFactors[FACTOR_TOUCH] = 15.0f; 129 mFactors[FACTOR_MOTION] = 10.0f; 130 mFactors[FACTOR_TRACKBALL] = 15.0f; 131 // Adjust the values if we want to enable rotation by default. 132 mFactors[FACTOR_ROTATION] = 0.0f; 133 mFactors[FACTOR_NAV] = 25.0f; 134 mFactors[FACTOR_MAJORNAV] = 15.0f; 135 mFactors[FACTOR_SYSOPS] = 2.0f; 136 mFactors[FACTOR_APPSWITCH] = 2.0f; 137 mFactors[FACTOR_FLIP] = 1.0f; 138 // disbale permission by default 139 mFactors[FACTOR_PERMISSION] = 0.0f; 140 mFactors[FACTOR_ANYTHING] = 13.0f; 141 mFactors[FACTOR_PINCHZOOM] = 2.0f; 142 143 mRandom = random; 144 mMainApps = MainApps; 145 mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle); 146 mPermissionUtil = new MonkeyPermissionUtil(); 147 mPermissionUtil.setTargetSystemPackages(permissionTargetSystem); 148 } 149 150 /** 151 * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale. 152 */ adjustEventFactors()153 private boolean adjustEventFactors() { 154 // go through all values and compute totals for user & default values 155 float userSum = 0.0f; 156 float defaultSum = 0.0f; 157 int defaultCount = 0; 158 for (int i = 0; i < FACTORZ_COUNT; ++i) { 159 if (mFactors[i] <= 0.0f) { // user values are zero or negative 160 userSum -= mFactors[i]; 161 } else { 162 defaultSum += mFactors[i]; 163 ++defaultCount; 164 } 165 } 166 167 // if the user request was > 100%, reject it 168 if (userSum > 100.0f) { 169 Logger.err.println("** Event weights > 100%"); 170 return false; 171 } 172 173 // if the user specified all of the weights, then they need to be 100% 174 if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) { 175 Logger.err.println("** Event weights != 100%"); 176 return false; 177 } 178 179 // compute the adjustment necessary 180 float defaultsTarget = (100.0f - userSum); 181 float defaultsAdjustment = defaultsTarget / defaultSum; 182 183 // fix all values, by adjusting defaults, or flipping user values back to >0 184 for (int i = 0; i < FACTORZ_COUNT; ++i) { 185 if (mFactors[i] <= 0.0f) { // user values are zero or negative 186 mFactors[i] = -mFactors[i]; 187 } else { 188 mFactors[i] *= defaultsAdjustment; 189 } 190 } 191 192 // if verbose, show factors 193 if (mVerbose > 0) { 194 Logger.out.println("// Event percentages:"); 195 for (int i = 0; i < FACTORZ_COUNT; ++i) { 196 Logger.out.println("// " + i + ": " + mFactors[i] + "%"); 197 } 198 } 199 200 if (!validateKeys()) { 201 return false; 202 } 203 204 // finally, normalize and convert to running sum 205 float sum = 0.0f; 206 for (int i = 0; i < FACTORZ_COUNT; ++i) { 207 sum += mFactors[i] / 100.0f; 208 mFactors[i] = sum; 209 } 210 return true; 211 } 212 validateKeyCategory(String catName, int[] keys, float factor)213 private static boolean validateKeyCategory(String catName, int[] keys, float factor) { 214 if (factor < 0.1f) { 215 return true; 216 } 217 for (int i = 0; i < keys.length; ++i) { 218 if (PHYSICAL_KEY_EXISTS[keys[i]]) { 219 return true; 220 } 221 } 222 Logger.err.println("** " + catName + " has no physical keys but with factor " + factor + "%."); 223 return false; 224 } 225 226 /** 227 * See if any key exists for non-zero factors. 228 */ validateKeys()229 private boolean validateKeys() { 230 return validateKeyCategory("NAV_KEYS", NAV_KEYS, mFactors[FACTOR_NAV]) 231 && validateKeyCategory("MAJOR_NAV_KEYS", MAJOR_NAV_KEYS, mFactors[FACTOR_MAJORNAV]) 232 && validateKeyCategory("SYS_KEYS", SYS_KEYS, mFactors[FACTOR_SYSOPS]); 233 } 234 235 /** 236 * set the factors 237 * 238 * @param factors percentages for each type of event 239 */ setFactors(float factors[])240 public void setFactors(float factors[]) { 241 int c = FACTORZ_COUNT; 242 if (factors.length < c) { 243 c = factors.length; 244 } 245 for (int i = 0; i < c; i++) 246 mFactors[i] = factors[i]; 247 } 248 setFactors(int index, float v)249 public void setFactors(int index, float v) { 250 mFactors[index] = v; 251 } 252 253 /** 254 * Generates a random motion event. This method counts a down, move, and up as multiple events. 255 * 256 * TODO: Test & fix the selectors when non-zero percentages 257 * TODO: Longpress. 258 * TODO: Fling. 259 * TODO: Meta state 260 * TODO: More useful than the random walk here would be to pick a single random direction 261 * and distance, and divvy it up into a random number of segments. (This would serve to 262 * generate fling gestures, which are important). 263 * 264 * @param random Random number source for positioning 265 * @param gesture The gesture to perform. 266 * 267 */ generatePointerEvent(Random random, int gesture)268 private void generatePointerEvent(Random random, int gesture) { 269 Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); 270 271 PointF p1 = randomPoint(random, display); 272 PointF v1 = randomVector(random); 273 274 long downAt = SystemClock.uptimeMillis(); 275 276 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN) 277 .setDownTime(downAt) 278 .addPointer(0, p1.x, p1.y) 279 .setIntermediateNote(false)); 280 281 // sometimes we'll move during the touch 282 if (gesture == GESTURE_DRAG) { 283 int count = random.nextInt(10); 284 for (int i = 0; i < count; i++) { 285 randomWalk(random, display, p1, v1); 286 287 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE) 288 .setDownTime(downAt) 289 .addPointer(0, p1.x, p1.y) 290 .setIntermediateNote(true)); 291 } 292 } else if (gesture == GESTURE_PINCH_OR_ZOOM) { 293 PointF p2 = randomPoint(random, display); 294 PointF v2 = randomVector(random); 295 296 randomWalk(random, display, p1, v1); 297 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN 298 | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)) 299 .setDownTime(downAt) 300 .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y) 301 .setIntermediateNote(true)); 302 303 int count = random.nextInt(10); 304 for (int i = 0; i < count; i++) { 305 randomWalk(random, display, p1, v1); 306 randomWalk(random, display, p2, v2); 307 308 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE) 309 .setDownTime(downAt) 310 .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y) 311 .setIntermediateNote(true)); 312 } 313 314 randomWalk(random, display, p1, v1); 315 randomWalk(random, display, p2, v2); 316 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_UP 317 | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT)) 318 .setDownTime(downAt) 319 .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y) 320 .setIntermediateNote(true)); 321 } 322 323 randomWalk(random, display, p1, v1); 324 mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP) 325 .setDownTime(downAt) 326 .addPointer(0, p1.x, p1.y) 327 .setIntermediateNote(false)); 328 } 329 randomPoint(Random random, Display display)330 private PointF randomPoint(Random random, Display display) { 331 return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight())); 332 } 333 randomVector(Random random)334 private PointF randomVector(Random random) { 335 return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50); 336 } 337 randomWalk(Random random, Display display, PointF point, PointF vector)338 private void randomWalk(Random random, Display display, PointF point, PointF vector) { 339 point.x = (float) Math.max(Math.min(point.x + random.nextFloat() * vector.x, 340 display.getWidth()), 0); 341 point.y = (float) Math.max(Math.min(point.y + random.nextFloat() * vector.y, 342 display.getHeight()), 0); 343 } 344 345 /** 346 * Generates a random trackball event. This consists of a sequence of small moves, followed by 347 * an optional single click. 348 * 349 * TODO: Longpress. 350 * TODO: Meta state 351 * TODO: Parameterize the % clicked 352 * TODO: More useful than the random walk here would be to pick a single random direction 353 * and distance, and divvy it up into a random number of segments. (This would serve to 354 * generate fling gestures, which are important). 355 * 356 * @param random Random number source for positioning 357 * 358 */ generateTrackballEvent(Random random)359 private void generateTrackballEvent(Random random) { 360 for (int i = 0; i < 10; ++i) { 361 // generate a small random step 362 int dX = random.nextInt(10) - 5; 363 int dY = random.nextInt(10) - 5; 364 365 mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE) 366 .addPointer(0, dX, dY) 367 .setIntermediateNote(i > 0)); 368 } 369 370 // 10% of trackball moves end with a click 371 if (0 == random.nextInt(10)) { 372 long downAt = SystemClock.uptimeMillis(); 373 374 mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_DOWN) 375 .setDownTime(downAt) 376 .addPointer(0, 0, 0) 377 .setIntermediateNote(true)); 378 379 mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_UP) 380 .setDownTime(downAt) 381 .addPointer(0, 0, 0) 382 .setIntermediateNote(false)); 383 } 384 } 385 386 /** 387 * Generates a random screen rotation event. 388 * 389 * @param random Random number source for rotation degree. 390 */ generateRotationEvent(Random random)391 private void generateRotationEvent(Random random) { 392 mQ.addLast(new MonkeyRotationEvent( 393 SCREEN_ROTATION_DEGREES[random.nextInt( 394 SCREEN_ROTATION_DEGREES.length)], 395 random.nextBoolean())); 396 } 397 398 /** 399 * generate a random event based on mFactor 400 */ generateEvents()401 private void generateEvents() { 402 float cls = mRandom.nextFloat(); 403 int lastKey = 0; 404 405 if (cls < mFactors[FACTOR_TOUCH]) { 406 generatePointerEvent(mRandom, GESTURE_TAP); 407 return; 408 } else if (cls < mFactors[FACTOR_MOTION]) { 409 generatePointerEvent(mRandom, GESTURE_DRAG); 410 return; 411 } else if (cls < mFactors[FACTOR_PINCHZOOM]) { 412 generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM); 413 return; 414 } else if (cls < mFactors[FACTOR_TRACKBALL]) { 415 generateTrackballEvent(mRandom); 416 return; 417 } else if (cls < mFactors[FACTOR_ROTATION]) { 418 generateRotationEvent(mRandom); 419 return; 420 } else if (cls < mFactors[FACTOR_PERMISSION]) { 421 mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom)); 422 return; 423 } 424 425 // The remaining event categories are injected as key events 426 for (;;) { 427 if (cls < mFactors[FACTOR_NAV]) { 428 lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)]; 429 } else if (cls < mFactors[FACTOR_MAJORNAV]) { 430 lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)]; 431 } else if (cls < mFactors[FACTOR_SYSOPS]) { 432 lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)]; 433 } else if (cls < mFactors[FACTOR_APPSWITCH]) { 434 MonkeyActivityEvent e = new MonkeyActivityEvent(new ArrayList<ComponentName>(mMainApps.keySet()).get( 435 mRandom.nextInt(mMainApps.size())), mMainApps); 436 mQ.addLast(e); 437 return; 438 } else if (cls < mFactors[FACTOR_FLIP]) { 439 MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen); 440 mKeyboardOpen = !mKeyboardOpen; 441 mQ.addLast(e); 442 return; 443 } else { 444 lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1); 445 } 446 447 if (lastKey != KeyEvent.KEYCODE_POWER 448 && lastKey != KeyEvent.KEYCODE_ENDCALL 449 && lastKey != KeyEvent.KEYCODE_SLEEP 450 && lastKey != KeyEvent.KEYCODE_SOFT_SLEEP 451 && PHYSICAL_KEY_EXISTS[lastKey]) { 452 break; 453 } 454 } 455 456 MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey); 457 mQ.addLast(e); 458 459 e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey); 460 mQ.addLast(e); 461 } 462 validate()463 public boolean validate() { 464 boolean ret = true; 465 // only populate & dump permissions if enabled 466 if (mFactors[FACTOR_PERMISSION] != 0.0f) { 467 ret &= mPermissionUtil.populatePermissionsMapping(); 468 if (ret && mVerbose >= 2) { 469 mPermissionUtil.dump(); 470 } 471 } 472 return ret & adjustEventFactors(); 473 } 474 setVerbose(int verbose)475 public void setVerbose(int verbose) { 476 mVerbose = verbose; 477 } 478 479 /** 480 * generate an activity event 481 */ generateActivity()482 public void generateActivity() { 483 MonkeyActivityEvent e = new MonkeyActivityEvent(new ArrayList<ComponentName>(mMainApps.keySet()).get( 484 mRandom.nextInt(mMainApps.size())), mMainApps); 485 mQ.addLast(e); 486 } 487 488 /** 489 * if the queue is empty, we generate events first 490 * @return the first event in the queue 491 */ getNextEvent()492 public MonkeyEvent getNextEvent() { 493 if (mQ.isEmpty()) { 494 generateEvents(); 495 } 496 mEventCount++; 497 MonkeyEvent e = mQ.getFirst(); 498 mQ.removeFirst(); 499 return e; 500 } 501 } 502