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