summaryrefslogtreecommitdiffstats
path: root/cmds/monkey
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:04:04 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2008-12-17 18:04:04 -0800
commite943f2fd8e7623ba3ea12bb65294d30446db4174 (patch)
treeeda2f24648d61fb33f3fa754d9fb4b5693517e0c /cmds/monkey
parent5c11852110eeb03dc5a69111354b383f98d15336 (diff)
downloadandroid_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.tar.gz
android_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.tar.bz2
android_development-e943f2fd8e7623ba3ea12bb65294d30446db4174.zip
Code drop from //branches/cupcake/...@124589
Diffstat (limited to 'cmds/monkey')
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/Monkey.java626
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java68
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java61
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java41
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java76
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java125
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java156
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java465
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java427
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;
+ }
+}