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