/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
https://github.com/CyanogenMod/android_development · Java · 500 lines · 337 code · 54 blank · 109 comment · 70 complexity · 26e262c780fee5dd5fa814012b48fd4c MD5 · raw file
- /*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.android.commands.monkey;
- import android.content.ComponentName;
- import android.graphics.PointF;
- import android.hardware.display.DisplayManagerGlobal;
- import android.os.SystemClock;
- import android.view.Display;
- import android.view.KeyCharacterMap;
- import android.view.KeyEvent;
- import android.view.MotionEvent;
- import android.view.Surface;
- import java.util.List;
- import java.util.Random;
- /**
- * monkey event queue
- */
- public class MonkeySourceRandom implements MonkeyEventSource {
- /** Key events that move around the UI. */
- private static final int[] NAV_KEYS = {
- KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
- KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
- };
- /**
- * Key events that perform major navigation options (so shouldn't be sent
- * as much).
- */
- private static final int[] MAJOR_NAV_KEYS = {
- KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
- KeyEvent.KEYCODE_DPAD_CENTER,
- };
- /** Key events that perform system operations. */
- private static final int[] SYS_KEYS = {
- KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK,
- KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL,
- KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE,
- KeyEvent.KEYCODE_MUTE,
- };
- /** If a physical key exists? */
- private static final boolean[] PHYSICAL_KEY_EXISTS = new boolean[KeyEvent.getMaxKeyCode() + 1];
- static {
- for (int i = 0; i < PHYSICAL_KEY_EXISTS.length; ++i) {
- PHYSICAL_KEY_EXISTS[i] = true;
- }
- // Only examine SYS_KEYS
- for (int i = 0; i < SYS_KEYS.length; ++i) {
- PHYSICAL_KEY_EXISTS[SYS_KEYS[i]] = KeyCharacterMap.deviceHasKey(SYS_KEYS[i]);
- }
- }
- /** Possible screen rotation degrees **/
- private static final int[] SCREEN_ROTATION_DEGREES = {
- Surface.ROTATION_0,
- Surface.ROTATION_90,
- Surface.ROTATION_180,
- Surface.ROTATION_270,
- };
- public static final int FACTOR_TOUCH = 0;
- public static final int FACTOR_MOTION = 1;
- public static final int FACTOR_PINCHZOOM = 2;
- public static final int FACTOR_TRACKBALL = 3;
- public static final int FACTOR_ROTATION = 4;
- public static final int FACTOR_PERMISSION = 5;
- public static final int FACTOR_NAV = 6;
- public static final int FACTOR_MAJORNAV = 7;
- public static final int FACTOR_SYSOPS = 8;
- public static final int FACTOR_APPSWITCH = 9;
- public static final int FACTOR_FLIP = 10;
- public static final int FACTOR_ANYTHING = 11;
- public static final int FACTORZ_COUNT = 12; // should be last+1
- private static final int GESTURE_TAP = 0;
- private static final int GESTURE_DRAG = 1;
- private static final int GESTURE_PINCH_OR_ZOOM = 2;
- /** percentages for each type of event. These will be remapped to working
- * values after we read any optional values.
- **/
- private float[] mFactors = new float[FACTORZ_COUNT];
- private List<ComponentName> mMainApps;
- private int mEventCount = 0; //total number of events generated so far
- private MonkeyEventQueue mQ;
- private Random mRandom;
- private int mVerbose = 0;
- private long mThrottle = 0;
- private MonkeyPermissionUtil mPermissionUtil;
- private boolean mKeyboardOpen = false;
- public static String getKeyName(int keycode) {
- return KeyEvent.keyCodeToString(keycode);
- }
- /**
- * Looks up the keyCode from a given KEYCODE_NAME. NOTE: This may
- * be an expensive operation.
- *
- * @param keyName the name of the KEYCODE_VALUE to lookup.
- * @returns the intenger keyCode value, or KeyEvent.KEYCODE_UNKNOWN if not found
- */
- public static int getKeyCode(String keyName) {
- return KeyEvent.keyCodeFromString(keyName);
- }
- public MonkeySourceRandom(Random random, List<ComponentName> MainApps,
- long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) {
- // default values for random distributions
- // note, these are straight percentages, to match user input (cmd line args)
- // but they will be converted to 0..1 values before the main loop runs.
- mFactors[FACTOR_TOUCH] = 15.0f;
- mFactors[FACTOR_MOTION] = 10.0f;
- mFactors[FACTOR_TRACKBALL] = 15.0f;
- // Adjust the values if we want to enable rotation by default.
- mFactors[FACTOR_ROTATION] = 0.0f;
- mFactors[FACTOR_NAV] = 25.0f;
- mFactors[FACTOR_MAJORNAV] = 15.0f;
- mFactors[FACTOR_SYSOPS] = 2.0f;
- mFactors[FACTOR_APPSWITCH] = 2.0f;
- mFactors[FACTOR_FLIP] = 1.0f;
- // disbale permission by default
- mFactors[FACTOR_PERMISSION] = 0.0f;
- mFactors[FACTOR_ANYTHING] = 13.0f;
- mFactors[FACTOR_PINCHZOOM] = 2.0f;
- mRandom = random;
- mMainApps = MainApps;
- mQ = new MonkeyEventQueue(random, throttle, randomizeThrottle);
- mPermissionUtil = new MonkeyPermissionUtil();
- mPermissionUtil.setTargetSystemPackages(permissionTargetSystem);
- }
- /**
- * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
- */
- private boolean adjustEventFactors() {
- // go through all values and compute totals for user & default values
- float userSum = 0.0f;
- float defaultSum = 0.0f;
- int defaultCount = 0;
- for (int i = 0; i < FACTORZ_COUNT; ++i) {
- if (mFactors[i] <= 0.0f) { // user values are zero or negative
- userSum -= mFactors[i];
- } else {
- defaultSum += mFactors[i];
- ++defaultCount;
- }
- }
- // if the user request was > 100%, reject it
- if (userSum > 100.0f) {
- System.err.println("** Event weights > 100%");
- return false;
- }
- // if the user specified all of the weights, then they need to be 100%
- if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
- System.err.println("** Event weights != 100%");
- return false;
- }
- // compute the adjustment necessary
- float defaultsTarget = (100.0f - userSum);
- float defaultsAdjustment = defaultsTarget / defaultSum;
- // fix all values, by adjusting defaults, or flipping user values back to >0
- for (int i = 0; i < FACTORZ_COUNT; ++i) {
- if (mFactors[i] <= 0.0f) { // user values are zero or negative
- mFactors[i] = -mFactors[i];
- } else {
- mFactors[i] *= defaultsAdjustment;
- }
- }
- // if verbose, show factors
- if (mVerbose > 0) {
- System.out.println("// Event percentages:");
- for (int i = 0; i < FACTORZ_COUNT; ++i) {
- System.out.println("// " + i + ": " + mFactors[i] + "%");
- }
- }
- if (!validateKeys()) {
- return false;
- }
- // finally, normalize and convert to running sum
- float sum = 0.0f;
- for (int i = 0; i < FACTORZ_COUNT; ++i) {
- sum += mFactors[i] / 100.0f;
- mFactors[i] = sum;
- }
- return true;
- }
- private static boolean validateKeyCategory(String catName, int[] keys, float factor) {
- if (factor < 0.1f) {
- return true;
- }
- for (int i = 0; i < keys.length; ++i) {
- if (PHYSICAL_KEY_EXISTS[keys[i]]) {
- return true;
- }
- }
- System.err.println("** " + catName + " has no physical keys but with factor " + factor + "%.");
- return false;
- }
- /**
- * See if any key exists for non-zero factors.
- */
- private boolean validateKeys() {
- return validateKeyCategory("NAV_KEYS", NAV_KEYS, mFactors[FACTOR_NAV])
- && validateKeyCategory("MAJOR_NAV_KEYS", MAJOR_NAV_KEYS, mFactors[FACTOR_MAJORNAV])
- && validateKeyCategory("SYS_KEYS", SYS_KEYS, mFactors[FACTOR_SYSOPS]);
- }
- /**
- * set the factors
- *
- * @param factors percentages for each type of event
- */
- public void setFactors(float factors[]) {
- int c = FACTORZ_COUNT;
- if (factors.length < c) {
- c = factors.length;
- }
- for (int i = 0; i < c; i++)
- mFactors[i] = factors[i];
- }
- public void setFactors(int index, float v) {
- mFactors[index] = v;
- }
- /**
- * Generates a random motion event. This method counts a down, move, and up as multiple events.
- *
- * TODO: Test & fix the selectors when non-zero percentages
- * TODO: Longpress.
- * TODO: Fling.
- * TODO: Meta state
- * TODO: More useful than the random walk here would be to pick a single random direction
- * and distance, and divvy it up into a random number of segments. (This would serve to
- * generate fling gestures, which are important).
- *
- * @param random Random number source for positioning
- * @param gesture The gesture to perform.
- *
- */
- private void generatePointerEvent(Random random, int gesture) {
- Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
- PointF p1 = randomPoint(random, display);
- PointF v1 = randomVector(random);
- long downAt = SystemClock.uptimeMillis();
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y)
- .setIntermediateNote(false));
- // sometimes we'll move during the touch
- if (gesture == GESTURE_DRAG) {
- int count = random.nextInt(10);
- for (int i = 0; i < count; i++) {
- randomWalk(random, display, p1, v1);
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE)
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y)
- .setIntermediateNote(true));
- }
- } else if (gesture == GESTURE_PINCH_OR_ZOOM) {
- PointF p2 = randomPoint(random, display);
- PointF v2 = randomVector(random);
- randomWalk(random, display, p1, v1);
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_DOWN
- | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT))
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
- .setIntermediateNote(true));
- int count = random.nextInt(10);
- for (int i = 0; i < count; i++) {
- randomWalk(random, display, p1, v1);
- randomWalk(random, display, p2, v2);
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_MOVE)
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
- .setIntermediateNote(true));
- }
- randomWalk(random, display, p1, v1);
- randomWalk(random, display, p2, v2);
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_POINTER_UP
- | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT))
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y).addPointer(1, p2.x, p2.y)
- .setIntermediateNote(true));
- }
- randomWalk(random, display, p1, v1);
- mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
- .setDownTime(downAt)
- .addPointer(0, p1.x, p1.y)
- .setIntermediateNote(false));
- }
- private PointF randomPoint(Random random, Display display) {
- return new PointF(random.nextInt(display.getWidth()), random.nextInt(display.getHeight()));
- }
- private PointF randomVector(Random random) {
- return new PointF((random.nextFloat() - 0.5f) * 50, (random.nextFloat() - 0.5f) * 50);
- }
- private void randomWalk(Random random, Display display, PointF point, PointF vector) {
- point.x = (float) Math.max(Math.min(point.x + random.nextFloat() * vector.x,
- display.getWidth()), 0);
- point.y = (float) Math.max(Math.min(point.y + random.nextFloat() * vector.y,
- display.getHeight()), 0);
- }
- /**
- * Generates a random trackball event. This consists of a sequence of small moves, followed by
- * an optional single click.
- *
- * TODO: Longpress.
- * TODO: Meta state
- * TODO: Parameterize the % clicked
- * TODO: More useful than the random walk here would be to pick a single random direction
- * and distance, and divvy it up into a random number of segments. (This would serve to
- * generate fling gestures, which are important).
- *
- * @param random Random number source for positioning
- *
- */
- private void generateTrackballEvent(Random random) {
- for (int i = 0; i < 10; ++i) {
- // generate a small random step
- int dX = random.nextInt(10) - 5;
- int dY = random.nextInt(10) - 5;
- mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_MOVE)
- .addPointer(0, dX, dY)
- .setIntermediateNote(i > 0));
- }
- // 10% of trackball moves end with a click
- if (0 == random.nextInt(10)) {
- long downAt = SystemClock.uptimeMillis();
- mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_DOWN)
- .setDownTime(downAt)
- .addPointer(0, 0, 0)
- .setIntermediateNote(true));
- mQ.addLast(new MonkeyTrackballEvent(MotionEvent.ACTION_UP)
- .setDownTime(downAt)
- .addPointer(0, 0, 0)
- .setIntermediateNote(false));
- }
- }
- /**
- * Generates a random screen rotation event.
- *
- * @param random Random number source for rotation degree.
- */
- private void generateRotationEvent(Random random) {
- mQ.addLast(new MonkeyRotationEvent(
- SCREEN_ROTATION_DEGREES[random.nextInt(
- SCREEN_ROTATION_DEGREES.length)],
- random.nextBoolean()));
- }
- /**
- * generate a random event based on mFactor
- */
- private void generateEvents() {
- float cls = mRandom.nextFloat();
- int lastKey = 0;
- if (cls < mFactors[FACTOR_TOUCH]) {
- generatePointerEvent(mRandom, GESTURE_TAP);
- return;
- } else if (cls < mFactors[FACTOR_MOTION]) {
- generatePointerEvent(mRandom, GESTURE_DRAG);
- return;
- } else if (cls < mFactors[FACTOR_PINCHZOOM]) {
- generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM);
- return;
- } else if (cls < mFactors[FACTOR_TRACKBALL]) {
- generateTrackballEvent(mRandom);
- return;
- } else if (cls < mFactors[FACTOR_ROTATION]) {
- generateRotationEvent(mRandom);
- return;
- } else if (cls < mFactors[FACTOR_PERMISSION]) {
- mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));
- return;
- }
- // The remaining event categories are injected as key events
- for (;;) {
- if (cls < mFactors[FACTOR_NAV]) {
- lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
- } else if (cls < mFactors[FACTOR_MAJORNAV]) {
- lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
- } else if (cls < mFactors[FACTOR_SYSOPS]) {
- lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
- } else if (cls < mFactors[FACTOR_APPSWITCH]) {
- MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
- mRandom.nextInt(mMainApps.size())));
- mQ.addLast(e);
- return;
- } else if (cls < mFactors[FACTOR_FLIP]) {
- MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
- mKeyboardOpen = !mKeyboardOpen;
- mQ.addLast(e);
- return;
- } else {
- lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
- }
- if (lastKey != KeyEvent.KEYCODE_POWER
- && lastKey != KeyEvent.KEYCODE_ENDCALL
- && lastKey != KeyEvent.KEYCODE_SLEEP
- && lastKey != KeyEvent.KEYCODE_SOFT_SLEEP
- && PHYSICAL_KEY_EXISTS[lastKey]) {
- break;
- }
- }
- MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
- mQ.addLast(e);
- e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
- mQ.addLast(e);
- }
- public boolean validate() {
- boolean ret = true;
- // only populate & dump permissions if enabled
- if (mFactors[FACTOR_PERMISSION] != 0.0f) {
- ret &= mPermissionUtil.populatePermissionsMapping();
- if (ret && mVerbose >= 2) {
- mPermissionUtil.dump();
- }
- }
- return ret & adjustEventFactors();
- }
- public void setVerbose(int verbose) {
- mVerbose = verbose;
- }
- /**
- * generate an activity event
- */
- public void generateActivity() {
- MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
- mRandom.nextInt(mMainApps.size())));
- mQ.addLast(e);
- }
- /**
- * if the queue is empty, we generate events first
- * @return the first event in the queue
- */
- public MonkeyEvent getNextEvent() {
- if (mQ.isEmpty()) {
- generateEvents();
- }
- mEventCount++;
- MonkeyEvent e = mQ.getFirst();
- mQ.removeFirst();
- return e;
- }
- }