diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:04:04 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-12-17 18:04:04 -0800 |
| commit | e943f2fd8e7623ba3ea12bb65294d30446db4174 (patch) | |
| tree | eda2f24648d61fb33f3fa754d9fb4b5693517e0c /cmds/monkey | |
| parent | 5c11852110eeb03dc5a69111354b383f98d15336 (diff) | |
| download | android_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.tar.gz android_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.tar.bz2 android_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.zip | |
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'cmds/monkey')
9 files changed, 1540 insertions, 505 deletions
diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java index 5a8080cda..f6ab19d73 100644 --- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java +++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java @@ -28,13 +28,8 @@ import android.os.Debug; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.server.data.CrashData; -import android.view.Display; import android.view.IWindowManager; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.WindowManagerImpl; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -43,12 +38,10 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Random; /** * Application that injects random key events and other actions into the system. @@ -62,117 +55,6 @@ public class Monkey { */ private final static int DEBUG_ALLOW_ANY_STARTS = 0; private final static int DEBUG_ALLOW_ANY_RESTARTS = 0; - - /** Key events that move around the UI. */ - private 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 final int[] MAJOR_NAV_KEYS = { - KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/ - KeyEvent.KEYCODE_DPAD_CENTER, - }; - /** Key events that perform system operations. */ - private final int[] SYS_KEYS = { - KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK, - KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL, - KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, - }; - /** Nice names for all key events. */ - private final String[] KEY_NAMES = { - "KEYCODE_UNKNOWN", - "KEYCODE_MENU", - "KEYCODE_SOFT_RIGHT", - "KEYCODE_HOME", - "KEYCODE_BACK", - "KEYCODE_CALL", - "KEYCODE_ENDCALL", - "KEYCODE_0", - "KEYCODE_1", - "KEYCODE_2", - "KEYCODE_3", - "KEYCODE_4", - "KEYCODE_5", - "KEYCODE_6", - "KEYCODE_7", - "KEYCODE_8", - "KEYCODE_9", - "KEYCODE_STAR", - "KEYCODE_POUND", - "KEYCODE_DPAD_UP", - "KEYCODE_DPAD_DOWN", - "KEYCODE_DPAD_LEFT", - "KEYCODE_DPAD_RIGHT", - "KEYCODE_DPAD_CENTER", - "KEYCODE_VOLUME_UP", - "KEYCODE_VOLUME_DOWN", - "KEYCODE_POWER", - "KEYCODE_CAMERA", - "KEYCODE_CLEAR", - "KEYCODE_A", - "KEYCODE_B", - "KEYCODE_C", - "KEYCODE_D", - "KEYCODE_E", - "KEYCODE_F", - "KEYCODE_G", - "KEYCODE_H", - "KEYCODE_I", - "KEYCODE_J", - "KEYCODE_K", - "KEYCODE_L", - "KEYCODE_M", - "KEYCODE_N", - "KEYCODE_O", - "KEYCODE_P", - "KEYCODE_Q", - "KEYCODE_R", - "KEYCODE_S", - "KEYCODE_T", - "KEYCODE_U", - "KEYCODE_V", - "KEYCODE_W", - "KEYCODE_X", - "KEYCODE_Y", - "KEYCODE_Z", - "KEYCODE_COMMA", - "KEYCODE_PERIOD", - "KEYCODE_ALT_LEFT", - "KEYCODE_ALT_RIGHT", - "KEYCODE_SHIFT_LEFT", - "KEYCODE_SHIFT_RIGHT", - "KEYCODE_TAB", - "KEYCODE_SPACE", - "KEYCODE_SYM", - "KEYCODE_EXPLORER", - "KEYCODE_ENVELOPE", - "KEYCODE_ENTER", - "KEYCODE_DEL", - "KEYCODE_GRAVE", - "KEYCODE_MINUS", - "KEYCODE_EQUALS", - "KEYCODE_LEFT_BRACKET", - "KEYCODE_RIGHT_BRACKET", - "KEYCODE_BACKSLASH", - "KEYCODE_SEMICOLON", - "KEYCODE_APOSTROPHE", - "KEYCODE_SLASH", - "KEYCODE_AT", - "KEYCODE_NUM", - "KEYCODE_HEADSETHOOK", - "KEYCODE_FOCUS", - "KEYCODE_PLUS", - "KEYCODE_MENU", - "KEYCODE_NOTIFICATION", - "KEYCODE_SEARCH", - - "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync - }; - private IActivityManager mAm; private IWindowManager mWm; private IPackageManager mPm; @@ -238,25 +120,16 @@ public class Monkey { long mDroppedKeyEvents = 0; long mDroppedPointerEvents = 0; long mDroppedTrackballEvents = 0; - - /** percentages for each type of event. These will be remapped to working - * values after we read any optional values. - **/ - public static final int FACTOR_TOUCH = 0; - public static final int FACTOR_MOTION = 1; - public static final int FACTOR_TRACKBALL = 2; - public static final int FACTOR_NAV = 3; - public static final int FACTOR_MAJORNAV = 4; - public static final int FACTOR_SYSOPS = 5; - public static final int FACTOR_APPSWITCH = 6; - public static final int FACTOR_ANYTHING = 7; - - public static final int FACTORZ_COUNT = 8; // should be last+1 - - float[] mFactors = new float[FACTORZ_COUNT]; + long mDroppedFlipEvents = 0; + + /** a filename to the script (if any) **/ + private String mScriptFileName = null; private static final File TOMBSTONES_PATH = new File("/data/tombstones"); private HashSet<String> mTombstones = null; + + float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT]; + MonkeyEventSource mEventSource; /** * Monitor operations happening in the system. @@ -440,21 +313,15 @@ public class Monkey { mSeed = 0; mThrottle = 0; - // 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; - mFactors[FACTOR_NAV] = 25.0f; - mFactors[FACTOR_MAJORNAV] = 15.0f; - mFactors[FACTOR_SYSOPS] = 2.0f; - mFactors[FACTOR_APPSWITCH] = 2.0f; - mFactors[FACTOR_ANYTHING] = 16.0f; - // prepare for command-line processing mArgs = args; mNextArg = 0; + + //set a positive value, indicating none of the factors is provided yet + for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { + mFactors[i] = 1.0f; + } + if (!processOptions()) { return -1; } @@ -493,16 +360,40 @@ public class Monkey { return -4; } - if (!adjustEventFactors()) { - return -5; + if (mScriptFileName != null) { + // script mode, ignore other options + mEventSource = new MonkeySourceScript(mScriptFileName); + mEventSource.setVerbose(mVerbose); + } else { + // random source by default + if (mVerbose >= 2) { // check seeding performance + System.out.println("// Seeded: " + mSeed); + } + mEventSource = new MonkeySourceRandom(mSeed, mMainApps); + mEventSource.setVerbose(mVerbose); + //set any of the factors that has been set + for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { + if (mFactors[i] <= 0.0f) { + ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]); + } + } + + //in random mode, we start with a random activity + ((MonkeySourceRandom) mEventSource).generateActivity(); } - // Java's Random doesn't scramble well at all on seeding, so we'll use - // the better random source here. - SecureRandom random = new SecureRandom(); - random.setSeed((mSeed == 0) ? -1 : mSeed); - if (mVerbose >= 2) { // check seeding performance - System.out.println("// Seeded: " + mSeed + " and pulling: " + random.nextFloat()); + //validate source generator + if (!mEventSource.validate()) { + return -5; + } + + if (mScriptFileName != null) { + // in random mode, count is the number of single events + // while in script mode, count is the number of repetition + // for a sequence of events, so we need do multiply the length of + // that sequence + mCount = mCount * ((MonkeySourceScript) mEventSource) + .getOneRoundEventCount(); } // If we're profiling, do it immediately before/after the main monkey loop @@ -510,7 +401,7 @@ public class Monkey { signalPersistentProcesses(); } - int crashedAtCycle = runMonkeyCycles(random); + int crashedAtCycle = runMonkeyCycles(); synchronized (this) { if (mRequestAnrTraces) { @@ -536,7 +427,7 @@ public class Monkey { // just in case this was latent (after mCount cycles), make sure // we report it if (crashedAtCycle >= mCount) { - crashedAtCycle = mCount -1; + crashedAtCycle = mCount - 1; } } @@ -547,10 +438,12 @@ public class Monkey { System.out.print(" pointers="); System.out.print(mDroppedPointerEvents); System.out.print(" trackballs="); - System.out.println(mDroppedTrackballEvents); + System.out.print(mDroppedTrackballEvents); + System.out.print(" flips="); + System.out.println(mDroppedFlipEvents); } - if (crashedAtCycle < mCount) { + if (crashedAtCycle < mCount - 1) { System.err.println("** System appears to have crashed at event " + crashedAtCycle + " of " + mCount + " using seed " + mSeed); return crashedAtCycle; @@ -576,7 +469,7 @@ public class Monkey { try { String opt; - while ((opt=nextOption()) != null) { + while ((opt = nextOption()) != null) { if (opt.equals("-s")) { mSeed = nextOptionLong("Seed"); } else if (opt.equals("-p")) { @@ -598,27 +491,37 @@ public class Monkey { } else if (opt.equals("--hprof")) { mGenerateHprof = true; } else if (opt.equals("--pct-touch")) { - mFactors[FACTOR_TOUCH] = -nextOptionLong("touch events percentage"); + mFactors[MonkeySourceRandom.FACTOR_TOUCH] = + -nextOptionLong("touch events percentage"); } else if (opt.equals("--pct-motion")) { - mFactors[FACTOR_MOTION] = -nextOptionLong("motion events percentage"); + mFactors[MonkeySourceRandom.FACTOR_MOTION] = + -nextOptionLong("motion events percentage"); } else if (opt.equals("--pct-trackball")) { - mFactors[FACTOR_TRACKBALL] = -nextOptionLong("trackball events percentage"); - } else if (opt.equals("--pct-syskeys")) { - mFactors[FACTOR_SYSOPS] = -nextOptionLong("system key events percentage"); + mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] = + -nextOptionLong("trackball events percentage"); } else if (opt.equals("--pct-nav")) { - mFactors[FACTOR_NAV] = -nextOptionLong("nav events percentage"); + mFactors[MonkeySourceRandom.FACTOR_NAV] = + -nextOptionLong("nav events percentage"); } else if (opt.equals("--pct-majornav")) { - mFactors[FACTOR_MAJORNAV] = -nextOptionLong("major nav events percentage"); + mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] = + -nextOptionLong("major nav events percentage"); } else if (opt.equals("--pct-appswitch")) { - mFactors[FACTOR_APPSWITCH] = -nextOptionLong("app switch events percentage"); + mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] = + -nextOptionLong("app switch events percentage"); + } else if (opt.equals("--pct-flip")) { + mFactors[MonkeySourceRandom.FACTOR_FLIP] = + -nextOptionLong("keyboard flip percentage"); } else if (opt.equals("--pct-anyevent")) { - mFactors[FACTOR_ANYTHING] = -nextOptionLong("any events percentage"); + mFactors[MonkeySourceRandom.FACTOR_ANYTHING] = + -nextOptionLong("any events percentage"); } else if (opt.equals("--throttle")) { - mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); + mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); } else if (opt.equals("--wait-dbg")) { // do nothing - it's caught at the very start of run() } else if (opt.equals("--dbg-no-events")) { mSendNoEvents = true; + } else if (opt.equals("-f")) { + mScriptFileName = nextOptionData(); } else if (opt.equals("-h")) { showUsage(); return false; @@ -648,10 +551,10 @@ public class Monkey { showUsage(); return false; } - + return true; } - + /** * Check for any internal configuration (primarily build-time) errors. * @@ -659,13 +562,13 @@ public class Monkey { */ private boolean checkInternalConfiguration() { // Check KEYCODE name array, make sure it's up to date. - + String lastKeyName = null; try { - lastKeyName = KEY_NAMES[KeyEvent.MAX_KEYCODE+1]; + lastKeyName = MonkeySourceRandom.getLastKeyName(); } catch (RuntimeException e) { } - if (! "TAG_LAST_KEYCODE".equals(lastKeyName)) { + if (!"TAG_LAST_KEYCODE".equals(lastKeyName)) { System.err.println("** Error: Key names array malformed (internal error)."); return false; } @@ -685,8 +588,7 @@ public class Monkey { return false; } - mWm = IWindowManager.Stub.asInterface( - ServiceManager.getService("window")); + mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); if (mWm == null) { System.err.println("** Error: Unable to connect to window manager; is the system running?"); return false; @@ -697,7 +599,7 @@ public class Monkey { System.err.println("** Error: Unable to connect to package manager; is the system running?"); return false; } - + try { mAm.setActivityWatcher(new ActivityWatcher()); } catch (RemoteException e) { @@ -707,7 +609,7 @@ public class Monkey { return true; } - + /** * Using the restrictions provided (categories & packages), generate a list of activities * that we can actually switch to. @@ -717,7 +619,7 @@ public class Monkey { private boolean getMainApps() { try { final int N = mMainCategories.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i< N; i++) { Intent intent = new Intent(Intent.ACTION_MAIN); String category = mMainCategories.get(i); if (category.length() > 0) { @@ -732,7 +634,7 @@ public class Monkey { System.out.println("// Selecting main activities from category " + category); } final int NA = mainApps.size(); - for (int a=0; a<NA; a++) { + for (int a = 0; a < NA; a++) { ResolveInfo r = mainApps.get(a); if (mValidPackages.size() == 0 || mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) { @@ -769,87 +671,20 @@ public class Monkey { return true; } - - /** - * 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%"); - showUsage(); - 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%"); - showUsage(); - 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] + "%"); - } - } - - // 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; - } - + /** * Run mCount cycles and see if we hit any crashers. * - * TODO: Meta state on keys + * TODO: Meta state on keys * - * @param random The random source to use - * @return Returns the last cycle which executed. If the value == mCount, no errors detected. + * @return Returns the last cycle which executed. If the value == mCount, no errors detected. */ - private int runMonkeyCycles(Random random) { + private int runMonkeyCycles() { int i = 0; int lastKey = 0; boolean systemCrashed = false; - if (!startRandomActivity(random)) { - systemCrashed = true; - } - while (!systemCrashed && i < mCount) { synchronized (this) { if (mRequestAnrTraces) { @@ -873,14 +708,14 @@ public class Monkey { return i; } } - + try { - Thread.sleep(mThrottle); + Thread.sleep(mThrottle); } catch (InterruptedException e1) { - System.out.println("** Monkey interrupted in sleep."); - return i; + System.out.println("** Monkey interrupted in sleep."); + return i; } - + // In this debugging mode, we never send any events. This is primarily // here so you can manually test the package or category limits, while manually // exercising the system. @@ -889,221 +724,35 @@ public class Monkey { continue; } - if ((mVerbose > 0) && (i%100) == 0 && i != 0 && lastKey == 0) { + if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) { System.out.println(" // Sending event #" + i); } - // if the last event was a keydown, then this event is a key-up - if (lastKey != 0) { - if (mVerbose > 1) { - try { - System.out.println(":SendKey (ACTION_UP):" + lastKey + " // " + KEY_NAMES[lastKey]); - } catch (ArrayIndexOutOfBoundsException e) { - System.out.println(":SendKey (ACTION_UP): " + lastKey + " // Unknown key event"); - } - } - try { - if (! mWm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, lastKey), false)) { + MonkeyEvent ev = mEventSource.getNextEvent(); + if (ev != null) { + i++; + int injectCode = ev.injectEvent(mWm, mAm, mVerbose); + if (injectCode == MonkeyEvent.INJECT_FAIL) { + if (ev instanceof MonkeyKeyEvent) { mDroppedKeyEvents++; + } else if (ev instanceof MonkeyMotionEvent) { + mDroppedPointerEvents++; + } else if (ev instanceof MonkeyFlipEvent) { + mDroppedFlipEvents++; } - } catch (RemoteException ex) { + } else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) { systemCrashed = true; - break; + } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) { + systemCrashed = !mIgnoreSecurityExceptions; } - lastKey = 0; - i++; - continue; - } - - // otherwise begin a new event cycle - float cls = random.nextFloat(); - - boolean touchEvent = cls < mFactors[FACTOR_TOUCH]; - boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]); - if (touchEvent || motionEvent) { - try { - generateMotionEvent(random, motionEvent); - } catch (RemoteException ex) { - systemCrashed = true; - break; - } - i++; - continue; - } - - if (cls < mFactors[FACTOR_TRACKBALL]) { - try { - generateTrackballEvent(random); - } catch (RemoteException ex) { - systemCrashed = true; - break; - } - i++; - continue; - } - - // The remaining event categories are injected as key events - if (cls < mFactors[FACTOR_NAV]) { - lastKey = NAV_KEYS[random.nextInt(NAV_KEYS.length)]; - } else if (cls < mFactors[FACTOR_MAJORNAV]) { - lastKey = MAJOR_NAV_KEYS[random.nextInt(MAJOR_NAV_KEYS.length)]; - } else if (cls < mFactors[FACTOR_SYSOPS]) { - lastKey = SYS_KEYS[random.nextInt(SYS_KEYS.length)]; - } else if (cls < mFactors[FACTOR_APPSWITCH]) { - if (!startRandomActivity(random)) { - systemCrashed = true; - break; - } - i++; - continue; - } else { - lastKey = 1 + random.nextInt(KeyEvent.MAX_KEYCODE - 1); - } - if (mVerbose > 0) { - try { - System.out.println(":SendKey: " + lastKey + " // " + KEY_NAMES[lastKey]); - } catch (ArrayIndexOutOfBoundsException e) { - System.out.println(":SendKey: " + lastKey + " // Unknown key event"); - } - } - try { - if (! mWm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, lastKey), false)) { - mDroppedKeyEvents++; - } - } catch (RemoteException ex) { - systemCrashed = true; - break; } } - + // If we got this far, we succeeded! return mCount; } /** - * Generates a random motion event. This method counts a down, move, and up as one event. - * - * 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 motionEvent If false, touch/release. If true, touch/move/release. - * - * @throws RemoteException - */ - private void generateMotionEvent(Random random, boolean motionEvent) throws RemoteException { - Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); - - float x = Math.abs(random.nextInt() % display.getWidth()); - float y = Math.abs(random.nextInt() % display.getHeight()); - long downAt = SystemClock.uptimeMillis(); - boolean drop = false; - drop = sendMotionEvent(MotionEvent.ACTION_DOWN, x, y, downAt, "Pointer ACTION_DOWN", false, - true); - - // sometimes we'll move during the touch - if (motionEvent) { - int count = random.nextInt(10); - for (int i = 0 ; i < count ; i++) { - // generate some slop in the up event - x = (x + (random.nextInt() % 10)) % display.getWidth(); - y = (y + (random.nextInt() % 10)) % display.getHeight(); - drop |= sendMotionEvent(MotionEvent.ACTION_MOVE, x, y, downAt, - "Pointer ACTION_MOVE", true, true); - } - } - - // TODO generate some slop in the up event - drop |= sendMotionEvent(MotionEvent.ACTION_UP, x, y, downAt, "Pointer ACTION_UP", false, - true); - - if (drop) { - mDroppedPointerEvents++; - } - } - - /** - * Sends a single motion event, either as a pointer or a trackball. - * - * @param action Must be one of the ACTION values defined in {@link MotionEvent} - * @param x The position, or movement, in the X axis - * @param y The position, or movement, in the Y axis - * @param downAt The time of the first DOWN must be sent here, and the same value must - * be sent for all subsequent events that are related (through the eventual UP event), or - * -1 to simply send the current time as the downTime. - * @param note This will be displayed when in verbose mode - * @param intermediateNote If true, this is an intermediate step (more verbose logging, only) - * @param isPointer Use true to send a pointer event, and false to send a trackball event - * - * @return Returns false if event was dispatched, true if it was dropped for any reason - * - * @throws RemoteException - */ - private boolean sendMotionEvent(int action, float x, float y, long downAt, final String note, - boolean intermediateNote, boolean isPointer) throws RemoteException { - if ((mVerbose > 0 && !intermediateNote) || mVerbose > 1) { - System.out.println(":Sending " + note + " x=" + x + " y=" + y); - } - long eventTime = SystemClock.uptimeMillis(); - if (downAt == -1) { - downAt = eventTime; - } - final MotionEvent evt = MotionEvent.obtain(downAt, eventTime, action, x, y, 0); - if (isPointer) { - return ! mWm.injectPointerEvent(evt, false); - } else { - return ! mWm.injectTrackballEvent(evt, false); - } - } - - /** - * 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 - * - * @throws RemoteException - */ - private void generateTrackballEvent(Random random) throws RemoteException { - Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); - - boolean drop = false; - int count = random.nextInt(10); - for (int i = 0; i < 10; ++i) { - // generate a small random step - int dX = random.nextInt(10) - 5; - int dY = random.nextInt(10) - 5; - drop |= sendMotionEvent(MotionEvent.ACTION_MOVE, dX, dY, -1, "Trackball ACTION_MOVE", - (i > 0), false); - } - - // 10% of trackball moves end with a click - if (0 == random.nextInt(10)) { - long downAt = SystemClock.uptimeMillis(); - drop |= sendMotionEvent(MotionEvent.ACTION_DOWN, 0, 0, downAt, "Trackball ACTION_DOWN", - true, false); - drop |= sendMotionEvent(MotionEvent.ACTION_UP, 0, 0, downAt, "Trackball ACTION_UP", - false, false); - } - - if (drop) { - mDroppedTrackballEvents++; - } - } - - /** * Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports * in data/misc, so use with care. */ @@ -1121,40 +770,6 @@ public class Monkey { } /** - * Have the activity manager start a new activity. - * - * @param random Random number source - * - * @return Returns true on success, false if there was an error calling - * the activity manager. - */ - private boolean startRandomActivity(Random random) { - int numApps = mMainApps.size(); - int which = random.nextInt(numApps); - ComponentName app = mMainApps.get(which); - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(app); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - if (mVerbose > 0) { - System.out.println(":Switch: " + intent.toURI()); - } - try { - mAm.startActivity(null, intent, null, null, 0, null, null, 0, false, false); - } catch (RemoteException e) { - System.err.println("** Failed talking with activity manager!"); - return false; - } catch (SecurityException e) { - if (mVerbose > 0) { - System.out.println("** Permissions error starting activity " + intent.toURI()); - } - return mIgnoreSecurityExceptions; // true = "launched ok" (pretend) - } - return true; - } - - /** * Watch for appearance of new tombstone files, which indicate native crashes. * * @return Returns true if new files have appeared in the list @@ -1185,7 +800,7 @@ public class Monkey { /** * Return the next command line option. This has a number of special cases which * closely, but not exactly, follow the POSIX command line options patterns: - * + * * -- means to stop processing additional options * -z means option z * -z ARGS means option z with (non-optional) arguments ARGS @@ -1274,17 +889,18 @@ public class Monkey { * Print how to use this command. */ private void showUsage() { - System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]"); - System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]"); - System.err.println(" [--ignore-crashes] [--ignore-timeouts]"); - System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]"); - System.err.println(" [--kill-process-after-error] [--hprof]"); - System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]"); - System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]"); - System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]"); - System.err.println(" [--pct-appswitch PERCENT] [--pct-anyevent PERCENT]"); - System.err.println(" [--wait-dbg] [--dbg-no-events]"); - System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]"); - System.err.println(" COUNT"); - } + System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]"); + System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]"); + System.err.println(" [--ignore-crashes] [--ignore-timeouts]"); + System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]"); + System.err.println(" [--kill-process-after-error] [--hprof]"); + System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]"); + System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]"); + System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]"); + System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]"); + System.err.println(" [--pct-anyevent PERCENT]"); + System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]"); + System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]"); + System.err.println(" COUNT"); + } } diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java new file mode 100644 index 000000000..68e6e6d7f --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java @@ -0,0 +1,68 @@ +/* + * 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.app.IActivityManager; +import android.content.ComponentName; +import android.content.Intent; +import android.os.RemoteException; +import android.view.IWindowManager; + +/** + * monkey activity event + */ +public class MonkeyActivityEvent extends MonkeyEvent { + private ComponentName mApp; + + public MonkeyActivityEvent(ComponentName app) { + super(EVENT_TYPE_ACTIVITY); + mApp = app; + } + + /** + * @return Intent for the new activity + */ + private Intent getEvent() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(mApp); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + + @Override + public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { + Intent intent = getEvent(); + if (verbose > 0) { + System.out.println(":Switch: " + intent.toURI()); + } + try { + iam.startActivity(null, intent, null, null, 0, null, null, 0, + false, false); + } catch (RemoteException e) { + System.err.println("** Failed talking with activity manager!"); + return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION; + } catch (SecurityException e) { + if (verbose > 0) { + System.out.println("** Permissions error starting activity " + + intent.toURI()); + } + return MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION; + } + return MonkeyEvent.INJECT_SUCCESS; + } +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java new file mode 100644 index 000000000..ff99f5f06 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java @@ -0,0 +1,61 @@ +/* + * 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.app.IActivityManager; +import android.view.IWindowManager; + +/** + * abstract class for monkey event + */ +public abstract class MonkeyEvent { + protected int eventType; + public static final int EVENT_TYPE_KEY = 0; + public static final int EVENT_TYPE_POINTER = 1; + public static final int EVENT_TYPE_TRACKBALL = 2; + public static final int EVENT_TYPE_ACTIVITY = 3; + public static final int EVENT_TYPE_FLIP = 4; // Keyboard flip + + public static final int INJECT_SUCCESS = 1; + public static final int INJECT_FAIL = 0; + + // error code for remote exception during injection + public static final int INJECT_ERROR_REMOTE_EXCEPTION = -1; + // error code for security exception during injection + public static final int INJECT_ERROR_SECURITY_EXCEPTION = -2; + + public MonkeyEvent(int type) { + eventType = type; + } + + /** + * @return event type + */ + public int getEventType() { + return eventType; + } + + /** + * a method for injecting event + * @param iwm wires to current window manager + * @param iam wires to current activity manager + * @param verbose a log switch + * @return INJECT_SUCCESS if it goes through, and INJECT_FAIL if it fails + * in the case of exceptions, return its corresponding error code + */ + public abstract int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose); +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java new file mode 100644 index 000000000..a236554a5 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * event source interface + */ +public interface MonkeyEventSource { + /** + * + * @return the next monkey event from the source + */ + public MonkeyEvent getNextEvent(); + + /** + * set verbose to allow different level of log + * @param verbose output mode? 1= verbose, 2=very verbose + */ + public void setVerbose(int verbose); + + /** + * check whether precondition is satisfied + * @return false if something fails, e.g. factor failure in random source + * or file can not open from script source etc + */ + public boolean validate(); +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java new file mode 100644 index 000000000..08fbedbc3 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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 java.io.FileOutputStream; +import java.io.IOException; + +import android.app.IActivityManager; +import android.view.IWindowManager; +/** + * monkey keyboard flip event + */ +public class MonkeyFlipEvent extends MonkeyEvent { + + // Raw keyboard flip event data + // Works on emulator and dream + + private static final byte[] FLIP_0 = { + 0x7f, 0x06, + 0x00, 0x00, + (byte) 0xe0, 0x39, + 0x01, 0x00, + 0x05, 0x00, + 0x00, 0x00, + 0x01, 0x00, + 0x00, 0x00 }; + + private static final byte[] FLIP_1 = { + (byte) 0x85, 0x06, + 0x00, 0x00, + (byte) 0x9f, (byte) 0xa5, + 0x0c, 0x00, + 0x05, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00 }; + + private final boolean mKeyboardOpen; + + public MonkeyFlipEvent(boolean keyboardOpen) { + super(EVENT_TYPE_FLIP); + mKeyboardOpen = keyboardOpen; + } + + @Override + public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { + if (verbose > 0) { + System.out.println(":Sending Flip keyboardOpen=" + mKeyboardOpen); + } + + // inject flip event + try { + FileOutputStream f = new FileOutputStream("/dev/input/event0"); + f.write(mKeyboardOpen ? FLIP_0 : FLIP_1); + f.close(); + return MonkeyEvent.INJECT_SUCCESS; + } catch (IOException e) { + System.out.println("Got IOException performing flip" + e); + return MonkeyEvent.INJECT_FAIL; + } + } +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java new file mode 100644 index 000000000..c1e0ffce7 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java @@ -0,0 +1,125 @@ +/* + * 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.app.IActivityManager; +import android.os.RemoteException; +import android.view.IWindowManager; +import android.view.KeyEvent; +/** + * monkey key event + */ +public class MonkeyKeyEvent extends MonkeyEvent { + private long mDownTime = -1; + private int mMetaState = -1; + private int mAction = -1; + private int mKeyCode = -1; + private int mScancode = -1; + private int mRepeatCount = -1; + private int mDeviceId = -1; + private long mEventTime = -1; + + public MonkeyKeyEvent(int action, int keycode) { + super(EVENT_TYPE_KEY); + mAction = action; + mKeyCode = keycode; + } + + public MonkeyKeyEvent(long downTime, long eventTime, int action, + int code, int repeat, int metaState, + int device, int scancode) { + super(EVENT_TYPE_KEY); + + mAction = action; + mKeyCode = code; + mMetaState = metaState; + mScancode = scancode; + mRepeatCount = repeat; + mDeviceId = device; + mDownTime = downTime; + mEventTime = eventTime; + } + + public int getKeyCode() { + return mKeyCode; + } + + public int getAction() { + return mAction; + } + + public long getDownTime() { + return mDownTime; + } + + public long getEventTime() { + return mEventTime; + } + + public void setDownTime(long downTime) { + mDownTime = downTime; + } + + public void setEventTime(long eventTime) { + mEventTime = eventTime; + } + + /** + * @return the key event + */ + private KeyEvent getEvent() { + if (mDeviceId < 0) { + return new KeyEvent(mAction, mKeyCode); + } + + // for scripts + return new KeyEvent(mDownTime, mEventTime, mAction, + mKeyCode, mRepeatCount, mMetaState, mDeviceId, mScancode); + } + + @Override + public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { + if (verbose > 1) { + String note; + if (mAction == KeyEvent.ACTION_UP) { + note = "ACTION_UP"; + } else { + note = "ACTION_DOWN"; + } + + try { + System.out.println(":SendKey (" + note + "): " + + mKeyCode + " // " + + MonkeySourceRandom.getKeyName(mKeyCode)); + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println(":SendKey (ACTION_UP): " + + mKeyCode + " // Unknown key event"); + } + } + + // inject key event + try { + if (!iwm.injectKeyEvent(getEvent(), false)) { + return MonkeyEvent.INJECT_FAIL; + } + } catch (RemoteException ex) { + return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION; + } + + return MonkeyEvent.INJECT_SUCCESS; + } +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java new file mode 100644 index 000000000..2657061be --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java @@ -0,0 +1,156 @@ +/* + * 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.app.IActivityManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.view.IWindowManager; +import android.view.MotionEvent; + + +/** + * monkey motion event + */ +public class MonkeyMotionEvent extends MonkeyEvent { + private long mDownTime = -1; + private long mEventTime = -1; + private int mAction = -1; + private float mX = -1; + private float mY = -1; + private float mPressure = -1; + private float mSize = -1; + private int mMetaState = -1; + private float mXPrecision = -1; + private float mYPrecision = -1; + private int mDeviceId = -1; + private int mEdgeFlags = -1; + + //If true, this is an intermediate step (more verbose logging, only) + private boolean mIntermediateNote; + + public MonkeyMotionEvent(int type, long downAt, int action, + float x, float y, int metaState) { + super(type); + mDownTime = downAt; + mAction = action; + mX = x; + mY = y; + mMetaState = metaState; + } + + public MonkeyMotionEvent(int type, long downTime, long eventTime, int action, + float x, float y, float pressure, float size, int metaState, + float xPrecision, float yPrecision, int deviceId, int edgeFlags) { + super(type); + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mX = x; + mY = y; + mPressure = pressure; + mSize = size; + mMetaState = metaState; + mXPrecision = xPrecision; + mYPrecision = yPrecision; + mDeviceId = deviceId; + mEdgeFlags = edgeFlags; + } + + public void setIntermediateNote(boolean b) { + mIntermediateNote = b; + } + + public boolean getIntermediateNote() { + return mIntermediateNote; + } + + public float getX() { + return mX; + } + + public float getY() { + return mY; + } + + public int getAction() { + return mAction; + } + + public long getDownTime() { + return mDownTime; + } + + public long getEventTime() { + return mEventTime; + } + + public void setDownTime(long downTime) { + mDownTime = downTime; + } + + public void setEventTime(long eventTime) { + mEventTime = eventTime; + } + + /** + * + * @return instance of a motion event + */ + private MotionEvent getEvent() { + if (mDeviceId < 0) { + return MotionEvent.obtain(mDownTime, SystemClock.uptimeMillis(), + mAction, mX, mY, mMetaState); + } + + // for scripts + return MotionEvent.obtain(mDownTime, mEventTime, + mAction, mX, mY, mPressure, mSize, mMetaState, + mXPrecision, mYPrecision, mDeviceId, mEdgeFlags); + } + + @Override + public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { + + String note; + if ((verbose > 0 && !mIntermediateNote) || verbose > 1) { + if (mAction == MotionEvent.ACTION_DOWN) { + note = "DOWN"; + } else if (mAction == MotionEvent.ACTION_UP) { + note = "UP"; + } else { + note = "MOVE"; + } + System.out.println(":Sending Pointer ACTION_" + note + + " x=" + mX + " y=" + mY); + } + try { + int type = this.getEventType(); + MotionEvent me = getEvent(); + + if ((type == MonkeyEvent.EVENT_TYPE_POINTER && + !iwm.injectPointerEvent(me, false)) + || (type == MonkeyEvent.EVENT_TYPE_TRACKBALL && + !iwm.injectTrackballEvent(me, false))) { + return MonkeyEvent.INJECT_FAIL; + } + } catch (RemoteException ex) { + return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION; + } + return MonkeyEvent.INJECT_SUCCESS; + } +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java new file mode 100644 index 000000000..dd9284604 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java @@ -0,0 +1,465 @@ +/* + * 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.os.SystemClock; +import android.view.Display; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerImpl; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.LinkedList; +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, + }; + /** Nice names for all key events. */ + private static final String[] KEY_NAMES = { + "KEYCODE_UNKNOWN", + "KEYCODE_MENU", + "KEYCODE_SOFT_RIGHT", + "KEYCODE_HOME", + "KEYCODE_BACK", + "KEYCODE_CALL", + "KEYCODE_ENDCALL", + "KEYCODE_0", + "KEYCODE_1", + "KEYCODE_2", + "KEYCODE_3", + "KEYCODE_4", + "KEYCODE_5", + "KEYCODE_6", + "KEYCODE_7", + "KEYCODE_8", + "KEYCODE_9", + "KEYCODE_STAR", + "KEYCODE_POUND", + "KEYCODE_DPAD_UP", + "KEYCODE_DPAD_DOWN", + "KEYCODE_DPAD_LEFT", + "KEYCODE_DPAD_RIGHT", + "KEYCODE_DPAD_CENTER", + "KEYCODE_VOLUME_UP", + "KEYCODE_VOLUME_DOWN", + "KEYCODE_POWER", + "KEYCODE_CAMERA", + "KEYCODE_CLEAR", + "KEYCODE_A", + "KEYCODE_B", + "KEYCODE_C", + "KEYCODE_D", + "KEYCODE_E", + "KEYCODE_F", + "KEYCODE_G", + "KEYCODE_H", + "KEYCODE_I", + "KEYCODE_J", + "KEYCODE_K", + "KEYCODE_L", + "KEYCODE_M", + "KEYCODE_N", + "KEYCODE_O", + "KEYCODE_P", + "KEYCODE_Q", + "KEYCODE_R", + "KEYCODE_S", + "KEYCODE_T", + "KEYCODE_U", + "KEYCODE_V", + "KEYCODE_W", + "KEYCODE_X", + "KEYCODE_Y", + "KEYCODE_Z", + "KEYCODE_COMMA", + "KEYCODE_PERIOD", + "KEYCODE_ALT_LEFT", + "KEYCODE_ALT_RIGHT", + "KEYCODE_SHIFT_LEFT", + "KEYCODE_SHIFT_RIGHT", + "KEYCODE_TAB", + "KEYCODE_SPACE", + "KEYCODE_SYM", + "KEYCODE_EXPLORER", + "KEYCODE_ENVELOPE", + "KEYCODE_ENTER", + "KEYCODE_DEL", + "KEYCODE_GRAVE", + "KEYCODE_MINUS", + "KEYCODE_EQUALS", + "KEYCODE_LEFT_BRACKET", + "KEYCODE_RIGHT_BRACKET", + "KEYCODE_BACKSLASH", + "KEYCODE_SEMICOLON", + "KEYCODE_APOSTROPHE", + "KEYCODE_SLASH", + "KEYCODE_AT", + "KEYCODE_NUM", + "KEYCODE_HEADSETHOOK", + "KEYCODE_FOCUS", + "KEYCODE_PLUS", + "KEYCODE_MENU", + "KEYCODE_NOTIFICATION", + "KEYCODE_SEARCH", + "KEYCODE_PLAYPAUSE", + "KEYCODE_STOP", + "KEYCODE_NEXTSONG", + "KEYCODE_PREVIOUSSONG", + "KEYCODE_REWIND", + "KEYCODE_FORWARD", + + "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync + }; + + public static final int FACTOR_TOUCH = 0; + public static final int FACTOR_MOTION = 1; + public static final int FACTOR_TRACKBALL = 2; + public static final int FACTOR_NAV = 3; + public static final int FACTOR_MAJORNAV = 4; + public static final int FACTOR_SYSOPS = 5; + public static final int FACTOR_APPSWITCH = 6; + public static final int FACTOR_FLIP = 7; + public static final int FACTOR_ANYTHING = 8; + public static final int FACTORZ_COUNT = 9; // should be last+1 + + + /** 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 ArrayList<ComponentName> mMainApps; + private int mEventCount = 0; //total number of events generated so far + private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>(); + private Random mRandom; + private int mVerbose = 0; + + private boolean mKeyboardOpen = false; + + /** + * @return the last name in the key list + */ + public static String getLastKeyName() { + return KEY_NAMES[KeyEvent.getMaxKeyCode() + 1]; + } + + public static String getKeyName(int keycode) { + return KEY_NAMES[keycode]; + } + + public MonkeySourceRandom(long seed, ArrayList<ComponentName> MainApps) { + // 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; + 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; + mFactors[FACTOR_ANYTHING] = 15.0f; + + mRandom = new SecureRandom(); + mRandom.setSeed((seed == 0) ? -1 : seed); + mMainApps = MainApps; + } + + /** + * 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] + "%"); + } + } + + // 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; + } + + /** + * 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 motionEvent If false, touch/release. If true, touch/move/release. + * + */ + private void generateMotionEvent(Random random, boolean motionEvent){ + + Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); + + float x = Math.abs(random.nextInt() % display.getWidth()); + float y = Math.abs(random.nextInt() % display.getHeight()); + long downAt = SystemClock.uptimeMillis(); + long eventTime = SystemClock.uptimeMillis(); + if (downAt == -1) { + downAt = eventTime; + } + + MonkeyMotionEvent e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, + downAt, MotionEvent.ACTION_DOWN, x, y, 0); + e.setIntermediateNote(false); + mQ.addLast(e); + + // sometimes we'll move during the touch + if (motionEvent) { + int count = random.nextInt(10); + for (int i = 0; i < count; i++) { + // generate some slop in the up event + x = (x + (random.nextInt() % 10)) % display.getWidth(); + y = (y + (random.nextInt() % 10)) % display.getHeight(); + + e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, + downAt, MotionEvent.ACTION_MOVE, x, y, 0); + e.setIntermediateNote(true); + mQ.addLast(e); + } + } + + // TODO generate some slop in the up event + e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER, + downAt, MotionEvent.ACTION_UP, x, y, 0); + e.setIntermediateNote(false); + mQ.addLast(e); + } + + /** + * 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) { + Display display = WindowManagerImpl.getDefault().getDefaultDisplay(); + + boolean drop = false; + int count = random.nextInt(10); + MonkeyMotionEvent e; + for (int i = 0; i < 10; ++i) { + // generate a small random step + int dX = random.nextInt(10) - 5; + int dY = random.nextInt(10) - 5; + + + e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1, + MotionEvent.ACTION_MOVE, dX, dY, 0); + e.setIntermediateNote(i > 0); + mQ.addLast(e); + } + + // 10% of trackball moves end with a click + if (0 == random.nextInt(10)) { + long downAt = SystemClock.uptimeMillis(); + + + e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt, + MotionEvent.ACTION_DOWN, 0, 0, 0); + e.setIntermediateNote(true); + mQ.addLast(e); + + + e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt, + MotionEvent.ACTION_UP, 0, 0, 0); + e.setIntermediateNote(false); + mQ.addLast(e); + } + } + + /** + * generate a random event based on mFactor + */ + private void generateEvents() { + float cls = mRandom.nextFloat(); + int lastKey = 0; + + boolean touchEvent = cls < mFactors[FACTOR_TOUCH]; + boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]); + if (touchEvent || motionEvent) { + generateMotionEvent(mRandom, motionEvent); + return; + } + + if (cls < mFactors[FACTOR_TRACKBALL]) { + generateTrackballEvent(mRandom); + return; + } + + // The remaining event categories are injected as key events + 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); + } + + MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey); + mQ.addLast(e); + + e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey); + mQ.addLast(e); + } + + public boolean validate() { + //check factors + return 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; + } +} diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java new file mode 100644 index 000000000..aadda9f62 --- /dev/null +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java @@ -0,0 +1,427 @@ +/* + * 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.os.SystemClock; +import android.view.KeyEvent; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedList; +import java.util.StringTokenizer; + +/** + * monkey event queue. It takes a script to produce events + * + * sample script format: + * type= raw events + * count= 10 + * speed= 1.0 + * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314, + * 0.06666667,0,0.0,0.0,65539,0) + * captureDispatchKey(5113146,5113146,0,20,0,0,0,0) + * captureDispatchFlip(true) + * ... + */ +public class MonkeySourceScript implements MonkeyEventSource{ + private int mEventCountInScript = 0; //total number of events in the file + private int mVerbose = 0; + private double mSpeed = 1.0; + private String mScriptFileName; + private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>(); + + private static final String HEADER_TYPE = "type="; + private static final String HEADER_COUNT = "count="; + private static final String HEADER_SPEED = "speed="; + + private long mLastRecordedDownTimeKey = 0; + private long mLastRecordedDownTimeMotion = 0; + private long mLastExportDownTimeKey = 0; + private long mLastExportDownTimeMotion = 0; + private long mLastExportEventTime = -1; + private long mLastRecordedEventTime = -1; + + private static final boolean THIS_DEBUG = false; + // a parameter that compensates the difference of real elapsed time and + // time in theory + private static final long SLEEP_COMPENSATE_DIFF = 16; + + // maximum number of events that we read at one time + private static final int MAX_ONE_TIME_READS = 100; + + // number of additional events added to the script + // add HOME_KEY down and up events to make start UI consistent in each round + private static final int POLICY_ADDITIONAL_EVENT_COUNT = 2; + + // event key word in the capture log + private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; + private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball"; + private static final String EVENT_KEYWORD_KEY = "DispatchKey"; + private static final String EVENT_KEYWORD_FLIP = "DispatchFlip"; + + // a line at the end of the header + private static final String STARTING_DATA_LINE = "start data >>"; + private boolean mFileOpened = false; + FileInputStream mFStream; + DataInputStream mInputStream; + BufferedReader mBufferReader; + + public MonkeySourceScript(String filename) { + mScriptFileName = filename; + } + + /** + * + * @return the number of total events that will be generated in a round + */ + public int getOneRoundEventCount() { + //plus one home key down and up event + return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT; + } + + private void resetValue() { + mLastRecordedDownTimeKey = 0; + mLastRecordedDownTimeMotion = 0; + mLastExportDownTimeKey = 0; + mLastExportDownTimeMotion = 0; + mLastRecordedEventTime = -1; + mLastExportEventTime = -1; + } + + private boolean readScriptHeader() { + mEventCountInScript = -1; + mFileOpened = false; + try { + if (THIS_DEBUG) { + System.out.println("reading script header"); + } + + mFStream = new FileInputStream(mScriptFileName); + mInputStream = new DataInputStream(mFStream); + mBufferReader = new BufferedReader( + new InputStreamReader(mInputStream)); + String sLine; + while ((sLine = mBufferReader.readLine()) != null) { + sLine = sLine.trim(); + if (sLine.indexOf(HEADER_TYPE) >= 0) { + // at this point, we only have one type of script + } else if (sLine.indexOf(HEADER_COUNT) >= 0) { + try { + mEventCountInScript = Integer.parseInt(sLine.substring( + HEADER_COUNT.length() + 1).trim()); + } catch (NumberFormatException e) { + System.err.println(e); + } + } else if (sLine.indexOf(HEADER_SPEED) >= 0) { + try { + mSpeed = Double.parseDouble(sLine.substring( + HEADER_SPEED.length() + 1).trim()); + + } catch (NumberFormatException e) { + System.err.println(e); + } + } else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) { + // header ends until we read the start data mark + mFileOpened = true; + if (THIS_DEBUG) { + System.out.println("read script header success"); + } + return true; + } + } + } catch (FileNotFoundException e) { + System.err.println(e); + } catch (IOException e) { + System.err.println(e); + } + + if (THIS_DEBUG) { + System.out.println("Error in reading script header"); + } + return false; + } + + private void processLine(String s) { + int index1 = s.indexOf('('); + int index2 = s.indexOf(')'); + + if (index1 < 0 || index2 < 0) { + return; + } + + StringTokenizer st = new StringTokenizer( + s.substring(index1 + 1, index2), ","); + + if (s.indexOf(EVENT_KEYWORD_KEY) >= 0) { + // key events + try { + long downTime = Long.parseLong(st.nextToken()); + long eventTime = Long.parseLong(st.nextToken()); + int action = Integer.parseInt(st.nextToken()); + int code = Integer.parseInt(st.nextToken()); + int repeat = Integer.parseInt(st.nextToken()); + int metaState = Integer.parseInt(st.nextToken()); + int device = Integer.parseInt(st.nextToken()); + int scancode = Integer.parseInt(st.nextToken()); + + MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime, + action, code, repeat, metaState, device, scancode); + mQ.addLast(e); + + } catch (NumberFormatException e) { + // something wrong with this line in the script + } + } else if (s.indexOf(EVENT_KEYWORD_POINTER) >= 0 || + s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0) { + // trackball/pointer event + try { + long downTime = Long.parseLong(st.nextToken()); + long eventTime = Long.parseLong(st.nextToken()); + int action = Integer.parseInt(st.nextToken()); + float x = Float.parseFloat(st.nextToken()); + float y = Float.parseFloat(st.nextToken()); + float pressure = Float.parseFloat(st.nextToken()); + float size = Float.parseFloat(st.nextToken()); + int metaState = Integer.parseInt(st.nextToken()); + float xPrecision = Float.parseFloat(st.nextToken()); + float yPrecision = Float.parseFloat(st.nextToken()); + int device = Integer.parseInt(st.nextToken()); + int edgeFlags = Integer.parseInt(st.nextToken()); + + int type = MonkeyEvent.EVENT_TYPE_TRACKBALL; + if (s.indexOf("Pointer") > 0) { + type = MonkeyEvent.EVENT_TYPE_POINTER; + } + MonkeyMotionEvent e = new MonkeyMotionEvent(type, downTime, eventTime, + action, x, y, pressure, size, metaState, xPrecision, yPrecision, + device, edgeFlags); + mQ.addLast(e); + } catch (NumberFormatException e) { + // we ignore this event + } + } else if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0) { + boolean keyboardOpen = Boolean.parseBoolean(st.nextToken()); + MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen); + mQ.addLast(e); + } + } + + private void closeFile() { + mFileOpened = false; + if (THIS_DEBUG) { + System.out.println("closing script file"); + } + + try { + mFStream.close(); + mInputStream.close(); + } catch (IOException e) { + System.out.println(e); + } + } + + /** + * add home key press/release event to the queue + */ + private void addHomeKeyEvent() { + MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_HOME); + mQ.addLast(e); + e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME); + mQ.addLast(e); + } + + /** + * read next batch of events from the provided script file + * @return true if success + */ + private boolean readNextBatch() { + String sLine = null; + int readCount = 0; + + if (THIS_DEBUG) { + System.out.println("readNextBatch(): reading next batch of events"); + } + + if (!mFileOpened) { + if (!readScriptHeader()) { + closeFile(); + return false; + } + resetValue(); + + /* + * In order to allow the Monkey to replay captured events multiple times + * we need to define a default start UI, which is the home screen + * Otherwise, it won't be accurate since the captured events + * could end anywhere + */ + addHomeKeyEvent(); + } + + try { + while (readCount++ < MAX_ONE_TIME_READS && + (sLine = mBufferReader.readLine()) != null) { + sLine = sLine.trim(); + processLine(sLine); + } + } catch (IOException e) { + System.err.println(e); + return false; + } + + if (sLine == null) { + // to the end of the file + if (THIS_DEBUG) { + System.out.println("readNextBatch(): to the end of file"); + } + closeFile(); + } + return true; + } + + /** + * sleep for a period of given time, introducing latency among events + * @param time to sleep in millisecond + */ + private void needSleep(long time) { + if (time < 1) { + return; + } + try { + Thread.sleep(time); + } catch (InterruptedException e) { + } + } + + /** + * check whether we can successfully read the header of the script file + */ + public boolean validate() { + boolean b = readNextBatch(); + if (mVerbose > 0) { + System.out.println("Replaying " + mEventCountInScript + + " events with speed " + mSpeed); + } + return b; + } + + public void setVerbose(int verbose) { + mVerbose = verbose; + } + + /** + * adjust key downtime and eventtime according to both + * recorded values and current system time + * @param e KeyEvent + */ + private void adjustKeyEventTime(MonkeyKeyEvent e) { + if (e.getEventTime() < 0) { + return; + } + long thisDownTime = 0; + long thisEventTime = 0; + long expectedDelay = 0; + + if (mLastRecordedEventTime <= 0) { + // first time event + thisDownTime = SystemClock.uptimeMillis(); + thisEventTime = thisDownTime; + } else { + if (e.getDownTime() != mLastRecordedDownTimeKey) { + thisDownTime = e.getDownTime(); + } else { + thisDownTime = mLastExportDownTimeKey; + } + expectedDelay = (long) ((e.getEventTime() - + mLastRecordedEventTime) * mSpeed); + thisEventTime = mLastExportEventTime + expectedDelay; + // add sleep to simulate everything in recording + needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); + } + mLastRecordedDownTimeKey = e.getDownTime(); + mLastRecordedEventTime = e.getEventTime(); + e.setDownTime(thisDownTime); + e.setEventTime(thisEventTime); + mLastExportDownTimeKey = thisDownTime; + mLastExportEventTime = thisEventTime; + } + + /** + * adjust motion downtime and eventtime according to both + * recorded values and current system time + * @param e KeyEvent + */ + private void adjustMotionEventTime(MonkeyMotionEvent e) { + if (e.getEventTime() < 0) { + return; + } + long thisDownTime = 0; + long thisEventTime = 0; + long expectedDelay = 0; + + if (mLastRecordedEventTime <= 0) { + // first time event + thisDownTime = SystemClock.uptimeMillis(); + thisEventTime = thisDownTime; + } else { + if (e.getDownTime() != mLastRecordedDownTimeMotion) { + thisDownTime = e.getDownTime(); + } else { + thisDownTime = mLastExportDownTimeMotion; + } + expectedDelay = (long) ((e.getEventTime() - + mLastRecordedEventTime) * mSpeed); + thisEventTime = mLastExportEventTime + expectedDelay; + // add sleep to simulate everything in recording + needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF); + } + + mLastRecordedDownTimeMotion = e.getDownTime(); + mLastRecordedEventTime = e.getEventTime(); + e.setDownTime(thisDownTime); + e.setEventTime(thisEventTime); + mLastExportDownTimeMotion = thisDownTime; + mLastExportEventTime = thisEventTime; + } + + /** + * if the queue is empty, we generate events first + * @return the first event in the queue, if null, indicating the system crashes + */ + public MonkeyEvent getNextEvent() { + long recordedEventTime = -1; + + if (mQ.isEmpty()) { + readNextBatch(); + } + MonkeyEvent e = mQ.getFirst(); + mQ.removeFirst(); + + if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) { + adjustKeyEventTime((MonkeyKeyEvent) e); + } else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER || + e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) { + adjustMotionEventTime((MonkeyMotionEvent) e); + } + return e; + } +} |
