FileDocCategorySizeDatePackage
Monkey.javaAPI DocAndroid 1.5 API33398Wed May 06 22:41:08 BST 2009com.android.commands.monkey

Monkey

public class Monkey extends Object
Application that injects random key events and other actions into the system.

Fields Summary
private static final int
DEBUG_ALLOW_ANY_STARTS
Monkey Debugging/Dev Support All values should be zero when checking in.
private static final int
DEBUG_ALLOW_ANY_RESTARTS
private android.app.IActivityManager
mAm
private android.view.IWindowManager
mWm
private android.content.pm.IPackageManager
mPm
private String[]
mArgs
Command line arguments
private int
mNextArg
Current argument being parsed
private String
mCurArgData
Data of current argument
private int
mVerbose
Running in verbose output mode? 1= verbose, 2=very verbose
private boolean
mIgnoreCrashes
Ignore any application crashes while running?
private boolean
mIgnoreTimeouts
Ignore any not responding timeouts while running?
private boolean
mIgnoreSecurityExceptions
(The activity launch still fails, but we keep pluggin' away)
private boolean
mMonitorNativeCrashes
Monitor /data/tombstones and stop the monkey if new files appear.
private boolean
mSendNoEvents
Send no events. Use with long throttle-time to watch user operations
private boolean
mAbort
This is set when we would like to abort the running of the monkey.
private boolean
mRequestAnrTraces
This is set by the ActivityWatcher thread to request collection of ANR trace files
private boolean
mRequestDumpsysMemInfo
This is set by the ActivityWatcher thread to request a "dumpsys meminfo"
private boolean
mKillProcessAfterError
Kill the process after a timeout or crash.
private boolean
mGenerateHprof
Generate hprof reports before/after monkey runs
private HashSet
mValidPackages
Packages we are allowed to run, or empty if no restriction.
ArrayList
mMainCategories
Categories we are allowed to launch
private ArrayList
mMainApps
Applications we can switch to.
long
mThrottle
The delay between event inputs
int
mCount
The number of iterations
long
mSeed
The random number seed
long
mDroppedKeyEvents
Dropped-event statistics
long
mDroppedPointerEvents
long
mDroppedTrackballEvents
long
mDroppedFlipEvents
private String
mScriptFileName
a filename to the script (if any)
private static final File
TOMBSTONES_PATH
private HashSet
mTombstones
float[]
mFactors
MonkeyEventSource
mEventSource
Constructors Summary
Methods Summary
private booleancheckInternalConfiguration()
Check for any internal configuration (primarily build-time) errors.

return
Returns true if ready to rock.

        // 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;
    
private booleancheckNativeCrashes()
Watch for appearance of new tombstone files, which indicate native crashes.

return
Returns true if new files have appeared in the list

        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;
    
private voidcommandLineReport(java.lang.String reportName, java.lang.String command)
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)

        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());
        }
    
private booleangetMainApps()
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

        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;
    
private booleangetSystemInterfaces()
Attach to the required system interfaces.

return
Returns true if all system interfaces were available.

        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;
    
public static voidmain(java.lang.String[] args)
Command-line entry point.

param
args The command-line arguments

        int resultCode = (new Monkey()).run(args);
        System.exit(resultCode);
    
private java.lang.StringnextArg()
Return the next argument on the command line.

return
Returns the argument string, or null if we have reached the end.

        if (mNextArg >= mArgs.length) {
            return null;
        }
        String arg = mArgs[mNextArg];
        mNextArg++;
        return arg;
    
private java.lang.StringnextOption()
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.

        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;
    
private java.lang.StringnextOptionData()
Return the next data associated with the current option.

return
Returns the data string, or null of there are no more arguments.

        if (mCurArgData != null) {
            return mCurArgData;
        }
        if (mNextArg >= mArgs.length) {
            return null;
        }
        String data = mArgs[mNextArg];
        mNextArg++;
        return data;
    
private longnextOptionLong(java.lang.String opt)
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.

        long result;
        try {
            result = Long.parseLong(nextOptionData());
        } catch (NumberFormatException e) {
            System.err.println("** Error: " + opt + " is not a number");
            throw e;
        }
        return result;
    
private booleanprocessOptions()
Process the command-line options

return
Returns true if options were parsed with no apparent errors.

        // 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;
    
private voidreportAnrTraces()
Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous report writing complete.

        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) { 
        }
        commandLineReport("anr traces", "cat /data/anr/traces.txt");
    
private voidreportDumpsysMemInfo()
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.

        commandLineReport("meminfo", "dumpsys meminfo");
    
private voidreportProcRank()
Run the procrank tool to insert system status information into the debug report.

      commandLineReport("procrank", "procrank");
    
private intrun(java.lang.String[] args)
Run the command!

param
args The command-line arguments
return
Returns a posix-style result code. 0 for no error.

        // 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, mThrottle);
            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;
        }
    
private intrunMonkeyCycles()
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.

        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;
                }
            }

            // 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) {
                // We don't want to count throttling as an event.
                if (!(ev instanceof MonkeyThrottleEvent)) {
                    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;
    
private voidshowUsage()
Print how to use this command.

      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");
  
private voidsignalPersistentProcesses()
Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports in data/misc, so use with care.

        try {
            mAm.signalPersistentProcesses(Process.SIGNAL_USR1);

            synchronized (this) {
                wait(2000);
            }
        } catch (RemoteException e) {
            System.err.println("** Failed talking with activity manager!");
        } catch (InterruptedException e) {
        }