/* * 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.IActivityController; import android.app.IActivityManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.ResolveInfo; import android.os.Build; import android.os.Debug; import android.os.Environment; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserId; import android.view.IWindowManager; import android.view.Surface; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; 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. */ 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;
/** Ignore any native crashes while running? */
private boolean mIgnoreNativeCrashes;
/** 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;
/**
* Count each event as a cycle. Set to false for scripts so that each time
* through the script increments the count.
*/
private boolean mCountEvents = true;
/**
* This is set by the ActivityController thread to request collection of ANR
* trace files
*/
private boolean mRequestAnrTraces = false;
/**
* This is set by the ActivityController thread to request a
* "dumpsys meminfo"
*/
private boolean mRequestDumpsysMemInfo = false;
/**
* This is set by the ActivityController thread to request a
* bugreport after ANR
*/
private boolean mRequestAnrBugreport = false;
/**
* This is set by the ActivityController thread to request a
* bugreport after java application crash
*/
private boolean mRequestAppCrashBugreport = false;
/**Request the bugreport based on the mBugreportFrequency. */
private boolean mGetPeriodicBugreport = false;
/**
* Request the bugreport based on the mBugreportFrequency.
*/
private boolean mRequestPeriodicBugreport = false;
/** Bugreport frequency. */
private long mBugreportFrequency = 10;
/** Failure process name */
private String mReportProcessName;
/**
* This is set by the ActivityController thread to request a "procrank"
*/
private boolean mRequestProcRank = false;
/** Kill the process after a timeout or crash. */
private boolean mKillProcessAfterError;
/** Generate hprof reports before/after monkey runs */
private boolean mGenerateHprof;
/** Package blacklist file. */
private String mPkgBlacklistFile;
/** Package whitelist file. */
private String mPkgWhitelistFile;
/** Packages we are allowed to run, or empty if no restriction. */
private HashSet
* NOTE: You cannot perform a dumpsys call from the ActivityController
* 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.
*
* TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both
* streams (might be important for some command lines)
*
* @param reportName Simple tag that will print before the report and in
* various annotations.
* @param command Command line to execute.
*/
private void commandLineReport(String reportName, String command) {
System.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
Writer logOutput = null;
try {
// Process must be fully qualified here because android.os.Process
// is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
if (mRequestBugreport) {
logOutput =
new BufferedWriter(new FileWriter(new File(Environment
.getExternalStorageDirectory(), reportName), true));
}
// 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) {
if (mRequestBugreport) {
logOutput.write(s);
logOutput.write("\n");
} else {
System.err.println(s);
}
}
int status = p.waitFor();
System.err.println("// " + reportName + " status was " + status);
if (logOutput != null) {
logOutput.close();
}
} catch (Exception e) {
System.err.println("// Exception from " + reportName + ":");
System.err.println(e.toString());
}
}
// Write the numbe of iteration to the log
private void writeScriptLog(int count) {
// TO DO: Add the script file name to the log.
try {
Writer output = new BufferedWriter(new FileWriter(new File(
Environment.getExternalStorageDirectory(), "scriptlog.txt"), true));
output.write("iteration: " + count + " time: "
+ MonkeyUtils.toCalendarTime(System.currentTimeMillis()) + "\n");
output.close();
} catch (IOException e) {
System.err.println(e.toString());
}
}
// Write the bugreport to the sdcard.
private void getBugreport(String reportName) {
reportName += MonkeyUtils.toCalendarTime(System.currentTimeMillis());
String bugreportName = reportName.replaceAll("[ ,:]", "_");
commandLineReport(bugreportName + ".txt", "bugreport");
}
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
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;
}
if (!loadPackageLists()) {
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
* TODO: Meta state on keys
*
* @return Returns the last cycle which executed. If the value == mCount, no
* errors detected.
*/
private int runMonkeyCycles() {
int eventCounter = 0;
int cycleCounter = 0;
boolean shouldReportAnrTraces = false;
boolean shouldReportDumpsysMemInfo = false;
boolean shouldAbort = false;
boolean systemCrashed = false;
// TO DO : The count should apply to each of the script file.
while (!systemCrashed && cycleCounter < mCount) {
synchronized (this) {
if (mRequestProcRank) {
reportProcRank();
mRequestProcRank = false;
}
if (mRequestAnrTraces) {
mRequestAnrTraces = false;
shouldReportAnrTraces = true;
}
if (mRequestAnrBugreport){
getBugreport("anr_" + mReportProcessName + "_");
mRequestAnrBugreport = false;
}
if (mRequestAppCrashBugreport){
getBugreport("app_crash" + mReportProcessName + "_");
mRequestAppCrashBugreport = false;
}
if (mRequestPeriodicBugreport){
getBugreport("Bugreport_");
mRequestPeriodicBugreport = false;
}
if (mRequestDumpsysMemInfo) {
mRequestDumpsysMemInfo = false;
shouldReportDumpsysMemInfo = true;
}
if (mMonitorNativeCrashes) {
// first time through, when eventCounter == 0, just set up
// the watcher (ignore the error)
if (checkNativeCrashes() && (eventCounter > 0)) {
System.out.println("** New native crash detected.");
if (mRequestBugreport) {
getBugreport("native_crash_");
}
mAbort = mAbort || !mIgnoreNativeCrashes || mKillProcessAfterError;
}
}
if (mAbort) {
shouldAbort = true;
}
}
// Report ANR, dumpsys after releasing lock on this.
// This ensures the availability of the lock to Activity controller's appNotResponding
if (shouldReportAnrTraces) {
shouldReportAnrTraces = false;
reportAnrTraces();
}
if (shouldReportDumpsysMemInfo) {
shouldReportDumpsysMemInfo = false;
reportDumpsysMemInfo();
}
if (shouldAbort) {
shouldAbort = false;
System.out.println("** Monkey aborted due to error.");
System.out.println("Events injected: " + eventCounter);
return eventCounter;
}
// 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) {
eventCounter++;
cycleCounter++;
continue;
}
if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
String calendarTime = MonkeyUtils.toCalendarTime(System.currentTimeMillis());
long systemUpTime = SystemClock.elapsedRealtime();
System.out.println(" //[calendar_time:" + calendarTime + " system_uptime:"
+ systemUpTime + "]");
System.out.println(" // Sending event #" + eventCounter);
}
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
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 (ev instanceof MonkeyRotationEvent) {
mDroppedRotationEvents++;
}
} else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
systemCrashed = true;
System.err.println("** Error: RemoteException while injecting event.");
} else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
systemCrashed = !mIgnoreSecurityExceptions;
if (systemCrashed) {
System.err.println("** Error: SecurityException while injecting event.");
}
}
// Don't count throttling as an event.
if (!(ev instanceof MonkeyThrottleEvent)) {
eventCounter++;
if (mCountEvents) {
cycleCounter++;
}
}
} else {
if (!mCountEvents) {
cycleCounter++;
writeScriptLog(cycleCounter);
//Capture the bugreport after n iteration
if (mGetPeriodicBugreport) {
if ((cycleCounter % mBugreportFrequency) == 0) {
mRequestPeriodicBugreport = true;
}
}
} else {
// Event Source has signaled that we have no more events to process
break;
}
}
}
System.out.println("Events injected: " + eventCounter);
return eventCounter;
}
/**
* 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
* -- 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() {
StringBuffer usage = new StringBuffer();
usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
usage.append(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
usage.append(" [--ignore-crashes] [--ignore-timeouts]\n");
usage.append(" [--ignore-security-exceptions]\n");
usage.append(" [--monitor-native-crashes] [--ignore-native-crashes]\n");
usage.append(" [--kill-process-after-error] [--hprof]\n");
usage.append(" [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
usage.append(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
usage.append(" [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
usage.append(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
usage.append(" [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
usage.append(" [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
usage.append(" [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
usage.append(" [--wait-dbg] [--dbg-no-events]\n");
usage.append(" [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
usage.append(" [--port port]\n");
usage.append(" [-s SEED] [-v [-v] ...]\n");
usage.append(" [--throttle MILLISEC] [--randomize-throttle]\n");
usage.append(" [--profile-wait MILLISEC]\n");
usage.append(" [--device-sleep-time MILLISEC]\n");
usage.append(" [--randomize-script]\n");
usage.append(" [--script-log]\n");
usage.append(" [--bugreport]\n");
usage.append(" [--periodic-bugreport]\n");
usage.append(" COUNT\n");
System.err.println(usage.toString());
}
}