bin/layoutopt tools/layoutopt
bin/traceview tools/traceview
bin/android tools/android
+bin/monkeyrunner tools/monkeyrunner
# sdk.git Ant templates for project build files
framework/sdklib.jar tools/lib/sdklib.jar
framework/sdkuilib.jar tools/lib/sdkuilib.jar
framework/sdkmanager.jar tools/lib/sdkmanager.jar
+framework/monkeyrunner.jar tools/lib/monkeyrunner.jar
+framework/gauvalib.jar tools/lib/guavalib.jar
+framework/jsilver.jar tools/lib/jsilver.jar
# 3rd Party java libraries
framework/groovy-all-1.7.0.jar tools/lib/groovy-all-1.7.0.jar
external/proguard/bin/proguardgui.sh tools/proguard/bin/proguardgui.sh
external/proguard/bin/retrace.sh tools/proguard/bin/retrace.sh
external/proguard/src/proguard/ant/task.properties tools/proguard/ant/task.properties
-
--- /dev/null
+#!/usr/bin/env monkeyrunner
+# Copyright 2010, 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.
+import com.android.monkeyrunner.MonkeyRunnerHelp as mrh
+import pydoc
+import sys
+
+def create_page(title, document):
+ return """
+page.title=%s
+@jd:body
+%s
+</body>
+</html>
+""" % (title, document)
+
+BASEDIR = 'frameworks/base/docs/html/guide/topics/testing/'
+
+def main():
+ document = ""
+
+ for clz in mrh.getAllDocumentedClasses():
+ object, name = pydoc.resolve(str(clz), 0)
+ document += pydoc.html.document(object, name)
+
+ page = create_page('MonkeyRunner API', document)
+ file = open(BASEDIR + 'monkeyrunner_api.html', 'w')
+ file.write(page)
+ file.close()
+
+if __name__ == '__main__':
+ main()
*/
package com.android.monkeyrunner;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.ImmutableMap.Builder;
-
-import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.text.BreakIterator;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyFloat;
import org.python.core.PyList;
import org.python.core.PyNone;
import org.python.core.PyObject;
+import org.python.core.PyReflectedFunction;
import org.python.core.PyString;
+import org.python.core.PyStringMap;
import org.python.core.PyTuple;
-import java.lang.reflect.Method;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap.Builder;
/**
* Collection of useful utilities function for interacting with the Jython interpreter.
}
return new PyDictionary(resultMap);
}
+
+ /**
+ * This function should be called from classDictInit for any classes that are being exported
+ * to jython. This jython converts all the MonkeyRunnerExported annotations for the given class
+ * into the proper python form. It also removes any functions listed in the dictionary that
+ * aren't specifically annotated in the java class.
+ *
+ * NOTE: Make sure the calling class implements {@link ClassDictInit} to ensure that
+ * classDictInit gets called.
+ *
+ * @param clz the class to examine.
+ * @param dict the dictionary to update.
+ */
+ public static void convertDocAnnotationsForClass(Class<?> clz, PyObject dict) {
+ Preconditions.checkNotNull(dict);
+ Preconditions.checkArgument(dict instanceof PyStringMap);
+
+ // See if the class has the annotation
+ if (clz.isAnnotationPresent(MonkeyRunnerExported.class)) {
+ MonkeyRunnerExported doc = clz.getAnnotation(MonkeyRunnerExported.class);
+ String fullDoc = buildClassDoc(doc, clz);
+ dict.__setitem__("__doc__", new PyString(fullDoc));
+ }
+
+ // Get all the keys from the dict and put them into a set. As we visit the annotated methods,
+ // we will remove them from this set. At the end, these are the "hidden" methods that
+ // should be removed from the dict
+ Collection<String> functions = Sets.newHashSet();
+ for (PyObject item : dict.asIterable()) {
+ functions.add(item.toString());
+ }
+
+ // And remove anything that starts with __, as those are pretty important to retain
+ functions = Collections2.filter(functions, new Predicate<String>() {
+ @Override
+ public boolean apply(String value) {
+ return !value.startsWith("__");
+ }
+ });
+
+ // Look at all the methods in the class and find the one's that have the
+ // @MonkeyRunnerExported annotation.
+ for (Method m : clz.getMethods()) {
+ if (m.isAnnotationPresent(MonkeyRunnerExported.class)) {
+ String methodName = m.getName();
+ PyObject pyFunc = dict.__finditem__(methodName);
+ if (pyFunc != null && pyFunc instanceof PyReflectedFunction) {
+ PyReflectedFunction realPyFunc = (PyReflectedFunction) pyFunc;
+ MonkeyRunnerExported doc = m.getAnnotation(MonkeyRunnerExported.class);
+
+ realPyFunc.__doc__ = new PyString(buildDoc(doc));
+ functions.remove(methodName);
+ }
+ }
+ }
+
+ // Now remove any elements left from the functions collection
+ for (String name : functions) {
+ dict.__delitem__(name);
+ }
+ }
+
+ private static final Predicate<AccessibleObject> SHOULD_BE_DOCUMENTED = new Predicate<AccessibleObject>() {
+ @Override
+ public boolean apply(AccessibleObject ao) {
+ return ao.isAnnotationPresent(MonkeyRunnerExported.class);
+ }
+ };
+ private static final Predicate<Field> IS_FIELD_STATIC = new Predicate<Field>() {
+ @Override
+ public boolean apply(Field f) {
+ return (f.getModifiers() & Modifier.STATIC) != 0;
+ }
+ };
+
+ /**
+ * build a jython doc-string for a class from the annotation and the fields
+ * contained within the class
+ *
+ * @param doc the annotation
+ * @param clz the class to be documented
+ * @return the doc-string
+ */
+ private static String buildClassDoc(MonkeyRunnerExported doc, Class<?> clz) {
+ // Below the class doc, we need to document all the documented field this class contains
+ Collection<Field> annotatedFields = Collections2.filter(Arrays.asList(clz.getFields()), SHOULD_BE_DOCUMENTED);
+ Collection<Field> staticFields = Collections2.filter(annotatedFields, IS_FIELD_STATIC);
+ Collection<Field> nonStaticFields = Collections2.filter(annotatedFields, Predicates.not(IS_FIELD_STATIC));
+
+ StringBuilder sb = new StringBuilder();
+ for (String line : splitString(doc.doc(), 80)) {
+ sb.append(line).append("\n");
+ }
+
+ if (staticFields.size() > 0) {
+ sb.append("\nClass Fields: \n");
+ for (Field f : staticFields) {
+ sb.append(buildFieldDoc(f));
+ }
+ }
+
+ if (nonStaticFields.size() > 0) {
+ sb.append("\n\nFields: \n");
+ for (Field f : nonStaticFields) {
+ sb.append(buildFieldDoc(f));
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Build a doc-string for the annotated field.
+ *
+ * @param f the field.
+ * @return the doc-string.
+ */
+ private static String buildFieldDoc(Field f) {
+ MonkeyRunnerExported annotation = f.getAnnotation(MonkeyRunnerExported.class);
+ StringBuilder sb = new StringBuilder();
+ int indentOffset = 2 + 3 + f.getName().length();
+ String indent = makeIndent(indentOffset);
+
+ sb.append(" ").append(f.getName()).append(" - ");
+
+ boolean first = true;
+ for (String line : splitString(annotation.doc(), 80 - indentOffset)) {
+ if (first) {
+ first = false;
+ sb.append(line).append("\n");
+ } else {
+ sb.append(indent).append(line).append("\n");
+ }
+ }
+
+
+ return sb.toString();
+ }
+
+ /**
+ * Build a jython doc-string from the MonkeyRunnerExported annotation.
+ *
+ * @param doc the annotation to build from
+ * @return a jython doc-string
+ */
+ private static String buildDoc(MonkeyRunnerExported doc) {
+ Collection<String> docs = splitString(doc.doc(), 80);
+ StringBuilder sb = new StringBuilder();
+ for (String d : docs) {
+ sb.append(d).append("\n");
+ }
+
+ if (doc.args() != null && doc.args().length > 0) {
+ String[] args = doc.args();
+ String[] argDocs = doc.argDocs();
+
+ sb.append("\n Args:\n");
+ for (int x = 0; x < doc.args().length; x++) {
+ sb.append(" ").append(args[x]);
+ if (argDocs != null && argDocs.length > x) {
+ sb.append(" - ");
+ int indentOffset = args[x].length() + 3 + 4;
+ Collection<String> lines = splitString(argDocs[x], 80 - indentOffset);
+ boolean first = true;
+ String indent = makeIndent(indentOffset);
+ for (String line : lines) {
+ if (first) {
+ first = false;
+ sb.append(line).append("\n");
+ } else {
+ sb.append(indent).append(line).append("\n");
+ }
+ }
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static String makeIndent(int indentOffset) {
+ if (indentOffset == 0) {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer();
+ while (indentOffset > 0) {
+ sb.append(' ');
+ indentOffset--;
+ }
+ return sb.toString();
+ }
+
+ private static Collection<String> splitString(String source, int offset) {
+ BreakIterator boundary = BreakIterator.getLineInstance();
+ boundary.setText(source);
+
+ List<String> lines = Lists.newArrayList();
+ StringBuilder currentLine = new StringBuilder();
+ int start = boundary.first();
+
+ for (int end = boundary.next();
+ end != BreakIterator.DONE;
+ start = end, end = boundary.next()) {
+ String b = source.substring(start, end);
+ if (currentLine.length() + b.length() < offset) {
+ currentLine.append(b);
+ } else {
+ // emit the old line
+ lines.add(currentLine.toString());
+ currentLine = new StringBuilder(b);
+ }
+ }
+ lines.add(currentLine.toString());
+ return lines;
+ }
}
*/
package com.android.monkeyrunner;
-import com.google.common.base.Functions;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Collections2;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
-import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import javax.annotation.Nullable;
import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyException;
import org.python.core.PyObject;
import org.python.core.PyTuple;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import javax.annotation.Nullable;
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
/*
* Abstract base class that represents a single connected Android
* that device. Each backend will need to create a concrete
* implementation of this class.
*/
-public abstract class MonkeyDevice {
+@MonkeyRunnerExported(doc = "Represents a device attached to the system.")
+public abstract class MonkeyDevice extends PyObject implements ClassDictInit {
+ public static void classDictInit(PyObject dict) {
+ JythonUtils.convertDocAnnotationsForClass(MonkeyDevice.class, dict);
+ }
+
+ @MonkeyRunnerExported(doc = "Sends a DOWN event when used with touch() or press().")
+ public static final String DOWN = "down";
+ @MonkeyRunnerExported(doc = "Sends an UP event when used with touch() or press().")
+ public static final String UP = "up";
+ @MonkeyRunnerExported(doc = "Sends a DOWN event, immediately followed by an UP event when used with touch() or press()")
+ public static final String DOWN_AND_UP = "downAndUp";
+
+ // Visible to subclasses
+ protected enum TouchPressType {
+ DOWN, UP, DOWN_AND_UP,
+ }
+
+ private static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM =
+ ImmutableMap.of(MonkeyDevice.DOWN, TouchPressType.DOWN,
+ MonkeyDevice.UP, TouchPressType.UP,
+ MonkeyDevice.DOWN_AND_UP, TouchPressType.DOWN_AND_UP);
+
+ private static final Set<String> VALID_DOWN_UP_TYPES = TOUCH_NAME_TO_ENUM.keySet();
+
/**
* Create a MonkeyMananger for talking to this device.
*
return getSystemProperty(ap.getString(0));
}
- @MonkeyRunnerExported(doc = "Enumerates the possible touch and key event types. Use this " +
- "with touch() or press() to specify the event type.",
- argDocs = {"Sends a DOWN event",
- "Sends an UP event",
- "Sends a DOWN event, immediately followed by an UP event"})
- public enum TouchPressType {
- DOWN, UP, DOWN_AND_UP
- }
-
@MonkeyRunnerExported(doc = "Sends a touch event at the specified location",
args = { "x", "y", "type" },
argDocs = { "x coordinate in pixels",
int y = ap.getInt(1);
// Default
- MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+ String type = MonkeyDevice.DOWN_AND_UP;
try {
- PyObject pyObject = ap.getPyObject(2);
- type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+ String tmpType = ap.getString(2);
+ if (VALID_DOWN_UP_TYPES.contains(tmpType)) {
+ type = tmpType;
+ } else {
+ // not a valid type
+ type = MonkeyDevice.DOWN_AND_UP;
+ }
} catch (PyException e) {
// bad stuff was passed in, just use the already specified default value
- type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+ type = MonkeyDevice.DOWN_AND_UP;
}
- touch(x, y, type);
+ touch(x, y, TOUCH_NAME_TO_ENUM.get(type));
}
@MonkeyRunnerExported(doc = "Simulates dragging (touch, hold, and move) on the device screen.",
String name = ap.getString(0);
// Default
- MonkeyDevice.TouchPressType type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+ String type = MonkeyDevice.DOWN_AND_UP;
try {
- PyObject pyObject = ap.getPyObject(1);
- type = (TouchPressType) pyObject.__tojava__(MonkeyDevice.TouchPressType.class);
+ String tmpType = ap.getString(2);
+ if (VALID_DOWN_UP_TYPES.contains(tmpType)) {
+ type = tmpType;
+ } else {
+ // not a valid type
+ type = MonkeyDevice.DOWN_AND_UP;
+ }
} catch (PyException e) {
// bad stuff was passed in, just use the already specified default value
- type = MonkeyDevice.TouchPressType.DOWN_AND_UP;
+ type = MonkeyDevice.DOWN_AND_UP;
}
- press(name, type);
+ press(name, TOUCH_NAME_TO_ENUM.get(type));
}
@MonkeyRunnerExported(doc = "Types the specified string on the keyboard. This is " +
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
import org.python.core.PyInteger;
import org.python.core.PyObject;
import org.python.core.PyTuple;
/**
* Jython object to encapsulate images that have been taken.
*/
-public abstract class MonkeyImage {
+@MonkeyRunnerExported(doc = "An image")
+public abstract class MonkeyImage extends PyObject implements ClassDictInit {
+ public static void classDictInit(PyObject dict) {
+ JythonUtils.convertDocAnnotationsForClass(MonkeyImage.class, dict);
+ }
+
/**
* Convert the MonkeyImage into a BufferedImage.
*
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import org.python.core.ArgParser;
+import org.python.core.ClassDictInit;
import org.python.core.PyException;
import org.python.core.PyObject;
/**
* This is the main interface class into the jython bindings.
*/
-public class MonkeyRunner {
+@MonkeyRunnerExported(doc = "Main entry point for MonkeyRunner")
+public class MonkeyRunner extends PyObject implements ClassDictInit {
private static final Logger LOG = Logger.getLogger(MonkeyRunner.class.getCanonicalName());
private static MonkeyRunnerBackend backend;
+ public static void classDictInit(PyObject dict) {
+ JythonUtils.convertDocAnnotationsForClass(MonkeyRunner.class, dict);
+ }
+
/**
* Set the backend MonkeyRunner is using.
*
import com.google.clearsilver.jsilver.data.Data;
import com.google.clearsilver.jsilver.resourceloader.ClassLoaderResourceLoader;
import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
+import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
return hdf;
}
+
+ public static Collection<String> getAllDocumentedClasses() {
+ Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER);
+ Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER);
+ Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER);
+ Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER);
+ getAllExportedClasses(fields, methods, constructors, classes);
+
+ // The classes object only captures classes that are specifically exporter, which isn't
+ // good enough. So go through all the fields, methods, etc. and collect those classes as
+ // as well
+ Set<Class<?>> allClasses = Sets.newHashSet();
+ allClasses.addAll(classes);
+ for (Field f : fields) {
+ allClasses.add(f.getDeclaringClass());
+ }
+ for (Method m : methods) {
+ allClasses.add(m.getDeclaringClass());
+ }
+ for (Constructor<?> constructor : constructors) {
+ allClasses.add(constructor.getDeclaringClass());
+ }
+
+ // And transform that collection into a list of simple names.
+ return Collections2.transform(allClasses, new Function<Class<?>, String>() {
+ @Override
+ public String apply(Class<?> clz) {
+ return clz.getName();
+ }
+ });
+ }
}
import com.android.monkeyrunner.adb.AdbBackend;
import com.android.monkeyrunner.stub.StubBackend;
+import org.python.core.Py;
+import org.python.core.PyBuiltinMethod;
+import org.python.core.PyDataDescr;
+import org.python.core.PyNewWrapper;
+import org.python.core.PyObject;
+import org.python.core.PySystemState;
+import org.python.core.PyType;
+import org.python.expose.BaseTypeBuilder;
+import org.python.expose.TypeBuilder;
import org.python.util.PythonInterpreter;
import java.io.File;
private int run() {
// This system property gets set by the included starter script
String monkeyRunnerPath = System.getProperty("com.android.monkeyrunner.bindir") +
- File.separator + "monkeyrunner";
+ File.separator + "monkeyrunner";
MonkeyRunner.setBackend(backend);
Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.Lists;
import org.python.core.Py;
import org.python.core.PyException;
+import org.python.core.PyJavaPackage;
+import org.python.core.PyList;
import org.python.core.PyObject;
+import org.python.core.PyString;
+import org.python.core.PySystemState;
import org.python.util.InteractiveConsole;
import org.python.util.JLineConsole;
import org.python.util.PythonInterpreter;
// Bind __name__ to __main__ so mains will run
python.set("__name__", "__main__");
+ // Also find __file__
+ python.set("__file__", scriptfilename);
try {
python.execfile(scriptfilename);
props.setProperty("python.executable", executablePath);
PythonInterpreter.initialize(System.getProperties(), props, argv);
+
+ String frameworkDir = System.getProperty("java.ext.dirs");
+ File monkeyRunnerJar = new File(frameworkDir, "monkeyrunner.jar");
+ if (monkeyRunnerJar.canRead()) {
+ PySystemState.packageManager.addJar(monkeyRunnerJar.getAbsolutePath(), false);
+ }
}
/**
* these methods in the scripting environment.
*/
@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE })
+@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR,
+ ElementType.TYPE, ElementType.FIELD })
public @interface MonkeyRunnerExported {
/**
* A documentation string for this method.