diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java index 521de16d6..838f274d3 100644 --- a/cmds/monkey/src/com/android/commands/monkey/Monkey.java +++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java @@ -1,19 +1,18 @@ -/** -** 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. -*/ - +/* + * 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; @@ -50,19 +49,25 @@ 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; @@ -82,16 +87,28 @@ public class Monkey { /** 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 */ + /** 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 ActivityController thread to request collection of ANR trace files */ + /** + * 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" */ + /** + * This is set by the ActivityController thread to request a + * "dumpsys meminfo" + */ private boolean mRequestDumpsysMemInfo = false; /** Kill the process after a timeout or crash. */ @@ -102,8 +119,10 @@ public class Monkey { /** Packages we are allowed to run, or empty if no restriction. */ private HashSet mValidPackages = new HashSet(); + /** Categories we are allowed to launch **/ - ArrayList mMainCategories = new ArrayList(); + private ArrayList mMainCategories = new ArrayList(); + /** Applications we can switch to. */ private ArrayList mMainApps = new ArrayList(); @@ -118,8 +137,11 @@ public class Monkey { /** Dropped-event statistics **/ long mDroppedKeyEvents = 0; + long mDroppedPointerEvents = 0; + long mDroppedTrackballEvents = 0; + long mDroppedFlipEvents = 0; /** a filename to the script (if any) **/ @@ -129,14 +151,18 @@ public class Monkey { private int mServerPort = -1; private static final File TOMBSTONES_PATH = new File("/data/tombstones"); + private HashSet mTombstones = null; float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT]; + MonkeyEventSource mEventSource; + private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor(); // information on the current activity. public static Intent currentIntent; + public static String currentPackage; /** @@ -146,8 +172,8 @@ public class Monkey { 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); + System.out.println(" // " + (allow ? "Allowing" : "Rejecting") + " start of " + + intent + " in package " + pkg); } currentPackage = pkg; currentIntent = intent; @@ -160,7 +186,7 @@ public class Monkey { if (!allow) { if (mVerbose > 0) { System.out.println(" // " + (allow ? "Allowing" : "Rejecting") - + " resume of package " + pkg); + + " resume of package " + pkg); } } currentPackage = pkg; @@ -171,7 +197,8 @@ public class Monkey { if (pkg == null) { return true; } - // preflight the hash lookup to avoid the cost of hash key generation + // preflight the hash lookup to avoid the cost of hash key + // generation if (mValidPackages.size() == 0) { return true; } else { @@ -179,26 +206,22 @@ public class Monkey { } } - public boolean appCrashed(String processName, int pid, String shortMsg, - String longMsg, byte[] crashData) { - System.err.println("// CRASH: " + processName + " (pid " + pid - + ")"); + 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()); + 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("// Build Time: " + cd.getBuildData().getTime()); System.err.println("// ID: " + cd.getId()); System.err.println("// Tag: " + cd.getActivity()); - System.err.println(cd.getThrowableData().toString( - "// ")); + System.err.println(cd.getThrowableData().toString("// ")); } catch (IOException e) { System.err.println("// BAD STACK CRAWL"); } @@ -214,10 +237,8 @@ public class Monkey { return false; } - public int appNotResponding(String processName, int pid, - String processStats) { - System.err.println("// NOT RESPONDING: " + processName - + " (pid " + pid + ")"); + 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) { @@ -235,15 +256,16 @@ public class Monkey { } /** - * Run the procrank tool to insert system status information into the debug report. + * Run the procrank tool to insert system status information into the debug + * report. */ private void reportProcRank() { - commandLineReport("procrank", "procrank"); + commandLineReport("procrank", "procrank"); } /** - * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous - * report writing complete. + * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the + * asynchronous report writing complete. */ private void reportAnrTraces() { try { @@ -255,9 +277,10 @@ public class Monkey { /** * Run "dumpsys meminfo" - * - * 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. + *

+ * 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"); @@ -265,16 +288,20 @@ public class Monkey { /** * Print report from a single command line. - * @param reportName Simple tag that will print before the report and in various annotations. + *

+ * 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. - * 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 + // 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 @@ -308,7 +335,7 @@ public class Monkey { * Run the command! * * @param args The command-line arguments - * @return Returns a posix-style result code. 0 for no error. + * @return Returns a posix-style result code. 0 for no error. */ private int run(String[] args) { // Super-early debugger wait @@ -328,7 +355,7 @@ public class Monkey { mArgs = args; mNextArg = 0; - //set a positive value, indicating none of the factors is provided yet + // 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; } @@ -375,6 +402,8 @@ public class Monkey { // script mode, ignore other options mEventSource = new MonkeySourceScript(mScriptFileName, mThrottle); mEventSource.setVerbose(mVerbose); + + mCountEvents = false; } else if (mServerPort != -1) { try { mEventSource = new MonkeySourceNetwork(mServerPort); @@ -385,37 +414,29 @@ public class Monkey { mCount = Integer.MAX_VALUE; } else { // random source by default - if (mVerbose >= 2) { // check seeding performance + 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 + // 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 + // in random mode, we start with a random activity ((MonkeySourceRandom) mEventSource).generateActivity(); } - //validate source generator + // 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 we're profiling, do it immediately before/after the main monkey + // loop if (mGenerateHprof) { signalPersistentProcesses(); } @@ -469,9 +490,9 @@ public class Monkey { mNetworkMonitor.dump(); if (crashedAtCycle < mCount - 1) { - System.err.println("** System appears to have crashed at event " - + crashedAtCycle + " of " + mCount + " using seed " + mSeed); - return crashedAtCycle; + 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"); @@ -516,29 +537,29 @@ public class Monkey { } else if (opt.equals("--hprof")) { mGenerateHprof = true; } else if (opt.equals("--pct-touch")) { - mFactors[MonkeySourceRandom.FACTOR_TOUCH] = - -nextOptionLong("touch events percentage"); + int i = MonkeySourceRandom.FACTOR_TOUCH; + mFactors[i] = -nextOptionLong("touch events percentage"); } else if (opt.equals("--pct-motion")) { - mFactors[MonkeySourceRandom.FACTOR_MOTION] = - -nextOptionLong("motion events percentage"); + int i = MonkeySourceRandom.FACTOR_MOTION; + mFactors[i] = -nextOptionLong("motion events percentage"); } else if (opt.equals("--pct-trackball")) { - mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] = - -nextOptionLong("trackball events percentage"); + int i = MonkeySourceRandom.FACTOR_TRACKBALL; + mFactors[i] = -nextOptionLong("trackball events percentage"); } else if (opt.equals("--pct-nav")) { - mFactors[MonkeySourceRandom.FACTOR_NAV] = - -nextOptionLong("nav events percentage"); + int i = MonkeySourceRandom.FACTOR_NAV; + mFactors[i] = -nextOptionLong("nav events percentage"); } else if (opt.equals("--pct-majornav")) { - mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] = - -nextOptionLong("major nav events percentage"); + int i = MonkeySourceRandom.FACTOR_MAJORNAV; + mFactors[i] = -nextOptionLong("major nav events percentage"); } else if (opt.equals("--pct-appswitch")) { - mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] = - -nextOptionLong("app switch events percentage"); + int i = MonkeySourceRandom.FACTOR_APPSWITCH; + mFactors[i] = -nextOptionLong("app switch events percentage"); } else if (opt.equals("--pct-flip")) { - mFactors[MonkeySourceRandom.FACTOR_FLIP] = - -nextOptionLong("keyboard flip percentage"); + int i = MonkeySourceRandom.FACTOR_FLIP; + mFactors[i] = -nextOptionLong("keyboard flip percentage"); } else if (opt.equals("--pct-anyevent")) { - mFactors[MonkeySourceRandom.FACTOR_ANYTHING] = - -nextOptionLong("any events percentage"); + int i = MonkeySourceRandom.FACTOR_ANYTHING; + mFactors[i] = -nextOptionLong("any events percentage"); } else if (opt.equals("--throttle")) { mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); } else if (opt.equals("--wait-dbg")) { @@ -615,19 +636,22 @@ public class Monkey { private boolean getSystemInterfaces() { mAm = ActivityManagerNative.getDefault(); if (mAm == null) { - System.err.println("** Error: Unable to connect to activity manager; is the system running?"); + 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?"); + 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?"); + System.err.println("** Error: Unable to connect to package manager; is the system " + + "running?"); return false; } @@ -643,15 +667,16 @@ public class Monkey { } /** - * Using the restrictions provided (categories & packages), generate a list of activities - * that we can actually switch to. + * 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 + * @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++) { + for (int i = 0; i < N; i++) { Intent intent = new Intent(Intent.ACTION_MAIN); String category = mMainCategories.get(i); if (category.length() > 0) { @@ -662,31 +687,26 @@ public class Monkey { System.err.println("// Warning: no activities found for category " + category); continue; } - if (mVerbose >= 2) { // very verbose + 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 + 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 - + ")"); + + r.activityInfo.applicationInfo.packageName + ")"); } - mMainApps.add(new ComponentName( - r.activityInfo.applicationInfo.packageName, + mMainApps.add(new ComponentName(r.activityInfo.applicationInfo.packageName, r.activityInfo.name)); } else { - if (mVerbose >= 3) { // very very verbose + if (mVerbose >= 3) { // very very verbose System.out.println("// - NOT USING main activity " - + r.activityInfo.name - + " (from package " - + r.activityInfo.applicationInfo.packageName - + ")"); + + r.activityInfo.name + " (from package " + + r.activityInfo.applicationInfo.packageName + ")"); } } } @@ -706,18 +726,19 @@ public class Monkey { /** * 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. + * @return Returns the last cycle which executed. If the value == mCount, no + * errors detected. */ private int runMonkeyCycles() { - int i = 0; - int lastKey = 0; + int eventCounter = 0; + int cycleCounter = 0; boolean systemCrashed = false; - while (!systemCrashed && i < mCount) { + while (!systemCrashed && cycleCounter < mCount) { synchronized (this) { if (mRequestAnrTraces) { reportAnrTraces(); @@ -728,37 +749,36 @@ public class Monkey { mRequestDumpsysMemInfo = false; } if (mMonitorNativeCrashes) { - // first time through, when i == 0, just set up the watcher (ignore the error) - if (checkNativeCrashes() && (i > 0)) { + // 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."); mAbort = mAbort || mKillProcessAfterError; } } if (mAbort) { System.out.println("** Monkey aborted due to error."); - System.out.println("Events injected: " + i); - return i; + 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. + // 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++; + eventCounter++; + cycleCounter++; continue; } - if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) { - System.out.println(" // Sending event #" + i); + if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) { + System.out.println(" // Sending event #" + eventCounter); } 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) { @@ -773,18 +793,29 @@ public class Monkey { } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) { systemCrashed = !mIgnoreSecurityExceptions; } + + // Don't count throttling as an event. + if (!(ev instanceof MonkeyThrottleEvent)) { + eventCounter++; + if (mCountEvents) { + cycleCounter++; + } + } } else { - // Event Source has signaled that we have no more events to process - break; + if (!mCountEvents) { + cycleCounter++; + } else { + break; + } } } // If we got this far, we succeeded! - return mCount; + return eventCounter; } /** - * Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports - * in data/misc, so use with care. + * Send SIGNAL_USR1 to all processes. This will generate large (5mb) + * profiling reports in data/misc, so use with care. */ private void signalPersistentProcesses() { try { @@ -800,14 +831,16 @@ public class Monkey { } /** - * Watch for appearance of new tombstone files, which indicate native crashes. + * 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 + // shortcut path for usually empty directory, so we don't waste even + // more objects if ((tombstones == null) || (tombstones.length == 0)) { mTombstones = null; return false; @@ -828,17 +861,20 @@ public class Monkey { } /** - * Return the next command line option. This has a number of special cases which - * closely, but not exactly, follow the POSIX command line options patterns: + * 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 + * 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. */ @@ -885,7 +921,8 @@ public class Monkey { } /** - * Returns a long converted from the next data argument, with error handling if not available. + * 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. @@ -919,19 +956,21 @@ public class Monkey { * Print how to use this command. */ private void showUsage() { - System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]"); - System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]"); - System.err.println(" [--ignore-crashes] [--ignore-timeouts]"); - System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]"); - System.err.println(" [--kill-process-after-error] [--hprof]"); - System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]"); - System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]"); - System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]"); - System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]"); - System.err.println(" [--pct-anyevent PERCENT]"); - System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]"); - System.err.println(" [--port port]"); - System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]"); - System.err.println(" COUNT"); - } + 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] [--monitor-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]\n"); + usage.append(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]\n"); + usage.append(" [--port port]\n"); + usage.append(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]\n"); + usage.append(" COUNT"); + System.err.println(usage.toString()); + } } diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java index a236554a5..af8b4ecbd 100644 --- a/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java @@ -19,23 +19,24 @@ package com.android.commands.monkey; /** * event source interface */ -public interface MonkeyEventSource { +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 + * + * @return false if something fails, e.g. factor failure in random source or + * file can not open from script source etc */ public boolean validate(); } diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java index d93f72606..5c7fdbc0e 100644 --- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java @@ -475,7 +475,7 @@ public class MonkeySourceRandom implements MonkeyEventSource { */ public MonkeyEvent getNextEvent() { if (mQ.isEmpty()) { - generateEvents(); + generateEvents(); } mEventCount++; MonkeyEvent e = mQ.getFirst(); diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java index 2c7df4891..e37238597 100644 --- a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java +++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java @@ -23,9 +23,9 @@ 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.NoSuchElementException; /** * monkey event queue. It takes a script to produce events sample script format: @@ -77,10 +77,6 @@ public class MonkeySourceScript implements MonkeyEventSource { // 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 = 0; - // event key word in the capture log private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; @@ -111,81 +107,99 @@ public class MonkeySourceScript implements MonkeyEventSource { DataInputStream mInputStream; - BufferedReader mBufferReader; + BufferedReader mBufferedReader; + /** + * Creates a MonkeySourceScript instance. + * + * @param filename The filename of the script (on the device). + * @param throttle The amount of time in ms to sleep between events. + */ public MonkeySourceScript(String filename, long throttle) { mScriptFileName = filename; mQ = new MonkeyEventQueue(throttle); } /** - * @return the number of total events that will be generated in a round + * Resets the globals used to timeshift events. */ - public int getOneRoundEventCount() { - // plus one home key down and up event - return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT; - } - private void resetValue() { mLastRecordedDownTimeKey = 0; mLastRecordedDownTimeMotion = 0; + mLastRecordedEventTime = -1; 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"); - } + /** + * Reads the header of the script file. + * + * @return True if the file header could be parsed, and false otherwise. + * @throws IOException If there was an error reading the file. + */ + private boolean readHeader() throws IOException { + mFileOpened = true; - mFStream = new FileInputStream(mScriptFileName); - mInputStream = new DataInputStream(mFStream); - mBufferReader = new BufferedReader(new InputStreamReader(mInputStream)); - String sLine; - while ((sLine = mBufferReader.readLine()) != null) { - sLine = sLine.trim(); + mFStream = new FileInputStream(mScriptFileName); + mInputStream = new DataInputStream(mFStream); + mBufferedReader = new BufferedReader(new InputStreamReader(mInputStream)); - 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()); + String line; - } 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; + while ((line = mBufferedReader.readLine()) != null) { + line = line.trim(); + + if (line.indexOf(HEADER_COUNT) >= 0) { + try { + String value = line.substring(HEADER_COUNT.length() + 1).trim(); + mEventCountInScript = Integer.parseInt(value); + } catch (NumberFormatException e) { + System.err.println(e); + return false; } + } else if (line.indexOf(HEADER_SPEED) >= 0) { + try { + String value = line.substring(HEADER_COUNT.length() + 1).trim(); + mSpeed = Double.parseDouble(value); + } catch (NumberFormatException e) { + System.err.println(e); + return false; + } + } else if (line.indexOf(STARTING_DATA_LINE) >= 0) { + 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; } + /** + * Reads a number of lines and passes the lines to be processed. + * + * @return The number of lines read. + * @throws IOException If there was an error reading the file. + */ + private int readLines() throws IOException { + String line; + for (int i = 0; i < MAX_ONE_TIME_READS; i++) { + line = mBufferedReader.readLine(); + if (line == null) { + return i; + } + line.trim(); + processLine(line); + } + return MAX_ONE_TIME_READS; + } + + /** + * Creates an event and adds it to the event queue. If the parameters are + * not understood, they are ignored and no events are added. + * + * @param s The entire string from the script file. + * @param args An array of arguments extracted from the script file line. + */ private void handleEvent(String s, String[] args) { // Handle key event if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) { @@ -291,82 +305,77 @@ public class MonkeySourceScript implements MonkeyEventSource { } } - private void processLine(String s) { - int index1 = s.indexOf('('); - int index2 = s.indexOf(')'); + /** + * Extracts an event and a list of arguments from a line. If the line does + * not match the format required, it is ignored. + * + * @param line A string in the form {@code cmd(arg1,arg2,arg3)}. + */ + private void processLine(String line) { + int index1 = line.indexOf('('); + int index2 = line.indexOf(')'); if (index1 < 0 || index2 < 0) { return; } - String[] args = s.substring(index1 + 1, index2).split(","); + String[] args = line.substring(index1 + 1, index2).split(","); - handleEvent(s, args); + for (int i = 0; i < args.length; i++) { + args[i] = args[i].trim(); + } + + handleEvent(line, args); } - private void closeFile() { + /** + * Closes the script file. + * + * @throws IOException If there was an error closing the file. + */ + private void closeFile() throws IOException { mFileOpened = false; - if (THIS_DEBUG) { - System.out.println("closing script file"); - } try { mFStream.close(); mInputStream.close(); - } catch (IOException e) { - System.out.println(e); + } catch (NullPointerException e) { + // File was never opened so it can't be closed. } } /** - * read next batch of events from the provided script file + * Read next batch of events from the script file into the event queue. + * Checks if the script is open and then reads the next MAX_ONE_TIME_READS + * events or reads until the end of the file. If no events are read, then + * the script is closed. * - * @return true if success + * @throws IOException If there was an error reading the file. */ - private boolean readNextBatch() { - /* - * The script should restore the original state when it run multiple - * times. - */ - String sLine = null; - int readCount = 0; + private void readNextBatch() throws IOException { + int linesRead = 0; if (THIS_DEBUG) { System.out.println("readNextBatch(): reading next batch of events"); } if (!mFileOpened) { - if (!readScriptHeader()) { - closeFile(); - return false; - } resetValue(); + readHeader(); } - 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; - } + linesRead = readLines(); - if (sLine == null) { - // to the end of the file - if (THIS_DEBUG) { - System.out.println("readNextBatch(): to the end of file"); - } + if (linesRead == 0) { closeFile(); } - return true; } /** - * sleep for a period of given time, introducing latency among events + * Sleep for a period of given time. Used to introduce latency between + * events. * - * @param time to sleep in millisecond + * @param time The amount of time to sleep in ms */ private void needSleep(long time) { if (time < 1) { @@ -379,14 +388,23 @@ public class MonkeySourceScript implements MonkeyEventSource { } /** - * check whether we can successfully read the header of the script file + * Checks if the file can be opened and if the header is valid. + * + * @return True if the file exists and the header is valid, false otherwise. */ public boolean validate() { - boolean b = readNextBatch(); + boolean validHeader; + try { + validHeader = readHeader(); + closeFile(); + } catch (IOException e) { + return false; + } + if (mVerbose > 0) { System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed); } - return b; + return validHeader; } public void setVerbose(int verbose) { @@ -394,10 +412,10 @@ public class MonkeySourceScript implements MonkeyEventSource { } /** - * adjust key downtime and eventtime according to both recorded values and - * current system time + * Adjust key downtime and eventtime according to both recorded values and + * current system time. * - * @param e KeyEvent + * @param e A KeyEvent */ private void adjustKeyEventTime(MonkeyKeyEvent e) { if (e.getEventTime() < 0) { @@ -431,10 +449,10 @@ public class MonkeySourceScript implements MonkeyEventSource { } /** - * adjust motion downtime and eventtime according to both recorded values - * and current system time + * Adjust motion downtime and eventtime according to both recorded values + * and current system time. * - * @param e KeyEvent + * @param e A KeyEvent */ private void adjustMotionEventTime(MonkeyMotionEvent e) { if (e.getEventTime() < 0) { @@ -469,25 +487,40 @@ public class MonkeySourceScript implements MonkeyEventSource { } /** - * if the queue is empty, we generate events first + * Gets the next event to be injected from the script. If the event queue is + * empty, reads the next n events from the script into the queue, where n is + * the lesser of the number of remaining events and the value specified by + * MAX_ONE_TIME_READS. If the end of the file is reached, no events are + * added to the queue and null is returned. * - * @return the first event in the queue, if null, indicating the system - * crashes + * @return The first event in the event queue or null if the end of the file + * is reached or if an error is encountered reading the file. */ public MonkeyEvent getNextEvent() { long recordedEventTime = -1; + MonkeyEvent ev; if (mQ.isEmpty()) { - readNextBatch(); + try { + readNextBatch(); + } catch (IOException e) { + return null; + } } - 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); + + try { + ev = mQ.getFirst(); + mQ.removeFirst(); + } catch (NoSuchElementException e) { + return null; } - return e; + + if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) { + adjustKeyEventTime((MonkeyKeyEvent) ev); + } else if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER + || ev.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) { + adjustMotionEventTime((MonkeyMotionEvent) ev); + } + return ev; } }