auto import from //depot/cupcake/@135843

This commit is contained in:
The Android Open Source Project
2009-03-03 18:28:16 -08:00
parent d2f2b1d7b7
commit d4aee0c0ca
2356 changed files with 0 additions and 295812 deletions

View File

@@ -1,906 +0,0 @@
/**
** Copyright 2007, 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.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IActivityWatcher;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.os.Debug;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.data.CrashData;
import android.view.IWindowManager;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
/**
* Application that injects random key events and other actions into the system.
*/
public class Monkey {
/**
* Monkey Debugging/Dev Support
*
* All values should be zero when checking in.
*/
private final static int DEBUG_ALLOW_ANY_STARTS = 0;
private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;
private IActivityManager mAm;
private IWindowManager mWm;
private IPackageManager mPm;
/** Command line arguments */
private String[] mArgs;
/** Current argument being parsed */
private int mNextArg;
/** Data of current argument */
private String mCurArgData;
/** Running in verbose output mode? 1= verbose, 2=very verbose */
private int mVerbose;
/** Ignore any application crashes while running? */
private boolean mIgnoreCrashes;
/** Ignore any not responding timeouts while running? */
private boolean mIgnoreTimeouts;
/** Ignore security exceptions when launching activities */
/** (The activity launch still fails, but we keep pluggin' away) */
private boolean mIgnoreSecurityExceptions;
/** Monitor /data/tombstones and stop the monkey if new files appear. */
private boolean mMonitorNativeCrashes;
/** Send no events. Use with long throttle-time to watch user operations */
private boolean mSendNoEvents;
/** This is set when we would like to abort the running of the monkey. */
private boolean mAbort;
/** This is set by the ActivityWatcher thread to request collection of ANR trace files */
private boolean mRequestAnrTraces = false;
/** This is set by the ActivityWatcher thread to request a "dumpsys meminfo" */
private boolean mRequestDumpsysMemInfo = false;
/** Kill the process after a timeout or crash. */
private boolean mKillProcessAfterError;
/** Generate hprof reports before/after monkey runs */
private boolean mGenerateHprof;
/** Packages we are allowed to run, or empty if no restriction. */
private HashSet<String> mValidPackages = new HashSet<String>();
/** Categories we are allowed to launch **/
ArrayList<String> mMainCategories = new ArrayList<String>();
/** Applications we can switch to. */
private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
/** The delay between event inputs **/
long mThrottle = 0;
/** The number of iterations **/
int mCount = 1000;
/** The random number seed **/
long mSeed = 0;
/** Dropped-event statistics **/
long mDroppedKeyEvents = 0;
long mDroppedPointerEvents = 0;
long mDroppedTrackballEvents = 0;
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.
*/
private class ActivityWatcher extends IActivityWatcher.Stub {
public boolean activityStarting(Intent intent, String pkg) {
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
+ " start of " + intent + " in package " + pkg);
}
return allow;
}
public boolean activityResuming(String pkg) {
System.out.println(" // activityResuming(" + pkg + ")");
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0);
if (!allow) {
if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
+ " resume of package " + pkg);
}
}
return allow;
}
private boolean checkEnteringPackage(String pkg) {
if (pkg == null) {
return true;
}
// preflight the hash lookup to avoid the cost of hash key generation
if (mValidPackages.size() == 0) {
return true;
} else {
return mValidPackages.contains(pkg);
}
}
public boolean appCrashed(String processName, int pid, String shortMsg,
String longMsg, byte[] crashData) {
System.err.println("// CRASH: " + processName + " (pid " + pid
+ ")");
System.err.println("// Short Msg: " + shortMsg);
System.err.println("// Long Msg: " + longMsg);
if (crashData != null) {
try {
CrashData cd = new CrashData(new DataInputStream(
new ByteArrayInputStream(crashData)));
System.err.println("// Build Label: "
+ cd.getBuildData().getFingerprint());
System.err.println("// Build Changelist: "
+ cd.getBuildData().getIncrementalVersion());
System.err.println("// Build Time: "
+ cd.getBuildData().getTime());
System.err.println("// ID: " + cd.getId());
System.err.println("// Tag: " + cd.getActivity());
System.err.println(cd.getThrowableData().toString(
"// "));
} catch (IOException e) {
System.err.println("// BAD STACK CRAWL");
}
}
if (!mIgnoreCrashes) {
synchronized (Monkey.this) {
mAbort = true;
}
return !mKillProcessAfterError;
}
return false;
}
public int appNotResponding(String processName, int pid,
String processStats) {
System.err.println("// NOT RESPONDING: " + processName
+ " (pid " + pid + ")");
System.err.println(processStats);
reportProcRank();
synchronized (Monkey.this) {
mRequestAnrTraces = true;
mRequestDumpsysMemInfo = true;
}
if (!mIgnoreTimeouts) {
synchronized (Monkey.this) {
mAbort = true;
}
return (mKillProcessAfterError) ? -1 : 1;
}
return 1;
}
}
/**
* Run the procrank tool to insert system status information into the debug report.
*/
private void reportProcRank() {
commandLineReport("procrank", "procrank");
}
/**
* Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous
* report writing complete.
*/
private void reportAnrTraces() {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
commandLineReport("anr traces", "cat /data/anr/traces.txt");
}
/**
* Run "dumpsys meminfo"
*
* NOTE: You cannot perform a dumpsys call from the ActivityWatcher callback, as it will
* deadlock. This should only be called from the main loop of the monkey.
*/
private void reportDumpsysMemInfo() {
commandLineReport("meminfo", "dumpsys meminfo");
}
/**
* Print report from a single command line.
* @param reportName Simple tag that will print before the report and in various annotations.
* @param command Command line to execute.
* TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both streams (might be
* important for some command lines)
*/
private void commandLineReport(String reportName, String command) {
System.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
try {
// Process must be fully qualified here because android.os.Process is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
// pipe everything from process stdout -> System.err
InputStream inStream = p.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream);
BufferedReader inBuffer = new BufferedReader(inReader);
String s;
while ((s = inBuffer.readLine()) != null) {
System.err.println(s);
}
int status = p.waitFor();
System.err.println("// " + reportName + " status was " + status);
} catch (Exception e) {
System.err.println("// Exception from " + reportName + ":");
System.err.println(e.toString());
}
}
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
/**
* Run the command!
*
* @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}
// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// 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;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mVerbose > 0) {
System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
if (mValidPackages.size() > 0) {
Iterator<String> it = mValidPackages.iterator();
while (it.hasNext()) {
System.out.println(":AllowPackage: " + it.next());
}
}
if (mMainCategories.size() != 0) {
Iterator<String> it = mMainCategories.iterator();
while (it.hasNext()) {
System.out.println(":IncludeCategory: " + it.next());
}
}
}
if (!checkInternalConfiguration()) {
return -2;
}
if (!getSystemInterfaces()) {
return -3;
}
if (!getMainApps()) {
return -4;
}
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();
}
//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
if (mGenerateHprof) {
signalPersistentProcesses();
}
int crashedAtCycle = runMonkeyCycles();
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestAnrTraces = false;
}
if (mRequestDumpsysMemInfo) {
reportDumpsysMemInfo();
mRequestDumpsysMemInfo = false;
}
}
if (mGenerateHprof) {
signalPersistentProcesses();
if (mVerbose > 0) {
System.out.println("// Generated profiling reports in /data/misc");
}
}
try {
mAm.setActivityWatcher(null);
} catch (RemoteException e) {
// just in case this was latent (after mCount cycles), make sure
// we report it
if (crashedAtCycle >= mCount) {
crashedAtCycle = mCount - 1;
}
}
// report dropped event stats
if (mVerbose > 0) {
System.out.print(":Dropped: keys=");
System.out.print(mDroppedKeyEvents);
System.out.print(" pointers=");
System.out.print(mDroppedPointerEvents);
System.out.print(" trackballs=");
System.out.print(mDroppedTrackballEvents);
System.out.print(" flips=");
System.out.println(mDroppedFlipEvents);
}
if (crashedAtCycle < mCount - 1) {
System.err.println("** System appears to have crashed at event "
+ crashedAtCycle + " of " + mCount + " using seed " + mSeed);
return crashedAtCycle;
} else {
if (mVerbose > 0) {
System.out.println("// Monkey finished");
}
return 0;
}
}
/**
* Process the command-line options
*
* @return Returns true if options were parsed with no apparent errors.
*/
private boolean processOptions() {
// quick (throwaway) check for unadorned command
if (mArgs.length < 1) {
showUsage();
return false;
}
try {
String opt;
while ((opt = nextOption()) != null) {
if (opt.equals("-s")) {
mSeed = nextOptionLong("Seed");
} else if (opt.equals("-p")) {
mValidPackages.add(nextOptionData());
} else if (opt.equals("-c")) {
mMainCategories.add(nextOptionData());
} else if (opt.equals("-v")) {
mVerbose += 1;
} else if (opt.equals("--ignore-crashes")) {
mIgnoreCrashes = true;
} else if (opt.equals("--ignore-timeouts")) {
mIgnoreTimeouts = true;
} else if (opt.equals("--ignore-security-exceptions")) {
mIgnoreSecurityExceptions = true;
} else if (opt.equals("--monitor-native-crashes")) {
mMonitorNativeCrashes = true;
} else if (opt.equals("--kill-process-after-error")) {
mKillProcessAfterError = true;
} else if (opt.equals("--hprof")) {
mGenerateHprof = true;
} else if (opt.equals("--pct-touch")) {
mFactors[MonkeySourceRandom.FACTOR_TOUCH] =
-nextOptionLong("touch events percentage");
} else if (opt.equals("--pct-motion")) {
mFactors[MonkeySourceRandom.FACTOR_MOTION] =
-nextOptionLong("motion events percentage");
} else if (opt.equals("--pct-trackball")) {
mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] =
-nextOptionLong("trackball events percentage");
} else if (opt.equals("--pct-nav")) {
mFactors[MonkeySourceRandom.FACTOR_NAV] =
-nextOptionLong("nav events percentage");
} else if (opt.equals("--pct-majornav")) {
mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] =
-nextOptionLong("major nav events percentage");
} else if (opt.equals("--pct-appswitch")) {
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[MonkeySourceRandom.FACTOR_ANYTHING] =
-nextOptionLong("any events percentage");
} else if (opt.equals("--throttle")) {
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;
} else {
System.err.println("** Error: Unknown option: " + opt);
showUsage();
return false;
}
}
} catch (RuntimeException ex) {
System.err.println("** Error: " + ex.toString());
showUsage();
return false;
}
String countStr = nextArg();
if (countStr == null) {
System.err.println("** Error: Count not specified");
showUsage();
return false;
}
try {
mCount = Integer.parseInt(countStr);
} catch (NumberFormatException e) {
System.err.println("** Error: Count is not a number");
showUsage();
return false;
}
return true;
}
/**
* Check for any internal configuration (primarily build-time) errors.
*
* @return Returns true if ready to rock.
*/
private boolean checkInternalConfiguration() {
// Check KEYCODE name array, make sure it's up to date.
String lastKeyName = null;
try {
lastKeyName = MonkeySourceRandom.getLastKeyName();
} catch (RuntimeException e) {
}
if (!"TAG_LAST_KEYCODE".equals(lastKeyName)) {
System.err.println("** Error: Key names array malformed (internal error).");
return false;
}
return true;
}
/**
* Attach to the required system interfaces.
*
* @return Returns true if all system interfaces were available.
*/
private boolean getSystemInterfaces() {
mAm = ActivityManagerNative.getDefault();
if (mAm == null) {
System.err.println("** Error: Unable to connect to activity manager; is the system running?");
return false;
}
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;
}
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println("** Error: Unable to connect to package manager; is the system running?");
return false;
}
try {
mAm.setActivityWatcher(new ActivityWatcher());
} catch (RemoteException e) {
System.err.println("** Failed talking with activity manager!");
return false;
}
return true;
}
/**
* Using the restrictions provided (categories & packages), generate a list of activities
* that we can actually switch to.
*
* @return Returns true if it could successfully build a list of target activities
*/
private boolean getMainApps() {
try {
final int N = mMainCategories.size();
for (int i = 0; i< N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i);
if (category.length() > 0) {
intent.addCategory(category);
}
List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0);
if (mainApps == null || mainApps.size() == 0) {
System.err.println("// Warning: no activities found for category " + category);
continue;
}
if (mVerbose >= 2) { // very verbose
System.out.println("// Selecting main activities from category " + category);
}
final int NA = mainApps.size();
for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a);
if (mValidPackages.size() == 0 ||
mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
if (mVerbose >= 2) { // very verbose
System.out.println("// + Using main activity "
+ r.activityInfo.name
+ " (from package "
+ r.activityInfo.applicationInfo.packageName
+ ")");
}
mMainApps.add(new ComponentName(
r.activityInfo.applicationInfo.packageName,
r.activityInfo.name));
} else {
if (mVerbose >= 3) { // very very verbose
System.out.println("// - NOT USING main activity "
+ r.activityInfo.name
+ " (from package "
+ r.activityInfo.applicationInfo.packageName
+ ")");
}
}
}
}
} catch (RemoteException e) {
System.err.println("** Failed talking with package manager!");
return false;
}
if (mMainApps.size() == 0) {
System.out.println("** No activities found to run, monkey aborted.");
return false;
}
return true;
}
/**
* Run mCount cycles and see if we hit any crashers.
*
* TODO: Meta state on keys
*
* @return Returns the last cycle which executed. If the value == mCount, no errors detected.
*/
private int runMonkeyCycles() {
int i = 0;
int lastKey = 0;
boolean systemCrashed = false;
while (!systemCrashed && i < mCount) {
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestAnrTraces = false;
}
if (mRequestDumpsysMemInfo) {
reportDumpsysMemInfo();
mRequestDumpsysMemInfo = false;
}
if (mMonitorNativeCrashes) {
// first time through, when i == 0, just set up the watcher (ignore the error)
if (checkNativeCrashes() && (i > 0)) {
System.out.println("** New native crash detected.");
mAbort = mAbort || mKillProcessAfterError;
}
}
if (mAbort) {
System.out.println("** Monkey aborted due to error.");
System.out.println("Events injected: " + i);
return i;
}
}
try {
Thread.sleep(mThrottle);
} catch (InterruptedException e1) {
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.
if (mSendNoEvents) {
i++;
continue;
}
if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) {
System.out.println(" // Sending event #" + i);
}
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++;
}
} else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
systemCrashed = true;
} else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
systemCrashed = !mIgnoreSecurityExceptions;
}
}
}
// If we got this far, we succeeded!
return mCount;
}
/**
* Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports
* in data/misc, so use with care.
*/
private void signalPersistentProcesses() {
try {
mAm.signalPersistentProcesses(Process.SIGNAL_USR1);
synchronized (this) {
wait(2000);
}
} catch (RemoteException e) {
System.err.println("** Failed talking with activity manager!");
} catch (InterruptedException e) {
}
}
/**
* Watch for appearance of new tombstone files, which indicate native crashes.
*
* @return Returns true if new files have appeared in the list
*/
private boolean checkNativeCrashes() {
String[] tombstones = TOMBSTONES_PATH.list();
// shortcut path for usually empty directory, so we don't waste even more objects
if ((tombstones == null) || (tombstones.length == 0)) {
mTombstones = null;
return false;
}
// use set logic to look for new files
HashSet<String> newStones = new HashSet<String>();
for (String x : tombstones) {
newStones.add(x);
}
boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones);
// keep the new list for the next time
mTombstones = newStones;
return result;
}
/**
* 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
* -zARGS means option z with (optional) arguments ARGS
* --zz means option zz
* --zz ARGS means option zz with (non-optional) arguments ARGS
*
* Note that you cannot combine single letter options; -abc != -a -b -c
*
* @return Returns the option string, or null if there are no more options.
*/
private String nextOption() {
if (mNextArg >= mArgs.length) {
return null;
}
String arg = mArgs[mNextArg];
if (!arg.startsWith("-")) {
return null;
}
mNextArg++;
if (arg.equals("--")) {
return null;
}
if (arg.length() > 1 && arg.charAt(1) != '-') {
if (arg.length() > 2) {
mCurArgData = arg.substring(2);
return arg.substring(0, 2);
} else {
mCurArgData = null;
return arg;
}
}
mCurArgData = null;
return arg;
}
/**
* Return the next data associated with the current option.
*
* @return Returns the data string, or null of there are no more arguments.
*/
private String nextOptionData() {
if (mCurArgData != null) {
return mCurArgData;
}
if (mNextArg >= mArgs.length) {
return null;
}
String data = mArgs[mNextArg];
mNextArg++;
return data;
}
/**
* Returns a long converted from the next data argument, with error handling if not available.
*
* @param opt The name of the option.
* @return Returns a long converted from the argument.
*/
private long nextOptionLong(final String opt) {
long result;
try {
result = Long.parseLong(nextOptionData());
} catch (NumberFormatException e) {
System.err.println("** Error: " + opt + " is not a number");
throw e;
}
return result;
}
/**
* Return the next argument on the command line.
*
* @return Returns the argument string, or null if we have reached the end.
*/
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
}
String arg = mArgs[mNextArg];
mNextArg++;
return arg;
}
/**
* 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-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");
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,61 +0,0 @@
/*
* 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);
}

View File

@@ -1,41 +0,0 @@
/*
* 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();
}

View File

@@ -1,76 +0,0 @@
/*
* 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;
}
}
}

View File

@@ -1,125 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,156 +0,0 @@
/*
* 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;
}
}

View File

@@ -1,467 +0,0 @@
/*
* 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,
KeyEvent.KEYCODE_MUTE,
};
/** 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",
"KEYCODE_MUTE",
"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;
}
}

View File

@@ -1,427 +0,0 @@
/*
* 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;
}
}