summaryrefslogtreecommitdiffstats
path: root/cmds
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98 (patch)
tree2eb01432fa3da41550dd622d29fb672509122b51 /cmds
parentd4aee0c0caa00aa02d4c50ed28151591ac0456b5 (diff)
downloadandroid_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.gz
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.bz2
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.zip
auto import from //depot/cupcake/@135843
Diffstat (limited to 'cmds')
-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.java906
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java68
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java61
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java41
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java76
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java125
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java156
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java467
-rw-r--r--cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java427
13 files changed, 2537 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..f6ab19d73
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/Monkey.java
@@ -0,0 +1,906 @@
+/**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+
+package com.android.commands.monkey;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IActivityWatcher;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Debug;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.server.data.CrashData;
+import android.view.IWindowManager;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Application that injects random key events and other actions into the system.
+ */
+public class Monkey {
+
+ /**
+ * Monkey Debugging/Dev Support
+ *
+ * All values should be zero when checking in.
+ */
+ private final static int DEBUG_ALLOW_ANY_STARTS = 0;
+ private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;
+ private IActivityManager mAm;
+ private IWindowManager mWm;
+ private IPackageManager mPm;
+
+ /** Command line arguments */
+ private String[] mArgs;
+ /** Current argument being parsed */
+ private int mNextArg;
+ /** Data of current argument */
+ private String mCurArgData;
+
+ /** Running in verbose output mode? 1= verbose, 2=very verbose */
+ private int mVerbose;
+
+ /** Ignore any application crashes while running? */
+ private boolean mIgnoreCrashes;
+
+ /** Ignore any not responding timeouts while running? */
+ private boolean mIgnoreTimeouts;
+
+ /** Ignore security exceptions when launching activities */
+ /** (The activity launch still fails, but we keep pluggin' away) */
+ private boolean mIgnoreSecurityExceptions;
+
+ /** Monitor /data/tombstones and stop the monkey if new files appear. */
+ private boolean mMonitorNativeCrashes;
+
+ /** Send no events. Use with long throttle-time to watch user operations */
+ private boolean mSendNoEvents;
+
+ /** This is set when we would like to abort the running of the monkey. */
+ private boolean mAbort;
+
+ /** This is set by the ActivityWatcher thread to request collection of ANR trace files */
+ private boolean mRequestAnrTraces = false;
+
+ /** This is set by the ActivityWatcher thread to request a "dumpsys meminfo" */
+ private boolean mRequestDumpsysMemInfo = false;
+
+ /** Kill the process after a timeout or crash. */
+ private boolean mKillProcessAfterError;
+
+ /** Generate hprof reports before/after monkey runs */
+ private boolean mGenerateHprof;
+
+ /** Packages we are allowed to run, or empty if no restriction. */
+ private HashSet<String> mValidPackages = new HashSet<String>();
+ /** Categories we are allowed to launch **/
+ ArrayList<String> mMainCategories = new ArrayList<String>();
+ /** Applications we can switch to. */
+ private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
+
+ /** The delay between event inputs **/
+ long mThrottle = 0;
+
+ /** The number of iterations **/
+ int mCount = 1000;
+
+ /** The random number seed **/
+ long mSeed = 0;
+
+ /** Dropped-event statistics **/
+ long mDroppedKeyEvents = 0;
+ long mDroppedPointerEvents = 0;
+ long mDroppedTrackballEvents = 0;
+ long mDroppedFlipEvents = 0;
+
+ /** a filename to the script (if any) **/
+ private String mScriptFileName = null;
+
+ private static final File TOMBSTONES_PATH = new File("/data/tombstones");
+ private HashSet<String> mTombstones = null;
+
+ float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
+ MonkeyEventSource mEventSource;
+
+ /**
+ * Monitor operations happening in the system.
+ */
+ private class ActivityWatcher extends IActivityWatcher.Stub {
+ public boolean activityStarting(Intent intent, String pkg) {
+ boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
+ if (mVerbose > 0) {
+ System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
+ + " start of " + intent + " in package " + pkg);
+ }
+ return allow;
+ }
+
+ public boolean activityResuming(String pkg) {
+ System.out.println(" // activityResuming(" + pkg + ")");
+ boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0);
+ if (!allow) {
+ if (mVerbose > 0) {
+ System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
+ + " resume of package " + pkg);
+ }
+ }
+ return allow;
+ }
+
+ private boolean checkEnteringPackage(String pkg) {
+ if (pkg == null) {
+ return true;
+ }
+ // preflight the hash lookup to avoid the cost of hash key generation
+ if (mValidPackages.size() == 0) {
+ return true;
+ } else {
+ return mValidPackages.contains(pkg);
+ }
+ }
+
+ public boolean appCrashed(String processName, int pid, String shortMsg,
+ String longMsg, byte[] crashData) {
+ System.err.println("// CRASH: " + processName + " (pid " + pid
+ + ")");
+ System.err.println("// Short Msg: " + shortMsg);
+ System.err.println("// Long Msg: " + longMsg);
+ if (crashData != null) {
+ try {
+ CrashData cd = new CrashData(new DataInputStream(
+ new ByteArrayInputStream(crashData)));
+ System.err.println("// Build Label: "
+ + cd.getBuildData().getFingerprint());
+ System.err.println("// Build Changelist: "
+ + cd.getBuildData().getIncrementalVersion());
+ System.err.println("// Build Time: "
+ + cd.getBuildData().getTime());
+ System.err.println("// ID: " + cd.getId());
+ System.err.println("// Tag: " + cd.getActivity());
+ System.err.println(cd.getThrowableData().toString(
+ "// "));
+ } catch (IOException e) {
+ System.err.println("// BAD STACK CRAWL");
+ }
+ }
+
+ if (!mIgnoreCrashes) {
+ synchronized (Monkey.this) {
+ mAbort = true;
+ }
+
+ return !mKillProcessAfterError;
+ }
+ return false;
+ }
+
+ public int appNotResponding(String processName, int pid,
+ String processStats) {
+ System.err.println("// NOT RESPONDING: " + processName
+ + " (pid " + pid + ")");
+ System.err.println(processStats);
+ reportProcRank();
+ synchronized (Monkey.this) {
+ mRequestAnrTraces = true;
+ mRequestDumpsysMemInfo = true;
+ }
+ if (!mIgnoreTimeouts) {
+ synchronized (Monkey.this) {
+ mAbort = true;
+ }
+ return (mKillProcessAfterError) ? -1 : 1;
+ }
+ return 1;
+ }
+ }
+
+ /**
+ * Run the procrank tool to insert system status information into the debug report.
+ */
+ private void reportProcRank() {
+ commandLineReport("procrank", "procrank");
+ }
+
+ /**
+ * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous
+ * report writing complete.
+ */
+ private void reportAnrTraces() {
+ try {
+ Thread.sleep(5 * 1000);
+ } catch (InterruptedException e) {
+ }
+ commandLineReport("anr traces", "cat /data/anr/traces.txt");
+ }
+
+ /**
+ * Run "dumpsys meminfo"
+ *
+ * NOTE: You cannot perform a dumpsys call from the ActivityWatcher callback, as it will
+ * deadlock. This should only be called from the main loop of the monkey.
+ */
+ private void reportDumpsysMemInfo() {
+ commandLineReport("meminfo", "dumpsys meminfo");
+ }
+
+ /**
+ * Print report from a single command line.
+ * @param reportName Simple tag that will print before the report and in various annotations.
+ * @param command Command line to execute.
+ * TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both streams (might be
+ * important for some command lines)
+ */
+ private void commandLineReport(String reportName, String command) {
+ System.err.println(reportName + ":");
+ Runtime rt = Runtime.getRuntime();
+ try {
+ // Process must be fully qualified here because android.os.Process is used elsewhere
+ java.lang.Process p = Runtime.getRuntime().exec(command);
+
+ // pipe everything from process stdout -> System.err
+ InputStream inStream = p.getInputStream();
+ InputStreamReader inReader = new InputStreamReader(inStream);
+ BufferedReader inBuffer = new BufferedReader(inReader);
+ String s;
+ while ((s = inBuffer.readLine()) != null) {
+ System.err.println(s);
+ }
+
+ int status = p.waitFor();
+ System.err.println("// " + reportName + " status was " + status);
+ } catch (Exception e) {
+ System.err.println("// Exception from " + reportName + ":");
+ System.err.println(e.toString());
+ }
+ }
+
+ /**
+ * Command-line entry point.
+ *
+ * @param args The command-line arguments
+ */
+ public static void main(String[] args) {
+ int resultCode = (new Monkey()).run(args);
+ System.exit(resultCode);
+ }
+
+ /**
+ * Run the command!
+ *
+ * @param args The command-line arguments
+ * @return Returns a posix-style result code. 0 for no error.
+ */
+ private int run(String[] args) {
+ // Super-early debugger wait
+ for (String s : args) {
+ if ("--wait-dbg".equals(s)) {
+ Debug.waitForDebugger();
+ }
+ }
+
+ // Default values for some command-line options
+ mVerbose = 0;
+ mCount = 1000;
+ mSeed = 0;
+ mThrottle = 0;
+
+ // prepare for command-line processing
+ mArgs = args;
+ mNextArg = 0;
+
+ //set a positive value, indicating none of the factors is provided yet
+ for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
+ mFactors[i] = 1.0f;
+ }
+
+ if (!processOptions()) {
+ return -1;
+ }
+
+ // now set up additional data in preparation for launch
+ if (mMainCategories.size() == 0) {
+ mMainCategories.add(Intent.CATEGORY_LAUNCHER);
+ mMainCategories.add(Intent.CATEGORY_MONKEY);
+ }
+
+ if (mVerbose > 0) {
+ System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
+ if (mValidPackages.size() > 0) {
+ Iterator<String> it = mValidPackages.iterator();
+ while (it.hasNext()) {
+ System.out.println(":AllowPackage: " + it.next());
+ }
+ }
+ if (mMainCategories.size() != 0) {
+ Iterator<String> it = mMainCategories.iterator();
+ while (it.hasNext()) {
+ System.out.println(":IncludeCategory: " + it.next());
+ }
+ }
+ }
+
+ if (!checkInternalConfiguration()) {
+ return -2;
+ }
+
+ if (!getSystemInterfaces()) {
+ return -3;
+ }
+
+ if (!getMainApps()) {
+ return -4;
+ }
+
+ if (mScriptFileName != null) {
+ // script mode, ignore other options
+ mEventSource = new MonkeySourceScript(mScriptFileName);
+ mEventSource.setVerbose(mVerbose);
+ } else {
+ // random source by default
+ if (mVerbose >= 2) { // check seeding performance
+ System.out.println("// Seeded: " + mSeed);
+ }
+ mEventSource = new MonkeySourceRandom(mSeed, mMainApps);
+ mEventSource.setVerbose(mVerbose);
+ //set any of the factors that has been set
+ for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
+ if (mFactors[i] <= 0.0f) {
+ ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
+ }
+ }
+
+ //in random mode, we start with a random activity
+ ((MonkeySourceRandom) mEventSource).generateActivity();
+ }
+
+ //validate source generator
+ if (!mEventSource.validate()) {
+ return -5;
+ }
+
+ if (mScriptFileName != null) {
+ // in random mode, count is the number of single events
+ // while in script mode, count is the number of repetition
+ // for a sequence of events, so we need do multiply the length of
+ // that sequence
+ mCount = mCount * ((MonkeySourceScript) mEventSource)
+ .getOneRoundEventCount();
+ }
+
+ // If we're profiling, do it immediately before/after the main monkey loop
+ if (mGenerateHprof) {
+ signalPersistentProcesses();
+ }
+
+ int crashedAtCycle = runMonkeyCycles();
+
+ synchronized (this) {
+ if (mRequestAnrTraces) {
+ reportAnrTraces();
+ mRequestAnrTraces = false;
+ }
+ if (mRequestDumpsysMemInfo) {
+ reportDumpsysMemInfo();
+ mRequestDumpsysMemInfo = false;
+ }
+ }
+
+ if (mGenerateHprof) {
+ signalPersistentProcesses();
+ if (mVerbose > 0) {
+ System.out.println("// Generated profiling reports in /data/misc");
+ }
+ }
+
+ try {
+ mAm.setActivityWatcher(null);
+ } catch (RemoteException e) {
+ // just in case this was latent (after mCount cycles), make sure
+ // we report it
+ if (crashedAtCycle >= mCount) {
+ crashedAtCycle = mCount - 1;
+ }
+ }
+
+ // report dropped event stats
+ if (mVerbose > 0) {
+ System.out.print(":Dropped: keys=");
+ System.out.print(mDroppedKeyEvents);
+ System.out.print(" pointers=");
+ System.out.print(mDroppedPointerEvents);
+ System.out.print(" trackballs=");
+ System.out.print(mDroppedTrackballEvents);
+ System.out.print(" flips=");
+ System.out.println(mDroppedFlipEvents);
+ }
+
+ if (crashedAtCycle < mCount - 1) {
+ System.err.println("** System appears to have crashed at event "
+ + crashedAtCycle + " of " + mCount + " using seed " + mSeed);
+ return crashedAtCycle;
+ } else {
+ if (mVerbose > 0) {
+ System.out.println("// Monkey finished");
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * Process the command-line options
+ *
+ * @return Returns true if options were parsed with no apparent errors.
+ */
+ private boolean processOptions() {
+ // quick (throwaway) check for unadorned command
+ if (mArgs.length < 1) {
+ showUsage();
+ return false;
+ }
+
+ try {
+ String opt;
+ while ((opt = nextOption()) != null) {
+ if (opt.equals("-s")) {
+ mSeed = nextOptionLong("Seed");
+ } else if (opt.equals("-p")) {
+ mValidPackages.add(nextOptionData());
+ } else if (opt.equals("-c")) {
+ mMainCategories.add(nextOptionData());
+ } else if (opt.equals("-v")) {
+ mVerbose += 1;
+ } else if (opt.equals("--ignore-crashes")) {
+ mIgnoreCrashes = true;
+ } else if (opt.equals("--ignore-timeouts")) {
+ mIgnoreTimeouts = true;
+ } else if (opt.equals("--ignore-security-exceptions")) {
+ mIgnoreSecurityExceptions = true;
+ } else if (opt.equals("--monitor-native-crashes")) {
+ mMonitorNativeCrashes = true;
+ } else if (opt.equals("--kill-process-after-error")) {
+ mKillProcessAfterError = true;
+ } else if (opt.equals("--hprof")) {
+ mGenerateHprof = true;
+ } else if (opt.equals("--pct-touch")) {
+ mFactors[MonkeySourceRandom.FACTOR_TOUCH] =
+ -nextOptionLong("touch events percentage");
+ } else if (opt.equals("--pct-motion")) {
+ mFactors[MonkeySourceRandom.FACTOR_MOTION] =
+ -nextOptionLong("motion events percentage");
+ } else if (opt.equals("--pct-trackball")) {
+ mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] =
+ -nextOptionLong("trackball events percentage");
+ } else if (opt.equals("--pct-nav")) {
+ mFactors[MonkeySourceRandom.FACTOR_NAV] =
+ -nextOptionLong("nav events percentage");
+ } else if (opt.equals("--pct-majornav")) {
+ mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] =
+ -nextOptionLong("major nav events percentage");
+ } else if (opt.equals("--pct-appswitch")) {
+ mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] =
+ -nextOptionLong("app switch events percentage");
+ } else if (opt.equals("--pct-flip")) {
+ mFactors[MonkeySourceRandom.FACTOR_FLIP] =
+ -nextOptionLong("keyboard flip percentage");
+ } else if (opt.equals("--pct-anyevent")) {
+ mFactors[MonkeySourceRandom.FACTOR_ANYTHING] =
+ -nextOptionLong("any events percentage");
+ } else if (opt.equals("--throttle")) {
+ mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
+ } else if (opt.equals("--wait-dbg")) {
+ // do nothing - it's caught at the very start of run()
+ } else if (opt.equals("--dbg-no-events")) {
+ mSendNoEvents = true;
+ } else if (opt.equals("-f")) {
+ mScriptFileName = nextOptionData();
+ } else if (opt.equals("-h")) {
+ showUsage();
+ return false;
+ } else {
+ System.err.println("** Error: Unknown option: " + opt);
+ showUsage();
+ return false;
+ }
+ }
+ } catch (RuntimeException ex) {
+ System.err.println("** Error: " + ex.toString());
+ showUsage();
+ return false;
+ }
+
+ String countStr = nextArg();
+ if (countStr == null) {
+ System.err.println("** Error: Count not specified");
+ showUsage();
+ return false;
+ }
+
+ try {
+ mCount = Integer.parseInt(countStr);
+ } catch (NumberFormatException e) {
+ System.err.println("** Error: Count is not a number");
+ showUsage();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check for any internal configuration (primarily build-time) errors.
+ *
+ * @return Returns true if ready to rock.
+ */
+ private boolean checkInternalConfiguration() {
+ // Check KEYCODE name array, make sure it's up to date.
+
+ String lastKeyName = null;
+ try {
+ lastKeyName = MonkeySourceRandom.getLastKeyName();
+ } catch (RuntimeException e) {
+ }
+ if (!"TAG_LAST_KEYCODE".equals(lastKeyName)) {
+ System.err.println("** Error: Key names array malformed (internal error).");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Attach to the required system interfaces.
+ *
+ * @return Returns true if all system interfaces were available.
+ */
+ private boolean getSystemInterfaces() {
+ mAm = ActivityManagerNative.getDefault();
+ if (mAm == null) {
+ System.err.println("** Error: Unable to connect to activity manager; is the system running?");
+ return false;
+ }
+
+ mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ if (mWm == null) {
+ System.err.println("** Error: Unable to connect to window manager; is the system running?");
+ return false;
+ }
+
+ mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ if (mPm == null) {
+ System.err.println("** Error: Unable to connect to package manager; is the system running?");
+ return false;
+ }
+
+ try {
+ mAm.setActivityWatcher(new ActivityWatcher());
+ } catch (RemoteException e) {
+ System.err.println("** Failed talking with activity manager!");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Using the restrictions provided (categories & packages), generate a list of activities
+ * that we can actually switch to.
+ *
+ * @return Returns true if it could successfully build a list of target activities
+ */
+ private boolean getMainApps() {
+ try {
+ final int N = mMainCategories.size();
+ for (int i = 0; i< N; i++) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ String category = mMainCategories.get(i);
+ if (category.length() > 0) {
+ intent.addCategory(category);
+ }
+ List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0);
+ if (mainApps == null || mainApps.size() == 0) {
+ System.err.println("// Warning: no activities found for category " + category);
+ continue;
+ }
+ if (mVerbose >= 2) { // very verbose
+ System.out.println("// Selecting main activities from category " + category);
+ }
+ final int NA = mainApps.size();
+ for (int a = 0; a < NA; a++) {
+ ResolveInfo r = mainApps.get(a);
+ if (mValidPackages.size() == 0 ||
+ mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
+ if (mVerbose >= 2) { // very verbose
+ System.out.println("// + Using main activity "
+ + r.activityInfo.name
+ + " (from package "
+ + r.activityInfo.applicationInfo.packageName
+ + ")");
+ }
+ mMainApps.add(new ComponentName(
+ r.activityInfo.applicationInfo.packageName,
+ r.activityInfo.name));
+ } else {
+ if (mVerbose >= 3) { // very very verbose
+ System.out.println("// - NOT USING main activity "
+ + r.activityInfo.name
+ + " (from package "
+ + r.activityInfo.applicationInfo.packageName
+ + ")");
+ }
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ System.err.println("** Failed talking with package manager!");
+ return false;
+ }
+
+ if (mMainApps.size() == 0) {
+ System.out.println("** No activities found to run, monkey aborted.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Run mCount cycles and see if we hit any crashers.
+ *
+ * TODO: Meta state on keys
+ *
+ * @return Returns the last cycle which executed. If the value == mCount, no errors detected.
+ */
+ private int runMonkeyCycles() {
+ int i = 0;
+ int lastKey = 0;
+
+ boolean systemCrashed = false;
+
+ while (!systemCrashed && i < mCount) {
+ synchronized (this) {
+ if (mRequestAnrTraces) {
+ reportAnrTraces();
+ mRequestAnrTraces = false;
+ }
+ if (mRequestDumpsysMemInfo) {
+ reportDumpsysMemInfo();
+ mRequestDumpsysMemInfo = false;
+ }
+ if (mMonitorNativeCrashes) {
+ // first time through, when i == 0, just set up the watcher (ignore the error)
+ if (checkNativeCrashes() && (i > 0)) {
+ System.out.println("** New native crash detected.");
+ mAbort = mAbort || mKillProcessAfterError;
+ }
+ }
+ if (mAbort) {
+ System.out.println("** Monkey aborted due to error.");
+ System.out.println("Events injected: " + i);
+ return i;
+ }
+ }
+
+ try {
+ Thread.sleep(mThrottle);
+ } catch (InterruptedException e1) {
+ System.out.println("** Monkey interrupted in sleep.");
+ return i;
+ }
+
+ // In this debugging mode, we never send any events. This is primarily
+ // here so you can manually test the package or category limits, while manually
+ // exercising the system.
+ if (mSendNoEvents) {
+ i++;
+ continue;
+ }
+
+ if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) {
+ System.out.println(" // Sending event #" + i);
+ }
+
+ MonkeyEvent ev = mEventSource.getNextEvent();
+ if (ev != null) {
+ i++;
+ int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
+ if (injectCode == MonkeyEvent.INJECT_FAIL) {
+ if (ev instanceof MonkeyKeyEvent) {
+ mDroppedKeyEvents++;
+ } else if (ev instanceof MonkeyMotionEvent) {
+ mDroppedPointerEvents++;
+ } else if (ev instanceof MonkeyFlipEvent) {
+ mDroppedFlipEvents++;
+ }
+ } else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
+ systemCrashed = true;
+ } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
+ systemCrashed = !mIgnoreSecurityExceptions;
+ }
+ }
+ }
+
+ // If we got this far, we succeeded!
+ return mCount;
+ }
+
+ /**
+ * Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports
+ * in data/misc, so use with care.
+ */
+ private void signalPersistentProcesses() {
+ try {
+ mAm.signalPersistentProcesses(Process.SIGNAL_USR1);
+
+ synchronized (this) {
+ wait(2000);
+ }
+ } catch (RemoteException e) {
+ System.err.println("** Failed talking with activity manager!");
+ } catch (InterruptedException e) {
+ }
+ }
+
+ /**
+ * Watch for appearance of new tombstone files, which indicate native crashes.
+ *
+ * @return Returns true if new files have appeared in the list
+ */
+ private boolean checkNativeCrashes() {
+ String[] tombstones = TOMBSTONES_PATH.list();
+
+ // shortcut path for usually empty directory, so we don't waste even more objects
+ if ((tombstones == null) || (tombstones.length == 0)) {
+ mTombstones = null;
+ return false;
+ }
+
+ // use set logic to look for new files
+ HashSet<String> newStones = new HashSet<String>();
+ for (String x : tombstones) {
+ newStones.add(x);
+ }
+
+ boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones);
+
+ // keep the new list for the next time
+ mTombstones = newStones;
+
+ return result;
+ }
+
+ /**
+ * Return the next command line option. This has a number of special cases which
+ * closely, but not exactly, follow the POSIX command line options patterns:
+ *
+ * -- means to stop processing additional options
+ * -z means option z
+ * -z ARGS means option z with (non-optional) arguments ARGS
+ * -zARGS means option z with (optional) arguments ARGS
+ * --zz means option zz
+ * --zz ARGS means option zz with (non-optional) arguments ARGS
+ *
+ * Note that you cannot combine single letter options; -abc != -a -b -c
+ *
+ * @return Returns the option string, or null if there are no more options.
+ */
+ private String nextOption() {
+ if (mNextArg >= mArgs.length) {
+ return null;
+ }
+ String arg = mArgs[mNextArg];
+ if (!arg.startsWith("-")) {
+ return null;
+ }
+ mNextArg++;
+ if (arg.equals("--")) {
+ return null;
+ }
+ if (arg.length() > 1 && arg.charAt(1) != '-') {
+ if (arg.length() > 2) {
+ mCurArgData = arg.substring(2);
+ return arg.substring(0, 2);
+ } else {
+ mCurArgData = null;
+ return arg;
+ }
+ }
+ mCurArgData = null;
+ return arg;
+ }
+
+ /**
+ * Return the next data associated with the current option.
+ *
+ * @return Returns the data string, or null of there are no more arguments.
+ */
+ private String nextOptionData() {
+ if (mCurArgData != null) {
+ return mCurArgData;
+ }
+ if (mNextArg >= mArgs.length) {
+ return null;
+ }
+ String data = mArgs[mNextArg];
+ mNextArg++;
+ return data;
+ }
+
+ /**
+ * Returns a long converted from the next data argument, with error handling if not available.
+ *
+ * @param opt The name of the option.
+ * @return Returns a long converted from the argument.
+ */
+ private long nextOptionLong(final String opt) {
+ long result;
+ try {
+ result = Long.parseLong(nextOptionData());
+ } catch (NumberFormatException e) {
+ System.err.println("** Error: " + opt + " is not a number");
+ throw e;
+ }
+ return result;
+ }
+
+ /**
+ * Return the next argument on the command line.
+ *
+ * @return Returns the argument string, or null if we have reached the end.
+ */
+ private String nextArg() {
+ if (mNextArg >= mArgs.length) {
+ return null;
+ }
+ String arg = mArgs[mNextArg];
+ mNextArg++;
+ return arg;
+ }
+
+ /**
+ * Print how to use this command.
+ */
+ private void showUsage() {
+ System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]");
+ System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]");
+ System.err.println(" [--ignore-crashes] [--ignore-timeouts]");
+ System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]");
+ System.err.println(" [--kill-process-after-error] [--hprof]");
+ System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]");
+ System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]");
+ System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]");
+ System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]");
+ System.err.println(" [--pct-anyevent PERCENT]");
+ System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]");
+ System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]");
+ System.err.println(" COUNT");
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java
new file mode 100644
index 000000000..68e6e6d7f
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyActivityEvent.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.app.IActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+/**
+ * monkey activity event
+ */
+public class MonkeyActivityEvent extends MonkeyEvent {
+ private ComponentName mApp;
+
+ public MonkeyActivityEvent(ComponentName app) {
+ super(EVENT_TYPE_ACTIVITY);
+ mApp = app;
+ }
+
+ /**
+ * @return Intent for the new activity
+ */
+ private Intent getEvent() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(mApp);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
+ Intent intent = getEvent();
+ if (verbose > 0) {
+ System.out.println(":Switch: " + intent.toURI());
+ }
+ try {
+ iam.startActivity(null, intent, null, null, 0, null, null, 0,
+ false, false);
+ } catch (RemoteException e) {
+ System.err.println("** Failed talking with activity manager!");
+ return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
+ } catch (SecurityException e) {
+ if (verbose > 0) {
+ System.out.println("** Permissions error starting activity "
+ + intent.toURI());
+ }
+ return MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION;
+ }
+ return MonkeyEvent.INJECT_SUCCESS;
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java
new file mode 100644
index 000000000..ff99f5f06
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEvent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.app.IActivityManager;
+import android.view.IWindowManager;
+
+/**
+ * abstract class for monkey event
+ */
+public abstract class MonkeyEvent {
+ protected int eventType;
+ public static final int EVENT_TYPE_KEY = 0;
+ public static final int EVENT_TYPE_POINTER = 1;
+ public static final int EVENT_TYPE_TRACKBALL = 2;
+ public static final int EVENT_TYPE_ACTIVITY = 3;
+ public static final int EVENT_TYPE_FLIP = 4; // Keyboard flip
+
+ public static final int INJECT_SUCCESS = 1;
+ public static final int INJECT_FAIL = 0;
+
+ // error code for remote exception during injection
+ public static final int INJECT_ERROR_REMOTE_EXCEPTION = -1;
+ // error code for security exception during injection
+ public static final int INJECT_ERROR_SECURITY_EXCEPTION = -2;
+
+ public MonkeyEvent(int type) {
+ eventType = type;
+ }
+
+ /**
+ * @return event type
+ */
+ public int getEventType() {
+ return eventType;
+ }
+
+ /**
+ * a method for injecting event
+ * @param iwm wires to current window manager
+ * @param iam wires to current activity manager
+ * @param verbose a log switch
+ * @return INJECT_SUCCESS if it goes through, and INJECT_FAIL if it fails
+ * in the case of exceptions, return its corresponding error code
+ */
+ public abstract int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose);
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java
new file mode 100644
index 000000000..a236554a5
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyEventSource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+/**
+ * event source interface
+ */
+public interface MonkeyEventSource {
+ /**
+ *
+ * @return the next monkey event from the source
+ */
+ public MonkeyEvent getNextEvent();
+
+ /**
+ * set verbose to allow different level of log
+ * @param verbose output mode? 1= verbose, 2=very verbose
+ */
+ public void setVerbose(int verbose);
+
+ /**
+ * check whether precondition is satisfied
+ * @return false if something fails, e.g. factor failure in random source
+ * or file can not open from script source etc
+ */
+ public boolean validate();
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java
new file mode 100644
index 000000000..08fbedbc3
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyFlipEvent.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.app.IActivityManager;
+import android.view.IWindowManager;
+/**
+ * monkey keyboard flip event
+ */
+public class MonkeyFlipEvent extends MonkeyEvent {
+
+ // Raw keyboard flip event data
+ // Works on emulator and dream
+
+ private static final byte[] FLIP_0 = {
+ 0x7f, 0x06,
+ 0x00, 0x00,
+ (byte) 0xe0, 0x39,
+ 0x01, 0x00,
+ 0x05, 0x00,
+ 0x00, 0x00,
+ 0x01, 0x00,
+ 0x00, 0x00 };
+
+ private static final byte[] FLIP_1 = {
+ (byte) 0x85, 0x06,
+ 0x00, 0x00,
+ (byte) 0x9f, (byte) 0xa5,
+ 0x0c, 0x00,
+ 0x05, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00 };
+
+ private final boolean mKeyboardOpen;
+
+ public MonkeyFlipEvent(boolean keyboardOpen) {
+ super(EVENT_TYPE_FLIP);
+ mKeyboardOpen = keyboardOpen;
+ }
+
+ @Override
+ public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
+ if (verbose > 0) {
+ System.out.println(":Sending Flip keyboardOpen=" + mKeyboardOpen);
+ }
+
+ // inject flip event
+ try {
+ FileOutputStream f = new FileOutputStream("/dev/input/event0");
+ f.write(mKeyboardOpen ? FLIP_0 : FLIP_1);
+ f.close();
+ return MonkeyEvent.INJECT_SUCCESS;
+ } catch (IOException e) {
+ System.out.println("Got IOException performing flip" + e);
+ return MonkeyEvent.INJECT_FAIL;
+ }
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java
new file mode 100644
index 000000000..c1e0ffce7
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyKeyEvent.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.app.IActivityManager;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+/**
+ * monkey key event
+ */
+public class MonkeyKeyEvent extends MonkeyEvent {
+ private long mDownTime = -1;
+ private int mMetaState = -1;
+ private int mAction = -1;
+ private int mKeyCode = -1;
+ private int mScancode = -1;
+ private int mRepeatCount = -1;
+ private int mDeviceId = -1;
+ private long mEventTime = -1;
+
+ public MonkeyKeyEvent(int action, int keycode) {
+ super(EVENT_TYPE_KEY);
+ mAction = action;
+ mKeyCode = keycode;
+ }
+
+ public MonkeyKeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int device, int scancode) {
+ super(EVENT_TYPE_KEY);
+
+ mAction = action;
+ mKeyCode = code;
+ mMetaState = metaState;
+ mScancode = scancode;
+ mRepeatCount = repeat;
+ mDeviceId = device;
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ }
+
+ public int getKeyCode() {
+ return mKeyCode;
+ }
+
+ public int getAction() {
+ return mAction;
+ }
+
+ public long getDownTime() {
+ return mDownTime;
+ }
+
+ public long getEventTime() {
+ return mEventTime;
+ }
+
+ public void setDownTime(long downTime) {
+ mDownTime = downTime;
+ }
+
+ public void setEventTime(long eventTime) {
+ mEventTime = eventTime;
+ }
+
+ /**
+ * @return the key event
+ */
+ private KeyEvent getEvent() {
+ if (mDeviceId < 0) {
+ return new KeyEvent(mAction, mKeyCode);
+ }
+
+ // for scripts
+ return new KeyEvent(mDownTime, mEventTime, mAction,
+ mKeyCode, mRepeatCount, mMetaState, mDeviceId, mScancode);
+ }
+
+ @Override
+ public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
+ if (verbose > 1) {
+ String note;
+ if (mAction == KeyEvent.ACTION_UP) {
+ note = "ACTION_UP";
+ } else {
+ note = "ACTION_DOWN";
+ }
+
+ try {
+ System.out.println(":SendKey (" + note + "): "
+ + mKeyCode + " // "
+ + MonkeySourceRandom.getKeyName(mKeyCode));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println(":SendKey (ACTION_UP): "
+ + mKeyCode + " // Unknown key event");
+ }
+ }
+
+ // inject key event
+ try {
+ if (!iwm.injectKeyEvent(getEvent(), false)) {
+ return MonkeyEvent.INJECT_FAIL;
+ }
+ } catch (RemoteException ex) {
+ return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
+ }
+
+ return MonkeyEvent.INJECT_SUCCESS;
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java b/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java
new file mode 100644
index 000000000..2657061be
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.app.IActivityManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+
+
+/**
+ * monkey motion event
+ */
+public class MonkeyMotionEvent extends MonkeyEvent {
+ private long mDownTime = -1;
+ private long mEventTime = -1;
+ private int mAction = -1;
+ private float mX = -1;
+ private float mY = -1;
+ private float mPressure = -1;
+ private float mSize = -1;
+ private int mMetaState = -1;
+ private float mXPrecision = -1;
+ private float mYPrecision = -1;
+ private int mDeviceId = -1;
+ private int mEdgeFlags = -1;
+
+ //If true, this is an intermediate step (more verbose logging, only)
+ private boolean mIntermediateNote;
+
+ public MonkeyMotionEvent(int type, long downAt, int action,
+ float x, float y, int metaState) {
+ super(type);
+ mDownTime = downAt;
+ mAction = action;
+ mX = x;
+ mY = y;
+ mMetaState = metaState;
+ }
+
+ public MonkeyMotionEvent(int type, long downTime, long eventTime, int action,
+ float x, float y, float pressure, float size, int metaState,
+ float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
+ super(type);
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mX = x;
+ mY = y;
+ mPressure = pressure;
+ mSize = size;
+ mMetaState = metaState;
+ mXPrecision = xPrecision;
+ mYPrecision = yPrecision;
+ mDeviceId = deviceId;
+ mEdgeFlags = edgeFlags;
+ }
+
+ public void setIntermediateNote(boolean b) {
+ mIntermediateNote = b;
+ }
+
+ public boolean getIntermediateNote() {
+ return mIntermediateNote;
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public int getAction() {
+ return mAction;
+ }
+
+ public long getDownTime() {
+ return mDownTime;
+ }
+
+ public long getEventTime() {
+ return mEventTime;
+ }
+
+ public void setDownTime(long downTime) {
+ mDownTime = downTime;
+ }
+
+ public void setEventTime(long eventTime) {
+ mEventTime = eventTime;
+ }
+
+ /**
+ *
+ * @return instance of a motion event
+ */
+ private MotionEvent getEvent() {
+ if (mDeviceId < 0) {
+ return MotionEvent.obtain(mDownTime, SystemClock.uptimeMillis(),
+ mAction, mX, mY, mMetaState);
+ }
+
+ // for scripts
+ return MotionEvent.obtain(mDownTime, mEventTime,
+ mAction, mX, mY, mPressure, mSize, mMetaState,
+ mXPrecision, mYPrecision, mDeviceId, mEdgeFlags);
+ }
+
+ @Override
+ public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
+
+ String note;
+ if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
+ if (mAction == MotionEvent.ACTION_DOWN) {
+ note = "DOWN";
+ } else if (mAction == MotionEvent.ACTION_UP) {
+ note = "UP";
+ } else {
+ note = "MOVE";
+ }
+ System.out.println(":Sending Pointer ACTION_" + note +
+ " x=" + mX + " y=" + mY);
+ }
+ try {
+ int type = this.getEventType();
+ MotionEvent me = getEvent();
+
+ if ((type == MonkeyEvent.EVENT_TYPE_POINTER &&
+ !iwm.injectPointerEvent(me, false))
+ || (type == MonkeyEvent.EVENT_TYPE_TRACKBALL &&
+ !iwm.injectTrackballEvent(me, false))) {
+ return MonkeyEvent.INJECT_FAIL;
+ }
+ } catch (RemoteException ex) {
+ return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
+ }
+ return MonkeyEvent.INJECT_SUCCESS;
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
new file mode 100644
index 000000000..3dbb575aa
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerImpl;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Random;
+
+/**
+ * monkey event queue
+ */
+public class MonkeySourceRandom implements MonkeyEventSource{
+ /** Key events that move around the UI. */
+ private static final int[] NAV_KEYS = {
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
+ };
+ /**
+ * Key events that perform major navigation options (so shouldn't be sent
+ * as much).
+ */
+ private static final int[] MAJOR_NAV_KEYS = {
+ KeyEvent.KEYCODE_MENU, /*KeyEvent.KEYCODE_SOFT_RIGHT,*/
+ KeyEvent.KEYCODE_DPAD_CENTER,
+ };
+ /** Key events that perform system operations. */
+ private static final int[] SYS_KEYS = {
+ KeyEvent.KEYCODE_HOME, KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_CALL, KeyEvent.KEYCODE_ENDCALL,
+ KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN,
+ KeyEvent.KEYCODE_MUTE,
+ };
+ /** Nice names for all key events. */
+ private static final String[] KEY_NAMES = {
+ "KEYCODE_UNKNOWN",
+ "KEYCODE_MENU",
+ "KEYCODE_SOFT_RIGHT",
+ "KEYCODE_HOME",
+ "KEYCODE_BACK",
+ "KEYCODE_CALL",
+ "KEYCODE_ENDCALL",
+ "KEYCODE_0",
+ "KEYCODE_1",
+ "KEYCODE_2",
+ "KEYCODE_3",
+ "KEYCODE_4",
+ "KEYCODE_5",
+ "KEYCODE_6",
+ "KEYCODE_7",
+ "KEYCODE_8",
+ "KEYCODE_9",
+ "KEYCODE_STAR",
+ "KEYCODE_POUND",
+ "KEYCODE_DPAD_UP",
+ "KEYCODE_DPAD_DOWN",
+ "KEYCODE_DPAD_LEFT",
+ "KEYCODE_DPAD_RIGHT",
+ "KEYCODE_DPAD_CENTER",
+ "KEYCODE_VOLUME_UP",
+ "KEYCODE_VOLUME_DOWN",
+ "KEYCODE_POWER",
+ "KEYCODE_CAMERA",
+ "KEYCODE_CLEAR",
+ "KEYCODE_A",
+ "KEYCODE_B",
+ "KEYCODE_C",
+ "KEYCODE_D",
+ "KEYCODE_E",
+ "KEYCODE_F",
+ "KEYCODE_G",
+ "KEYCODE_H",
+ "KEYCODE_I",
+ "KEYCODE_J",
+ "KEYCODE_K",
+ "KEYCODE_L",
+ "KEYCODE_M",
+ "KEYCODE_N",
+ "KEYCODE_O",
+ "KEYCODE_P",
+ "KEYCODE_Q",
+ "KEYCODE_R",
+ "KEYCODE_S",
+ "KEYCODE_T",
+ "KEYCODE_U",
+ "KEYCODE_V",
+ "KEYCODE_W",
+ "KEYCODE_X",
+ "KEYCODE_Y",
+ "KEYCODE_Z",
+ "KEYCODE_COMMA",
+ "KEYCODE_PERIOD",
+ "KEYCODE_ALT_LEFT",
+ "KEYCODE_ALT_RIGHT",
+ "KEYCODE_SHIFT_LEFT",
+ "KEYCODE_SHIFT_RIGHT",
+ "KEYCODE_TAB",
+ "KEYCODE_SPACE",
+ "KEYCODE_SYM",
+ "KEYCODE_EXPLORER",
+ "KEYCODE_ENVELOPE",
+ "KEYCODE_ENTER",
+ "KEYCODE_DEL",
+ "KEYCODE_GRAVE",
+ "KEYCODE_MINUS",
+ "KEYCODE_EQUALS",
+ "KEYCODE_LEFT_BRACKET",
+ "KEYCODE_RIGHT_BRACKET",
+ "KEYCODE_BACKSLASH",
+ "KEYCODE_SEMICOLON",
+ "KEYCODE_APOSTROPHE",
+ "KEYCODE_SLASH",
+ "KEYCODE_AT",
+ "KEYCODE_NUM",
+ "KEYCODE_HEADSETHOOK",
+ "KEYCODE_FOCUS",
+ "KEYCODE_PLUS",
+ "KEYCODE_MENU",
+ "KEYCODE_NOTIFICATION",
+ "KEYCODE_SEARCH",
+ "KEYCODE_PLAYPAUSE",
+ "KEYCODE_STOP",
+ "KEYCODE_NEXTSONG",
+ "KEYCODE_PREVIOUSSONG",
+ "KEYCODE_REWIND",
+ "KEYCODE_FORWARD",
+ "KEYCODE_MUTE",
+
+ "TAG_LAST_KEYCODE" // EOL. used to keep the lists in sync
+ };
+
+ public static final int FACTOR_TOUCH = 0;
+ public static final int FACTOR_MOTION = 1;
+ public static final int FACTOR_TRACKBALL = 2;
+ public static final int FACTOR_NAV = 3;
+ public static final int FACTOR_MAJORNAV = 4;
+ public static final int FACTOR_SYSOPS = 5;
+ public static final int FACTOR_APPSWITCH = 6;
+ public static final int FACTOR_FLIP = 7;
+ public static final int FACTOR_ANYTHING = 8;
+ public static final int FACTORZ_COUNT = 9; // should be last+1
+
+
+ /** percentages for each type of event. These will be remapped to working
+ * values after we read any optional values.
+ **/
+ private float[] mFactors = new float[FACTORZ_COUNT];
+ private ArrayList<ComponentName> mMainApps;
+ private int mEventCount = 0; //total number of events generated so far
+ private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>();
+ private Random mRandom;
+ private int mVerbose = 0;
+
+ private boolean mKeyboardOpen = false;
+
+ /**
+ * @return the last name in the key list
+ */
+ public static String getLastKeyName() {
+ return KEY_NAMES[KeyEvent.getMaxKeyCode() + 1];
+ }
+
+ public static String getKeyName(int keycode) {
+ return KEY_NAMES[keycode];
+ }
+
+ public MonkeySourceRandom(long seed, ArrayList<ComponentName> MainApps) {
+ // default values for random distributions
+ // note, these are straight percentages, to match user input (cmd line args)
+ // but they will be converted to 0..1 values before the main loop runs.
+ mFactors[FACTOR_TOUCH] = 15.0f;
+ mFactors[FACTOR_MOTION] = 10.0f;
+ mFactors[FACTOR_TRACKBALL] = 15.0f;
+ mFactors[FACTOR_NAV] = 25.0f;
+ mFactors[FACTOR_MAJORNAV] = 15.0f;
+ mFactors[FACTOR_SYSOPS] = 2.0f;
+ mFactors[FACTOR_APPSWITCH] = 2.0f;
+ mFactors[FACTOR_FLIP] = 1.0f;
+ mFactors[FACTOR_ANYTHING] = 15.0f;
+
+ mRandom = new SecureRandom();
+ mRandom.setSeed((seed == 0) ? -1 : seed);
+ mMainApps = MainApps;
+ }
+
+ /**
+ * Adjust the percentages (after applying user values) and then normalize to a 0..1 scale.
+ */
+ private boolean adjustEventFactors() {
+ // go through all values and compute totals for user & default values
+ float userSum = 0.0f;
+ float defaultSum = 0.0f;
+ int defaultCount = 0;
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ if (mFactors[i] <= 0.0f) { // user values are zero or negative
+ userSum -= mFactors[i];
+ } else {
+ defaultSum += mFactors[i];
+ ++defaultCount;
+ }
+ }
+
+ // if the user request was > 100%, reject it
+ if (userSum > 100.0f) {
+ System.err.println("** Event weights > 100%");
+ return false;
+ }
+
+ // if the user specified all of the weights, then they need to be 100%
+ if (defaultCount == 0 && (userSum < 99.9f || userSum > 100.1f)) {
+ System.err.println("** Event weights != 100%");
+ return false;
+ }
+
+ // compute the adjustment necessary
+ float defaultsTarget = (100.0f - userSum);
+ float defaultsAdjustment = defaultsTarget / defaultSum;
+
+ // fix all values, by adjusting defaults, or flipping user values back to >0
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ if (mFactors[i] <= 0.0f) { // user values are zero or negative
+ mFactors[i] = -mFactors[i];
+ } else {
+ mFactors[i] *= defaultsAdjustment;
+ }
+ }
+
+ // if verbose, show factors
+
+ if (mVerbose > 0) {
+ System.out.println("// Event percentages:");
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ System.out.println("// " + i + ": " + mFactors[i] + "%");
+ }
+ }
+
+ // finally, normalize and convert to running sum
+ float sum = 0.0f;
+ for (int i = 0; i < FACTORZ_COUNT; ++i) {
+ sum += mFactors[i] / 100.0f;
+ mFactors[i] = sum;
+ }
+ return true;
+ }
+
+ /**
+ * set the factors
+ *
+ * @param factors: percentages for each type of event
+ */
+ public void setFactors(float factors[]) {
+ int c = FACTORZ_COUNT;
+ if (factors.length < c) {
+ c = factors.length;
+ }
+ for (int i = 0; i < c; i++)
+ mFactors[i] = factors[i];
+ }
+
+ public void setFactors(int index, float v) {
+ mFactors[index] = v;
+ }
+
+ /**
+ * Generates a random motion event. This method counts a down, move, and up as multiple events.
+ *
+ * TODO: Test & fix the selectors when non-zero percentages
+ * TODO: Longpress.
+ * TODO: Fling.
+ * TODO: Meta state
+ * TODO: More useful than the random walk here would be to pick a single random direction
+ * and distance, and divvy it up into a random number of segments. (This would serve to
+ * generate fling gestures, which are important).
+ *
+ * @param random Random number source for positioning
+ * @param motionEvent If false, touch/release. If true, touch/move/release.
+ *
+ */
+ private void generateMotionEvent(Random random, boolean motionEvent){
+
+ Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+
+ float x = Math.abs(random.nextInt() % display.getWidth());
+ float y = Math.abs(random.nextInt() % display.getHeight());
+ long downAt = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+ if (downAt == -1) {
+ downAt = eventTime;
+ }
+
+ MonkeyMotionEvent e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
+ downAt, MotionEvent.ACTION_DOWN, x, y, 0);
+ e.setIntermediateNote(false);
+ mQ.addLast(e);
+
+ // sometimes we'll move during the touch
+ if (motionEvent) {
+ int count = random.nextInt(10);
+ for (int i = 0; i < count; i++) {
+ // generate some slop in the up event
+ x = (x + (random.nextInt() % 10)) % display.getWidth();
+ y = (y + (random.nextInt() % 10)) % display.getHeight();
+
+ e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
+ downAt, MotionEvent.ACTION_MOVE, x, y, 0);
+ e.setIntermediateNote(true);
+ mQ.addLast(e);
+ }
+ }
+
+ // TODO generate some slop in the up event
+ e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_POINTER,
+ downAt, MotionEvent.ACTION_UP, x, y, 0);
+ e.setIntermediateNote(false);
+ mQ.addLast(e);
+ }
+
+ /**
+ * Generates a random trackball event. This consists of a sequence of small moves, followed by
+ * an optional single click.
+ *
+ * TODO: Longpress.
+ * TODO: Meta state
+ * TODO: Parameterize the % clicked
+ * TODO: More useful than the random walk here would be to pick a single random direction
+ * and distance, and divvy it up into a random number of segments. (This would serve to
+ * generate fling gestures, which are important).
+ *
+ * @param random Random number source for positioning
+ *
+ */
+ private void generateTrackballEvent(Random random) {
+ Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
+
+ boolean drop = false;
+ int count = random.nextInt(10);
+ MonkeyMotionEvent e;
+ for (int i = 0; i < 10; ++i) {
+ // generate a small random step
+ int dX = random.nextInt(10) - 5;
+ int dY = random.nextInt(10) - 5;
+
+
+ e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, -1,
+ MotionEvent.ACTION_MOVE, dX, dY, 0);
+ e.setIntermediateNote(i > 0);
+ mQ.addLast(e);
+ }
+
+ // 10% of trackball moves end with a click
+ if (0 == random.nextInt(10)) {
+ long downAt = SystemClock.uptimeMillis();
+
+
+ e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
+ MotionEvent.ACTION_DOWN, 0, 0, 0);
+ e.setIntermediateNote(true);
+ mQ.addLast(e);
+
+
+ e = new MonkeyMotionEvent(MonkeyEvent.EVENT_TYPE_TRACKBALL, downAt,
+ MotionEvent.ACTION_UP, 0, 0, 0);
+ e.setIntermediateNote(false);
+ mQ.addLast(e);
+ }
+ }
+
+ /**
+ * generate a random event based on mFactor
+ */
+ private void generateEvents() {
+ float cls = mRandom.nextFloat();
+ int lastKey = 0;
+
+ boolean touchEvent = cls < mFactors[FACTOR_TOUCH];
+ boolean motionEvent = !touchEvent && (cls < mFactors[FACTOR_MOTION]);
+ if (touchEvent || motionEvent) {
+ generateMotionEvent(mRandom, motionEvent);
+ return;
+ }
+
+ if (cls < mFactors[FACTOR_TRACKBALL]) {
+ generateTrackballEvent(mRandom);
+ return;
+ }
+
+ // The remaining event categories are injected as key events
+ if (cls < mFactors[FACTOR_NAV]) {
+ lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_MAJORNAV]) {
+ lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_SYSOPS]) {
+ lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
+ } else if (cls < mFactors[FACTOR_APPSWITCH]) {
+ MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
+ mRandom.nextInt(mMainApps.size())));
+ mQ.addLast(e);
+ return;
+ } else if (cls < mFactors[FACTOR_FLIP]) {
+ MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
+ mKeyboardOpen = !mKeyboardOpen;
+ mQ.addLast(e);
+ return;
+ } else {
+ lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
+ }
+
+ MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
+ mQ.addLast(e);
+
+ e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
+ mQ.addLast(e);
+ }
+
+ public boolean validate() {
+ //check factors
+ return adjustEventFactors();
+ }
+
+ public void setVerbose(int verbose) {
+ mVerbose = verbose;
+ }
+
+ /**
+ * generate an activity event
+ */
+ public void generateActivity() {
+ MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
+ mRandom.nextInt(mMainApps.size())));
+ mQ.addLast(e);
+ }
+
+ /**
+ * if the queue is empty, we generate events first
+ * @return the first event in the queue
+ */
+ public MonkeyEvent getNextEvent() {
+ if (mQ.isEmpty()) {
+ generateEvents();
+ }
+ mEventCount++;
+ MonkeyEvent e = mQ.getFirst();
+ mQ.removeFirst();
+ return e;
+ }
+}
diff --git a/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java
new file mode 100644
index 000000000..aadda9f62
--- /dev/null
+++ b/cmds/monkey/src/com/android/commands/monkey/MonkeySourceScript.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.monkey;
+
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+/**
+ * monkey event queue. It takes a script to produce events
+ *
+ * sample script format:
+ * type= raw events
+ * count= 10
+ * speed= 1.0
+ * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314,
+ * 0.06666667,0,0.0,0.0,65539,0)
+ * captureDispatchKey(5113146,5113146,0,20,0,0,0,0)
+ * captureDispatchFlip(true)
+ * ...
+ */
+public class MonkeySourceScript implements MonkeyEventSource{
+ private int mEventCountInScript = 0; //total number of events in the file
+ private int mVerbose = 0;
+ private double mSpeed = 1.0;
+ private String mScriptFileName;
+ private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>();
+
+ private static final String HEADER_TYPE = "type=";
+ private static final String HEADER_COUNT = "count=";
+ private static final String HEADER_SPEED = "speed=";
+
+ private long mLastRecordedDownTimeKey = 0;
+ private long mLastRecordedDownTimeMotion = 0;
+ private long mLastExportDownTimeKey = 0;
+ private long mLastExportDownTimeMotion = 0;
+ private long mLastExportEventTime = -1;
+ private long mLastRecordedEventTime = -1;
+
+ private static final boolean THIS_DEBUG = false;
+ // a parameter that compensates the difference of real elapsed time and
+ // time in theory
+ private static final long SLEEP_COMPENSATE_DIFF = 16;
+
+ // maximum number of events that we read at one time
+ private static final int MAX_ONE_TIME_READS = 100;
+
+ // number of additional events added to the script
+ // add HOME_KEY down and up events to make start UI consistent in each round
+ private static final int POLICY_ADDITIONAL_EVENT_COUNT = 2;
+
+ // event key word in the capture log
+ private static final String EVENT_KEYWORD_POINTER = "DispatchPointer";
+ private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball";
+ private static final String EVENT_KEYWORD_KEY = "DispatchKey";
+ private static final String EVENT_KEYWORD_FLIP = "DispatchFlip";
+
+ // a line at the end of the header
+ private static final String STARTING_DATA_LINE = "start data >>";
+ private boolean mFileOpened = false;
+ FileInputStream mFStream;
+ DataInputStream mInputStream;
+ BufferedReader mBufferReader;
+
+ public MonkeySourceScript(String filename) {
+ mScriptFileName = filename;
+ }
+
+ /**
+ *
+ * @return the number of total events that will be generated in a round
+ */
+ public int getOneRoundEventCount() {
+ //plus one home key down and up event
+ return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT;
+ }
+
+ private void resetValue() {
+ mLastRecordedDownTimeKey = 0;
+ mLastRecordedDownTimeMotion = 0;
+ mLastExportDownTimeKey = 0;
+ mLastExportDownTimeMotion = 0;
+ mLastRecordedEventTime = -1;
+ mLastExportEventTime = -1;
+ }
+
+ private boolean readScriptHeader() {
+ mEventCountInScript = -1;
+ mFileOpened = false;
+ try {
+ if (THIS_DEBUG) {
+ System.out.println("reading script header");
+ }
+
+ mFStream = new FileInputStream(mScriptFileName);
+ mInputStream = new DataInputStream(mFStream);
+ mBufferReader = new BufferedReader(
+ new InputStreamReader(mInputStream));
+ String sLine;
+ while ((sLine = mBufferReader.readLine()) != null) {
+ sLine = sLine.trim();
+ if (sLine.indexOf(HEADER_TYPE) >= 0) {
+ // at this point, we only have one type of script
+ } else if (sLine.indexOf(HEADER_COUNT) >= 0) {
+ try {
+ mEventCountInScript = Integer.parseInt(sLine.substring(
+ HEADER_COUNT.length() + 1).trim());
+ } catch (NumberFormatException e) {
+ System.err.println(e);
+ }
+ } else if (sLine.indexOf(HEADER_SPEED) >= 0) {
+ try {
+ mSpeed = Double.parseDouble(sLine.substring(
+ HEADER_SPEED.length() + 1).trim());
+
+ } catch (NumberFormatException e) {
+ System.err.println(e);
+ }
+ } else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) {
+ // header ends until we read the start data mark
+ mFileOpened = true;
+ if (THIS_DEBUG) {
+ System.out.println("read script header success");
+ }
+ return true;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ System.err.println(e);
+ } catch (IOException e) {
+ System.err.println(e);
+ }
+
+ if (THIS_DEBUG) {
+ System.out.println("Error in reading script header");
+ }
+ return false;
+ }
+
+ private void processLine(String s) {
+ int index1 = s.indexOf('(');
+ int index2 = s.indexOf(')');
+
+ if (index1 < 0 || index2 < 0) {
+ return;
+ }
+
+ StringTokenizer st = new StringTokenizer(
+ s.substring(index1 + 1, index2), ",");
+
+ if (s.indexOf(EVENT_KEYWORD_KEY) >= 0) {
+ // key events
+ try {
+ long downTime = Long.parseLong(st.nextToken());
+ long eventTime = Long.parseLong(st.nextToken());
+ int action = Integer.parseInt(st.nextToken());
+ int code = Integer.parseInt(st.nextToken());
+ int repeat = Integer.parseInt(st.nextToken());
+ int metaState = Integer.parseInt(st.nextToken());
+ int device = Integer.parseInt(st.nextToken());
+ int scancode = Integer.parseInt(st.nextToken());
+
+ MonkeyKeyEvent e = new MonkeyKeyEvent(downTime, eventTime,
+ action, code, repeat, metaState, device, scancode);
+ mQ.addLast(e);
+
+ } catch (NumberFormatException e) {
+ // something wrong with this line in the script
+ }
+ } else if (s.indexOf(EVENT_KEYWORD_POINTER) >= 0 ||
+ s.indexOf(EVENT_KEYWORD_TRACKBALL) >= 0) {
+ // trackball/pointer event
+ try {
+ long downTime = Long.parseLong(st.nextToken());
+ long eventTime = Long.parseLong(st.nextToken());
+ int action = Integer.parseInt(st.nextToken());
+ float x = Float.parseFloat(st.nextToken());
+ float y = Float.parseFloat(st.nextToken());
+ float pressure = Float.parseFloat(st.nextToken());
+ float size = Float.parseFloat(st.nextToken());
+ int metaState = Integer.parseInt(st.nextToken());
+ float xPrecision = Float.parseFloat(st.nextToken());
+ float yPrecision = Float.parseFloat(st.nextToken());
+ int device = Integer.parseInt(st.nextToken());
+ int edgeFlags = Integer.parseInt(st.nextToken());
+
+ int type = MonkeyEvent.EVENT_TYPE_TRACKBALL;
+ if (s.indexOf("Pointer") > 0) {
+ type = MonkeyEvent.EVENT_TYPE_POINTER;
+ }
+ MonkeyMotionEvent e = new MonkeyMotionEvent(type, downTime, eventTime,
+ action, x, y, pressure, size, metaState, xPrecision, yPrecision,
+ device, edgeFlags);
+ mQ.addLast(e);
+ } catch (NumberFormatException e) {
+ // we ignore this event
+ }
+ } else if (s.indexOf(EVENT_KEYWORD_FLIP) >= 0) {
+ boolean keyboardOpen = Boolean.parseBoolean(st.nextToken());
+ MonkeyFlipEvent e = new MonkeyFlipEvent(keyboardOpen);
+ mQ.addLast(e);
+ }
+ }
+
+ private void closeFile() {
+ mFileOpened = false;
+ if (THIS_DEBUG) {
+ System.out.println("closing script file");
+ }
+
+ try {
+ mFStream.close();
+ mInputStream.close();
+ } catch (IOException e) {
+ System.out.println(e);
+ }
+ }
+
+ /**
+ * add home key press/release event to the queue
+ */
+ private void addHomeKeyEvent() {
+ MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_HOME);
+ mQ.addLast(e);
+ e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HOME);
+ mQ.addLast(e);
+ }
+
+ /**
+ * read next batch of events from the provided script file
+ * @return true if success
+ */
+ private boolean readNextBatch() {
+ String sLine = null;
+ int readCount = 0;
+
+ if (THIS_DEBUG) {
+ System.out.println("readNextBatch(): reading next batch of events");
+ }
+
+ if (!mFileOpened) {
+ if (!readScriptHeader()) {
+ closeFile();
+ return false;
+ }
+ resetValue();
+
+ /*
+ * In order to allow the Monkey to replay captured events multiple times
+ * we need to define a default start UI, which is the home screen
+ * Otherwise, it won't be accurate since the captured events
+ * could end anywhere
+ */
+ addHomeKeyEvent();
+ }
+
+ try {
+ while (readCount++ < MAX_ONE_TIME_READS &&
+ (sLine = mBufferReader.readLine()) != null) {
+ sLine = sLine.trim();
+ processLine(sLine);
+ }
+ } catch (IOException e) {
+ System.err.println(e);
+ return false;
+ }
+
+ if (sLine == null) {
+ // to the end of the file
+ if (THIS_DEBUG) {
+ System.out.println("readNextBatch(): to the end of file");
+ }
+ closeFile();
+ }
+ return true;
+ }
+
+ /**
+ * sleep for a period of given time, introducing latency among events
+ * @param time to sleep in millisecond
+ */
+ private void needSleep(long time) {
+ if (time < 1) {
+ return;
+ }
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ /**
+ * check whether we can successfully read the header of the script file
+ */
+ public boolean validate() {
+ boolean b = readNextBatch();
+ if (mVerbose > 0) {
+ System.out.println("Replaying " + mEventCountInScript +
+ " events with speed " + mSpeed);
+ }
+ return b;
+ }
+
+ public void setVerbose(int verbose) {
+ mVerbose = verbose;
+ }
+
+ /**
+ * adjust key downtime and eventtime according to both
+ * recorded values and current system time
+ * @param e KeyEvent
+ */
+ private void adjustKeyEventTime(MonkeyKeyEvent e) {
+ if (e.getEventTime() < 0) {
+ return;
+ }
+ long thisDownTime = 0;
+ long thisEventTime = 0;
+ long expectedDelay = 0;
+
+ if (mLastRecordedEventTime <= 0) {
+ // first time event
+ thisDownTime = SystemClock.uptimeMillis();
+ thisEventTime = thisDownTime;
+ } else {
+ if (e.getDownTime() != mLastRecordedDownTimeKey) {
+ thisDownTime = e.getDownTime();
+ } else {
+ thisDownTime = mLastExportDownTimeKey;
+ }
+ expectedDelay = (long) ((e.getEventTime() -
+ mLastRecordedEventTime) * mSpeed);
+ thisEventTime = mLastExportEventTime + expectedDelay;
+ // add sleep to simulate everything in recording
+ needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
+ }
+ mLastRecordedDownTimeKey = e.getDownTime();
+ mLastRecordedEventTime = e.getEventTime();
+ e.setDownTime(thisDownTime);
+ e.setEventTime(thisEventTime);
+ mLastExportDownTimeKey = thisDownTime;
+ mLastExportEventTime = thisEventTime;
+ }
+
+ /**
+ * adjust motion downtime and eventtime according to both
+ * recorded values and current system time
+ * @param e KeyEvent
+ */
+ private void adjustMotionEventTime(MonkeyMotionEvent e) {
+ if (e.getEventTime() < 0) {
+ return;
+ }
+ long thisDownTime = 0;
+ long thisEventTime = 0;
+ long expectedDelay = 0;
+
+ if (mLastRecordedEventTime <= 0) {
+ // first time event
+ thisDownTime = SystemClock.uptimeMillis();
+ thisEventTime = thisDownTime;
+ } else {
+ if (e.getDownTime() != mLastRecordedDownTimeMotion) {
+ thisDownTime = e.getDownTime();
+ } else {
+ thisDownTime = mLastExportDownTimeMotion;
+ }
+ expectedDelay = (long) ((e.getEventTime() -
+ mLastRecordedEventTime) * mSpeed);
+ thisEventTime = mLastExportEventTime + expectedDelay;
+ // add sleep to simulate everything in recording
+ needSleep(expectedDelay - SLEEP_COMPENSATE_DIFF);
+ }
+
+ mLastRecordedDownTimeMotion = e.getDownTime();
+ mLastRecordedEventTime = e.getEventTime();
+ e.setDownTime(thisDownTime);
+ e.setEventTime(thisEventTime);
+ mLastExportDownTimeMotion = thisDownTime;
+ mLastExportEventTime = thisEventTime;
+ }
+
+ /**
+ * if the queue is empty, we generate events first
+ * @return the first event in the queue, if null, indicating the system crashes
+ */
+ public MonkeyEvent getNextEvent() {
+ long recordedEventTime = -1;
+
+ if (mQ.isEmpty()) {
+ readNextBatch();
+ }
+ MonkeyEvent e = mQ.getFirst();
+ mQ.removeFirst();
+
+ if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
+ adjustKeyEventTime((MonkeyKeyEvent) e);
+ } else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER ||
+ e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
+ adjustMotionEventTime((MonkeyMotionEvent) e);
+ }
+ return e;
+ }
+}