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