summaryrefslogtreecommitdiffstats
path: root/cmds/monkey
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit5c11852110eeb03dc5a69111354b383f98d15336 (patch)
tree7b26fde6effb80c241f28fc3e620016e7f86418e /cmds/monkey
downloadandroid_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.gz
android_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.bz2
android_development-5c11852110eeb03dc5a69111354b383f98d15336.zip
Initial Contribution
Diffstat (limited to 'cmds/monkey')
-rw-r--r--cmds/monkey/Android.mk13
-rw-r--r--cmds/monkey/MODULE_LICENSE_APACHE20
-rw-r--r--cmds/monkey/NOTICE190
-rwxr-xr-xcmds/monkey/monkey7
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/Monkey.java1290
5 files changed, 1500 insertions, 0 deletions
diff --git a/cmds/monkey/Android.mk b/cmds/monkey/Android.mk
new file mode 100644
index 000000000..6bedc43e8
--- /dev/null
+++ b/cmds/monkey/Android.mk
@@ -0,0 +1,13 @@
+# Copyright 2008 The Android Open Source Project
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := monkey
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+ALL_PREBUILT += $(TARGET_OUT)/bin/monkey
+$(TARGET_OUT)/bin/monkey : $(LOCAL_PATH)/monkey | $(ACP)
+ $(transform-prebuilt-to-target)
diff --git a/cmds/monkey/MODULE_LICENSE_APACHE2 b/cmds/monkey/MODULE_LICENSE_APACHE2
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/cmds/monkey/MODULE_LICENSE_APACHE2
diff --git a/cmds/monkey/NOTICE b/cmds/monkey/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/cmds/monkey/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/cmds/monkey/monkey b/cmds/monkey/monkey
new file mode 100755
index 000000000..45f43a45c
--- /dev/null
+++ b/cmds/monkey/monkey
@@ -0,0 +1,7 @@
+# Script to start "monkey" on the device, which has a very rudimentary
+# shell.
+#
+base=/system
+export CLASSPATH=$base/framework/monkey.jar
+exec app_process $base/bin com.android.commands.monkey.Monkey $*
+
diff --git a/cmds/monkey/src/com/android/commands/monkey/Monkey.java b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
new file mode 100644
index 000000000..5a8080cda
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
@@ -0,0 +1,1290 @@
+/**
+** 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.os.SystemClock;
+import android.server.data.CrashData;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerImpl;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Application that injects random key events and other actions into the system.
+ */
+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;
+
+ /** Key events that move around the UI. */
+ private final int[] NAV_KEYS = {
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
+ };
+ /**
+ * Key events that perform major navigation options (so shouldn't be sent
+ * as much).
+ */
+ private final int[] MAJOR_NAV_KEYS = {
+ KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
+ KeyEvent.KEYCODE_DPAD_CENTER,
+ };
+ /** Key events that perform system operations. */
+ private final int[] SYS_KEYS = {
+ KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL,
+ KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN,
+ };
+ /** Nice names for all key events. */
+ private final String[] KEY_NAMES = {
+ "KEYCODE_UNKNOWN",
+ "KEYCODE_MENU",
+ "KEYCODE_SOFT_RIGHT",
+ "KEYCODE_HOME",
+ "KEYCODE_BACK",
+ "KEYCODE_CALL",
+ "KEYCODE_ENDCALL",
+ "KEYCODE_0",
+ "KEYCODE_1",
+ "KEYCODE_2",
+ "KEYCODE_3",
+ "KEYCODE_4",
+ "KEYCODE_5",
+ "KEYCODE_6",
+ "KEYCODE_7",
+ "KEYCODE_8",
+ "KEYCODE_9",
+ "KEYCODE_STAR",
+ "KEYCODE_POUND",
+ "KEYCODE_DPAD_UP",
+ "KEYCODE_DPAD_DOWN",
+ "KEYCODE_DPAD_LEFT",
+ "KEYCODE_DPAD_RIGHT",
+ "KEYCODE_DPAD_CENTER",
+ "KEYCODE_VOLUME_UP",
+ "KEYCODE_VOLUME_DOWN",
+ "KEYCODE_POWER",
+ "KEYCODE_CAMERA",
+ "KEYCODE_CLEAR",
+ "KEYCODE_A",
+ "KEYCODE_B",
+ "KEYCODE_C",
+ "KEYCODE_D",
+ "KEYCODE_E",
+ "KEYCODE_F",
+ "KEYCODE_G",
+ "KEYCODE_H",
+ "KEYCODE_I",
+ "KEYCODE_J",
+ "KEYCODE_K",
+ "KEYCODE_L",
+ "KEYCODE_M",
+ "KEYCODE_N",
+ "KEYCODE_O",
+ "KEYCODE_P",
+ "KEYCODE_Q",
+ "KEYCODE_R",
+ "KEYCODE_S",
+ "KEYCODE_T",
+ "KEYCODE_U",
+ "KEYCODE_V",
+ "KEYCODE_W",
+ "KEYCODE_X",
+ "KEYCODE_Y",
+ "KEYCODE_Z",
+ "KEYCODE_COMMA",
+ "KEYCODE_PERIOD",
+ "KEYCODE_ALT_LEFT",
+ "KEYCODE_ALT_RIGHT",
+ "KEYCODE_SHIFT_LEFT",
+ "KEYCODE_SHIFT_RIGHT",
+ "KEYCODE_TAB",
+ "KEYCODE_SPACE",
+ "KEYCODE_SYM",
+ "KEYCODE_EXPLORER",
+ "KEYCODE_ENVELOPE",
+ "KEYCODE_ENTER",
+ "KEYCODE_DEL",
+ "KEYCODE_GRAVE",
+ "KEYCODE_MINUS",
+ "KEYCODE_EQUALS",
+ "KEYCODE_LEFT_BRACKET",
+ "KEYCODE_RIGHT_BRACKET",
+ "KEYCODE_BACKSLASH",
+ "KEYCODE_SEMICOLON",
+ "KEYCODE_APOSTROPHE",
+ "KEYCODE_SLASH",
+ "KEYCODE_AT",
+ "KEYCODE_NUM",
+ "KEYCODE_HEADSETHOOK",
+ "KEYCODE_FOCUS",
+ "KEYCODE_PLUS",
+ "KEYCODE_MENU",
+ "KEYCODE_NOTIFICATION",
+ "KEYCODE_SEARCH",
+
+ "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync
+ };
+
+ private IActivityManager mAm;
+ private IWindowManager mWm;
+ private IPackageManager mPm;
+
+ /** 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;
+
+ /** percentages for each type of event. These will be remapped to working
+ * values after we read any optional values.
+ **/
+ public static final int FACTOR_TOUCH = 0;
+ public static final int FACTOR_MOTION = 1;
+ public static final int FACTOR_TRACKBALL = 2;
+ public static final int FACTOR_NAV = 3;
+ public static final int FACTOR_MAJORNAV = 4;
+ public static final int FACTOR_SYSOPS = 5;
+ public static final int FACTOR_APPSWITCH = 6;
+ public static final int FACTOR_ANYTHING = 7;
+
+ public static final int FACTORZ_COUNT = 8; // should be last+1
+
+ float[] mFactors = new float[FACTORZ_COUNT];
+
+ private static final File TOMBSTONES_PATH = new File("/data/tombstones");
+ private HashSet<String> mTombstones = null;
+
+ /**
+ * 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;
+
+ // default values for random distributions
+ // note, these are straight percentages, to match user input (cmd line args)
+ // but they will be converted to 0..1 values before the main loop runs.
+ mFactors[FACTOR_TOUCH] = 15.0f;
+ mFactors[FACTOR_MOTION] = 10.0f;
+ mFactors[FACTOR_TRACKBALL] = 15.0f;
+ mFactors[FACTOR_NAV] = 25.0f;
+ mFactors[FACTOR_MAJORNAV] = 15.0f;
+ mFactors[FACTOR_SYSOPS] = 2.0f;
+ mFactors[FACTOR_APPSWITCH] = 2.0f;
+ mFactors[FACTOR_ANYTHING] = 16.0f;
+
+ // prepare for command-line processing
+ mArgs = args;
+ mNextArg = 0;
+ 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 (!adjustEventFactors()) {
+ return -5;
+ }
+
+ // Java's Random doesn't scramble well at all on seeding, so we'll use
+ // the better random source here.
+ SecureRandom random = new SecureRandom();
+ random.setSeed((mSeed == 0) ? -1 : mSeed);
+ if (mVerbose >= 2) { // check seeding performance
+ System.out.println("// Seeded: " + mSeed + " and pulling: " + random.nextFloat());
+ }
+
+ // If we're profiling, do it immediately before/after the main monkey loop
+ if (mGenerateHprof) {
+ signalPersistentProcesses();
+ }
+
+ int crashedAtCycle = runMonkeyCycles(random);
+
+ 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.println(mDroppedTrackballEvents);
+ }
+
+ if (crashedAtCycle < mCount) {
+ 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[FACTOR_TOUCH] = -nextOptionLong("touch events percentage");
+ } else if (opt.equals("--pct-motion")) {
+ mFactors[FACTOR_MOTION] = -nextOptionLong("motion events percentage");
+ } else if (opt.equals("--pct-trackball")) {
+ mFactors[FACTOR_TRACKBALL] = -nextOptionLong("trackball events percentage");
+ } else if (opt.equals("--pct-syskeys")) {
+ mFactors[FACTOR_SYSOPS] = -nextOptionLong("system key events percentage");
+ } else if (opt.equals("--pct-nav")) {
+ mFactors[FACTOR_NAV] = -nextOptionLong("nav events percentage");
+ } else if (opt.equals("--pct-majornav")) {
+ mFactors[FACTOR_MAJORNAV] = -nextOptionLong("major nav events percentage");
+ } else if (opt.equals("--pct-appswitch")) {
+ mFactors[FACTOR_APPSWITCH] = -nextOptionLong("app switch events percentage");
+ } else if (opt.equals("--pct-anyevent")) {
+ mFactors[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("-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 = KEY_NAMES[KeyEvent.MAX_KEYCODE+1];
+ } 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;
+ }
+
+ /**
+ * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
+ */
+ private boolean adjustEventFactors() {
+ // go through all values and compute totals for user & default values
+ float userSum = 0.0f;
+ float defaultSum = 0.0f;
+ int defaultCount = 0;
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ if (mFactors[i] <= 0.0f) { // user values are zero or negative
+ userSum -= mFactors[i];
+ } else {
+ defaultSum += mFactors[i];
+ ++defaultCount;
+ }
+ }
+
+ // if the user request was > 100%, reject it
+ if (userSum > 100.0f) {
+ System.err.println("** Event weights > 100%");
+ showUsage();
+ return false;
+ }
+
+ // if the user specified all of the weights, then they need to be 100%
+ if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
+ System.err.println("** Event weights != 100%");
+ showUsage();
+ return false;
+ }
+
+ // compute the adjustment necessary
+ float defaultsTarget = (100.0f - userSum);
+ float defaultsAdjustment = defaultsTarget / defaultSum;
+
+ // fix all values, by adjusting defaults, or flipping user values back to >0
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ if (mFactors[i] <= 0.0f) { // user values are zero or negative
+ mFactors[i] = -mFactors[i];
+ } else {
+ mFactors[i] *= defaultsAdjustment;
+ }
+ }
+
+ // if verbose, show factors
+ if (mVerbose > 0) {
+ System.out.println("// Event percentages:");
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ System.out.println("// " + i + ": " + mFactors[i] + "%");
+ }
+ }
+
+ // finally, normalize and convert to running sum
+ float sum = 0.0f;
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ sum += mFactors[i] / 100.0f;
+ mFactors[i] = sum;
+ }
+
+ return true;
+ }
+
+ /**
+ * Run mCount cycles and see if we hit any crashers.
+ *
+ * TODO: Meta state on keys
+ *
+ * @param random The random source to use
+ * @return Returns the last cycle which executed. If the value == mCount, no errors detected.
+ */
+ private int runMonkeyCycles(Random random) {
+ int i = 0;
+ int lastKey = 0;
+
+ boolean systemCrashed = false;
+
+ if (!startRandomActivity(random)) {
+ systemCrashed = true;
+ }
+
+ 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);
+ }
+
+ // if the last event was a keydown, then this event is a key-up
+ if (lastKey != 0) {
+ if (mVerbose > 1) {
+ try {
+ System.out.println(":SendKey (ACTION_UP):" + lastKey + " // " + KEY_NAMES[lastKey]);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println(":SendKey (ACTION_UP): " + lastKey + " // Unknown key event");
+ }
+ }
+ try {
+ if (! mWm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, lastKey), false)) {
+ mDroppedKeyEvents++;
+ }
+ } catch (RemoteException ex) {
+ systemCrashed = true;
+ break;
+ }
+ lastKey = 0;
+ i++;
+ continue;
+ }
+
+ // otherwise begin a new event cycle
+ float cls = random.nextFloat();
+
+ boolean touchEvent = cls < mFactors[FACTOR_TOUCH];
+ boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]);
+ if (touchEvent || motionEvent) {
+ try {
+ generateMotionEvent(random, motionEvent);
+ } catch (RemoteException ex) {
+ systemCrashed = true;
+ break;
+ }
+ i++;
+ continue;
+ }
+
+ if (cls < mFactors[FACTOR_TRACKBALL]) {
+ try {
+ generateTrackballEvent(random);
+ } catch (RemoteException ex) {
+ systemCrashed = true;
+ break;
+ }
+ i++;
+ continue;
+ }
+
+ // The remaining event categories are injected as key events
+ if (cls < mFactors[FACTOR_NAV]) {
+ lastKey = NAV_KEYS[random.nextInt(NAV_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_MAJORNAV]) {
+ lastKey = MAJOR_NAV_KEYS[random.nextInt(MAJOR_NAV_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_SYSOPS]) {
+ lastKey = SYS_KEYS[random.nextInt(SYS_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_APPSWITCH]) {
+ if (!startRandomActivity(random)) {
+ systemCrashed = true;
+ break;
+ }
+ i++;
+ continue;
+ } else {
+ lastKey = 1 + random.nextInt(KeyEvent.MAX_KEYCODE - 1);
+ }
+ if (mVerbose > 0) {
+ try {
+ System.out.println(":SendKey: " + lastKey + " // " + KEY_NAMES[lastKey]);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println(":SendKey: " + lastKey + " // Unknown key event");
+ }
+ }
+ try {
+ if (! mWm.injectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, lastKey), false)) {
+ mDroppedKeyEvents++;
+ }
+ } catch (RemoteException ex) {
+ systemCrashed = true;
+ break;
+ }
+ }
+
+ // If we got this far, we succeeded!
+ return mCount;
+ }
+
+ /**
+ * Generates a random motion event. This method counts a down, move, and up as one event.
+ *
+ * TODO: Test & fix the selectors when non-zero percentages
+ * TODO: Longpress.
+ * TODO: Fling.
+ * TODO: Meta state
+ * TODO: More useful than the random walk here would be to pick a single random direction
+ * and distance, and divvy it up into a random number of segments. (This would serve to
+ * generate fling gestures, which are important).
+ *
+ * @param random Random number source for positioning
+ * @param motionEvent If false, touch/release. If true, touch/move/release.
+ *
+ * @throws RemoteException
+ */
+ private void generateMotionEvent(Random random, boolean motionEvent) throws RemoteException {
+ Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+
+ float x = Math.abs(random.nextInt() % display.getWidth());
+ float y = Math.abs(random.nextInt() % display.getHeight());
+ long downAt = SystemClock.uptimeMillis();
+ boolean drop = false;
+ drop = sendMotionEvent(MotionEvent.ACTION_DOWN, x, y, downAt, "Pointer ACTION_DOWN", false,
+ true);
+
+ // sometimes we'll move during the touch
+ if (motionEvent) {
+ int count = random.nextInt(10);
+ for (int i = 0 ; i < count ; i++) {
+ // generate some slop in the up event
+ x = (x + (random.nextInt() % 10)) % display.getWidth();
+ y = (y + (random.nextInt() % 10)) % display.getHeight();
+ drop |= sendMotionEvent(MotionEvent.ACTION_MOVE, x, y, downAt,
+ "Pointer ACTION_MOVE", true, true);
+ }
+ }
+
+ // TODO generate some slop in the up event
+ drop |= sendMotionEvent(MotionEvent.ACTION_UP, x, y, downAt, "Pointer ACTION_UP", false,
+ true);
+
+ if (drop) {
+ mDroppedPointerEvents++;
+ }
+ }
+
+ /**
+ * Sends a single motion event, either as a pointer or a trackball.
+ *
+ * @param action Must be one of the ACTION values defined in {@link MotionEvent}
+ * @param x The position, or movement, in the X axis
+ * @param y The position, or movement, in the Y axis
+ * @param downAt The time of the first DOWN must be sent here, and the same value must
+ * be sent for all subsequent events that are related (through the eventual UP event), or
+ * -1 to simply send the current time as the downTime.
+ * @param note This will be displayed when in verbose mode
+ * @param intermediateNote If true, this is an intermediate step (more verbose logging, only)
+ * @param isPointer Use true to send a pointer event, and false to send a trackball event
+ *
+ * @return Returns false if event was dispatched, true if it was dropped for any reason
+ *
+ * @throws RemoteException
+ */
+ private boolean sendMotionEvent(int action, float x, float y, long downAt, final String note,
+ boolean intermediateNote, boolean isPointer) throws RemoteException {
+ if ((mVerbose > 0 && !intermediateNote) || mVerbose > 1) {
+ System.out.println(":Sending " + note + " x=" + x + " y=" + y);
+ }
+ long eventTime = SystemClock.uptimeMillis();
+ if (downAt == -1) {
+ downAt = eventTime;
+ }
+ final MotionEvent evt = MotionEvent.obtain(downAt, eventTime, action, x, y, 0);
+ if (isPointer) {
+ return ! mWm.injectPointerEvent(evt, false);
+ } else {
+ return ! mWm.injectTrackballEvent(evt, false);
+ }
+ }
+
+ /**
+ * Generates a random trackball event. This consists of a sequence of small moves, followed by
+ * an optional single click.
+ *
+ * TODO: Longpress.
+ * TODO: Meta state
+ * TODO: Parameterize the % clicked
+ * TODO: More useful than the random walk here would be to pick a single random direction
+ * and distance, and divvy it up into a random number of segments. (This would serve to
+ * generate fling gestures, which are important).
+ *
+ * @param random Random number source for positioning
+ *
+ * @throws RemoteException
+ */
+ private void generateTrackballEvent(Random random) throws RemoteException {
+ Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+
+ boolean drop = false;
+ int count = random.nextInt(10);
+ for (int i = 0; i < 10; ++i) {
+ // generate a small random step
+ int dX = random.nextInt(10) - 5;
+ int dY = random.nextInt(10) - 5;
+ drop |= sendMotionEvent(MotionEvent.ACTION_MOVE, dX, dY, -1, "Trackball ACTION_MOVE",
+ (i > 0), false);
+ }
+
+ // 10% of trackball moves end with a click
+ if (0 == random.nextInt(10)) {
+ long downAt = SystemClock.uptimeMillis();
+ drop |= sendMotionEvent(MotionEvent.ACTION_DOWN, 0, 0, downAt, "Trackball ACTION_DOWN",
+ true, false);
+ drop |= sendMotionEvent(MotionEvent.ACTION_UP, 0, 0, downAt, "Trackball ACTION_UP",
+ false, false);
+ }
+
+ if (drop) {
+ mDroppedTrackballEvents++;
+ }
+ }
+
+ /**
+ * Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports
+ * in data/misc, so use with care.
+ */
+ 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) {
+ }
+ }
+
+ /**
+ * Have the activity manager start a new activity.
+ *
+ * @param random Random number source
+ *
+ * @return Returns true on success, false if there was an error calling
+ * the activity manager.
+ */
+ private boolean startRandomActivity(Random random) {
+ int numApps = mMainApps.size();
+ int which = random.nextInt(numApps);
+ ComponentName app = mMainApps.get(which);
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(app);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ if (mVerbose > 0) {
+ System.out.println(":Switch: " + intent.toURI());
+ }
+ try {
+ mAm.startActivity(null, intent, null, null, 0, null, null, 0, false, false);
+ } catch (RemoteException e) {
+ System.err.println("** Failed talking with activity manager!");
+ return false;
+ } catch (SecurityException e) {
+ if (mVerbose > 0) {
+ System.out.println("** Permissions error starting activity " + intent.toURI());
+ }
+ return mIgnoreSecurityExceptions; // true = "launched ok" (pretend)
+ }
+ return true;
+ }
+
+ /**
+ * Watch for appearance of new tombstone files, which indicate native crashes.
+ *
+ * @return Returns true if new files have appeared in the list
+ */
+ 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-anyevent PERCENT]");
+ System.err.println(" [--wait-dbg] [--dbg-no-events]");
+ System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]");
+ System.err.println(" COUNT");
+ }
+}