am 87bf1fd2: Merge "Don\'t load abstract classes."

* commit '87bf1fd2dc91da265938b47cb2cabed07e2faf31':
  Don't load abstract classes.
diff --git a/uiautomator/api/16.txt b/uiautomator/api/16.txt
index 6634d7c..f3b0eb7 100644
--- a/uiautomator/api/16.txt
+++ b/uiautomator/api/16.txt
@@ -68,7 +68,6 @@
     method public java.lang.String getPackageName() throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public final com.android.uiautomator.core.UiSelector getSelector();
     method public java.lang.String getText() throws com.android.uiautomator.core.UiObjectNotFoundException;
-    method public android.graphics.Rect getVisibleBounds() throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean isCheckable() throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean isChecked() throws com.android.uiautomator.core.UiObjectNotFoundException;
     method public boolean isClickable() throws com.android.uiautomator.core.UiObjectNotFoundException;
diff --git a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
index 862c341..7320207 100644
--- a/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
+++ b/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/RunTestCommand.java
@@ -17,11 +17,16 @@
 package com.android.commands.uiautomator;
 
 import android.os.Bundle;
+import android.util.Log;
 
 import com.android.commands.uiautomator.Launcher.Command;
 import com.android.uiautomator.testrunner.UiAutomatorTestRunner;
 
+import dalvik.system.DexFile;
+
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
 
 /**
@@ -29,11 +34,16 @@
  *
  */
 public class RunTestCommand extends Command {
+    private static final String LOGTAG = RunTestCommand.class.getSimpleName();
 
+    private static final String OUTPUT_SIMPLE = "simple";
+    private static final String OUTPUT_FORMAT_KEY = "outputFormat";
     private static final String CLASS_PARAM = "class";
+    private static final String JARS_PARAM = "jars";
     private static final String DEBUG_PARAM = "debug";
     private static final String RUNNER_PARAM = "runner";
     private static final String CLASS_SEPARATOR = ",";
+    private static final String JARS_SEPARATOR = ":";
     private static final int ARG_OK = 0;
     private static final int ARG_FAIL_INCOMPLETE_E = -1;
     private static final int ARG_FAIL_INCOMPLETE_C = -2;
@@ -44,7 +54,8 @@
     private Bundle mParams = new Bundle();
     private List<String> mTestClasses = new ArrayList<String>();
     private boolean mDebug;
-    private String mRunner;
+    private String mRunnerClassName;
+    private UiAutomatorTestRunner mRunner;
 
     public RunTestCommand() {
         super("runtest");
@@ -70,8 +81,11 @@
                 break;
         }
         if (mTestClasses.isEmpty()) {
-            System.err.println("Please specify at least one test class to run.");
-            System.exit(ARG_FAIL_NO_CLASS);
+            addTestClassesFromJars();
+            if (mTestClasses.isEmpty()) {
+                System.err.println("No test classes found.");
+                System.exit(ARG_FAIL_NO_CLASS);
+            }
         }
         getRunner().run(mTestClasses, mParams, mDebug);
     }
@@ -85,6 +99,7 @@
         // key is "debug", parameter will determine whether to wait for debugger
         // to attach
         // -c <class name>
+        // -s turns on the simple output format
         // equivalent to -e class <class name>, i.e. passed onto JUnit
         for (int i = 0; i < args.length; i++) {
             if (args[i].equals("-e")) {
@@ -96,7 +111,7 @@
                     } else if (DEBUG_PARAM.equals(key)) {
                         mDebug = "true".equals(value) || "1".equals(value);
                     } else if (RUNNER_PARAM.equals(key)) {
-                        mRunner = value;
+                        mRunnerClassName = value;
                     } else {
                         mParams.putString(key, value);
                     }
@@ -109,6 +124,8 @@
                 } else {
                     return ARG_FAIL_INCOMPLETE_C;
                 }
+            } else if (args[i].equals("-s")) {
+                mParams.putString(OUTPUT_FORMAT_KEY, OUTPUT_SIMPLE);
             } else {
                 return ARG_FAIL_UNSUPPORTED;
             }
@@ -117,26 +134,32 @@
     }
 
     protected UiAutomatorTestRunner getRunner() {
-        if (mRunner == null) {
-            return new UiAutomatorTestRunner();
+        if (mRunner != null) {
+            return mRunner;
+        }
+
+        if (mRunnerClassName == null) {
+            mRunner = new UiAutomatorTestRunner();
+            return mRunner;
         }
         // use reflection to get the runner
         Object o = null;
         try {
-            Class<?> clazz = Class.forName(mRunner);
+            Class<?> clazz = Class.forName(mRunnerClassName);
             o = clazz.newInstance();
         } catch (ClassNotFoundException cnfe) {
-            System.err.println("Cannot find runner: " + mRunner);
+            System.err.println("Cannot find runner: " + mRunnerClassName);
             System.exit(ARG_FAIL_RUNNER);
         } catch (InstantiationException ie) {
-            System.err.println("Cannot instantiate runner: " + mRunner);
+            System.err.println("Cannot instantiate runner: " + mRunnerClassName);
             System.exit(ARG_FAIL_RUNNER);
         } catch (IllegalAccessException iae) {
-            System.err.println("Constructor of runner " + mRunner + " is not accessibile");
+            System.err.println("Constructor of runner " + mRunnerClassName + " is not accessibile");
             System.exit(ARG_FAIL_RUNNER);
         }
         try {
             UiAutomatorTestRunner runner = (UiAutomatorTestRunner)o;
+            mRunner = runner;
             return runner;
         } catch (ClassCastException cce) {
             System.err.println("Specified runner is not subclass of "
@@ -158,6 +181,49 @@
         }
     }
 
+    /**
+     * Add test classes from jars passed on the command line. Use this if nothing was explicitly
+     * specified on the command line.
+     */
+    private void addTestClassesFromJars() {
+        String jars = mParams.getString(JARS_PARAM);
+        if (jars == null) return;
+
+        String[] jarFileNames = jars.split(JARS_SEPARATOR);
+        for (String fileName : jarFileNames) {
+            fileName = fileName.trim();
+            if (fileName.isEmpty()) continue;
+            try {
+                DexFile dexFile = new DexFile(fileName);
+                for(Enumeration<String> e = dexFile.entries(); e.hasMoreElements();) {
+                    String className = e.nextElement();
+                    if (isTestClass(className)) {
+                        mTestClasses.add(className);
+                    }
+                }
+                dexFile.close();
+            } catch (IOException e) {
+                Log.w(LOGTAG, String.format("Could not read %s: %s", fileName, e.getMessage()));
+            }
+        }
+    }
+
+    /**
+     * Tries to determine if a given class is a test class. A test class has to inherit from
+     * UiAutomator test case and it must be a top-level class.
+     * @param className
+     * @return
+     */
+    private boolean isTestClass(String className) {
+        try {
+            Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
+            if (clazz.getEnclosingClass() != null) return false;
+            return getRunner().getTestCaseFilter().accept(clazz);
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
     @Override
     public String detailedOptions() {
         return "    runtest <class spec> [options]\n"
@@ -168,7 +234,8 @@
             + "        specified, separated by space.\n"
             + "      <CLASSES>: a list of test class names to run, separated by comma. To\n"
             + "        a single method, use TestClass#testMethod format. The -e or -c option\n"
-            + "        may be repeated.\n"
+            + "        may be repeated. This option is not required and if not provided then\n"
+            + "        all the tests in provided jars will be run automatically.\n"
             + "    options:\n"
             + "      --nohup: trap SIG_HUP, so test won't terminate even if parent process\n"
             + "               is terminated, e.g. USB is disconnected.\n"
@@ -176,7 +243,8 @@
             + "      -e runner [CLASS]: use specified test runner class instead. If\n"
             + "        unspecified, framework default runner will be used.\n"
             + "      -e <NAME> <VALUE>: other name-value pairs to be passed to test classes.\n"
-            + "        May be repeated.\n";
+            + "        May be repeated.\n"
+            + "      -e outputFormat simple | -s: enabled less verbose JUnit style output.\n";
     }
 
     @Override
@@ -184,4 +252,4 @@
         return "executes UI automation tests";
     }
 
-}
\ No newline at end of file
+}
diff --git a/uiautomator/cmds/uiautomator/uiautomator b/uiautomator/cmds/uiautomator/uiautomator
index 30ff2e8..9aec2c4 100755
--- a/uiautomator/cmds/uiautomator/uiautomator
+++ b/uiautomator/cmds/uiautomator/uiautomator
@@ -61,6 +61,9 @@
 
 # eventually args will be what get passed down to Java code
 args=
+# we also pass the list of jar files, so we can extract class names for tests
+# if they are not explicitly specified
+jars=
 
 # special case pre-processing for 'runtest' command
 if [ "${cmd}" == "runtest" ]; then
@@ -87,7 +90,7 @@
       cmd="help"
       break
     fi
-    CLASSPATH=${CLASSPATH}:${jar}
+    jars=${jars}:${jar}
     # done processing current arg, moving on
     shift
   done
@@ -108,6 +111,10 @@
 fi
 
 args="${cmd} ${args}"
+if [ -n "${jars}" ]; then
+   args="${args} -e jars ${jars}"
+fi
 
+CLASSPATH=${CLASSPATH}:${jars}
 export CLASSPATH
 exec app_process ${base}/bin com.android.commands.uiautomator.Launcher ${args}
diff --git a/uiautomator/library/Android.mk b/uiautomator/library/Android.mk
index a9ad065..d4b745a 100644
--- a/uiautomator/library/Android.mk
+++ b/uiautomator/library/Android.mk
@@ -19,7 +19,7 @@
 uiautomator.core_src_files := $(call all-java-files-under, src)
 uiautomator.core_java_libraries := android.test.runner core-junit
 
-INTERNAL_UIAUTOMATOR_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_api.txt
+uiautomator_internal_api_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/uiautomator_api.txt
 
 ###############################################
 include $(CLEAR_VARS)
@@ -40,7 +40,7 @@
 LOCAL_DROIDDOC_OPTIONS:= \
     -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_uiautomator_intermediates/src \
     -stubpackages com.android.uiautomator.core:com.android.uiautomator.testrunner \
-    -api $(INTERNAL_UIAUTOMATOR_API_FILE) \
+    -api $(uiautomator_internal_api_file) \
     -nodocs
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
@@ -49,7 +49,8 @@
 LOCAL_MODULE := uiautomator-stubs
 
 include $(BUILD_DROIDDOC)
-uiautomator-stubs-stamp := $(full_target)
+uiautomator_stubs_stamp := $(full_target)
+$(uiautomator_internal_api_file) : $(full_target)
 
 ###############################################
 # Build the stub source files into a jar.
@@ -59,10 +60,63 @@
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 include $(BUILD_STATIC_JAVA_LIBRARY)
 # Make sure to run droiddoc first to generate the stub source files.
-$(full_classes_compiled_jar) : $(uiautomator-stubs-stamp)
+$(full_classes_compiled_jar) : $(uiautomator_stubs_stamp)
+uiautomator_stubs_jar := $(full_classes_compiled_jar)
+
+###############################################
+# API check
+# Please refer to build/core/tasks/apicheck.mk.
+uiautomator_api_dir := frameworks/testing/uiautomator/api
+last_released_sdk_version := $(lastword $(call numerically_sort, \
+    $(filter-out current, \
+        $(patsubst $(uiautomator_api_dir)/%.txt,%, $(wildcard $(uiautomator_api_dir)/*.txt)) \
+    )))
+
+checkapi_last_error_level_flags := \
+    -hide 2 -hide 3 -hide 4 -hide 5 -hide 6 -hide 24 -hide 25 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18
+
+# Check that the API we're building hasn't broken the last-released SDK version.
+$(eval $(call check-api, \
+    uiautomator-checkapi-last, \
+    $(uiautomator_api_dir)/$(last_released_sdk_version).txt, \
+    $(uiautomator_internal_api_file), \
+    $(checkapi_last_error_level_flags), \
+    cat $(LOCAL_PATH)/apicheck_msg_last.txt, \
+    $(uiautomator_stubs_jar), \
+    $(uiautomator_stubs_stamp)))
+
+checkapi_current_error_level_flags := \
+    -error 2 -error 3 -error 4 -error 5 -error 6 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+    -error 25
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+    uiautomator-checkapi-current, \
+    $(uiautomator_api_dir)/current.txt, \
+    $(uiautomator_internal_api_file), \
+    $(checkapi_current_error_level_flags), \
+    cat $(LOCAL_PATH)/apicheck_msg_current.txt, \
+    $(uiautomator_stubs_jar), \
+    $(uiautomator_stubs_stamp)))
+
+.PHONY: update-uiautomator-api
+update-uiautomator-api: PRIVATE_API_DIR := $(uiautomator_api_dir)
+update-uiautomator-api: $(uiautomator_internal_api_file) | $(ACP)
+	@echo Copying uiautomator current.txt
+	$(hide) $(ACP) $< $(PRIVATE_API_DIR)/current.txt
 
 ###############################################
 # clean up temp vars
 uiautomator.core_src_files :=
 uiautomator.core_java_libraries :=
-uiautomator-stubs-stamp :=
+uiautomator_stubs_stamp :=
+uiautomator_internal_api_file :=
+uiautomator_stubs_jar :=
+uiautomator_api_dir :=
+checkapi_last_error_level_flags :=
+checkapi_current_error_level_flags :=
diff --git a/uiautomator/library/apicheck_msg_current.txt b/uiautomator/library/apicheck_msg_current.txt
new file mode 100644
index 0000000..989248d
--- /dev/null
+++ b/uiautomator/library/apicheck_msg_current.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+      errors above.
+
+   2) You can update current.txt by executing the following command:
+         make update-uiautomator-api
+
+      To submit the revised current.txt to the main Android repository,
+      you will need approval.
+******************************
+
+
+
diff --git a/uiautomator/library/apicheck_msg_last.txt b/uiautomator/library/apicheck_msg_last.txt
new file mode 100644
index 0000000..2993157
--- /dev/null
+++ b/uiautomator/library/apicheck_msg_last.txt
@@ -0,0 +1,7 @@
+
+******************************
+You have tried to change the API from what has been previously released in
+an SDK.  Please fix the errors listed above.
+******************************
+
+
diff --git a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index 65fcd28..10878e3 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -63,7 +63,8 @@
     /**
      * Using {@link AccessibilityNodeInfo} this method will walk the layout hierarchy
      * and generates an xml dump to the location specified by <code>dumpFile</code>
-     * @param info
+     * @param root The root accessibility node.
+     * @param dumpFile The file to dump to.
      */
     public static void dumpWindowToFile(AccessibilityNodeInfo root, File dumpFile) {
         if (root == null) {
@@ -137,11 +138,11 @@
      * only reduce noise from standard layout classes that may be falsely
      * configured to accept clicks and are also enabled.
      *
-     * @param n
-     * @return
+     * @param node
+     * @return true if node is excluded.
      */
-    private static boolean nafExcludedClass(AccessibilityNodeInfo n) {
-        String className = safeCharSeqToString(n.getClassName());
+    private static boolean nafExcludedClass(AccessibilityNodeInfo node) {
+        String className = safeCharSeqToString(node.getClassName());
         for(String excludedClassName : NAF_EXCLUDED_CLASSES) {
             if(className.endsWith(excludedClassName))
                 return true;
@@ -183,7 +184,7 @@
      * considered by this dumper as acceptable for accessibility.
      *
      * @param node
-     * @return
+     * @return false if node fails the check.
      */
     private static boolean childNafCheck(AccessibilityNodeInfo node) {
         int childCount = node.getChildCount();
diff --git a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java b/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
index 5ba090b..0fc6466 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/InteractionController.java
@@ -162,7 +162,7 @@
      * @param timeout
      * @param waitForAll boolean to indicate whether to wait for any or all events
      * @param eventTypes mask
-     * @return
+     * @return true if events are received, else false if timeout.
      */
     public boolean clickAndWaitForEvents(final int x, final int y, long timeout,
             boolean waitForAll, int eventTypes) {
@@ -255,14 +255,14 @@
      *
      * Most key presses will cause some UI change to occur. If the device is busy, this will
      * block until the device begins to process the key press at which point the call returns
-     * and normal wait for idle processing may begin. If no evens are detected for the
+     * and normal wait for idle processing may begin. If no events are detected for the
      * timeout period specified, the call will return anyway with false.
      *
      * @param keyCode
      * @param metaState
      * @param eventType
      * @param timeout
-     * @return
+     * @return true if events is received, otherwise false.
      */
     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
             final int eventType, long timeout) {
@@ -291,7 +291,7 @@
      * that require stressing the target.
      * @param x
      * @param y
-     * @return
+     * @return true if the click executed successfully
      */
     public boolean click(int x, int y) {
         Log.d(LOG_TAG, "click (" + x + ", " + y + ")");
@@ -428,7 +428,7 @@
      * @param upX
      * @param upY
      * @param steps
-     * @return
+     * @return true if the swipe executed successfully
      */
     public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
         boolean ret = false;
diff --git a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java b/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
index d2938ac..f14b017 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/QueryController.java
@@ -23,10 +23,8 @@
 import com.android.uiautomator.core.UiAutomatorBridge.AccessibilityEventListener;
 
 /**
- * The QuertController main purpose is to translate a {@link UiSelector} selectors to
- * {@link AccessibilityNodeInfo}. This is all this controller does. It is typically
- * created in conjunction with a {@link InteractionController} by {@link UiAutomationContext}
- * which owns both. {@link UiAutomationContext} is used by {@link UiBase} classes.
+ * The QueryController main purpose is to translate a {@link UiSelector} selectors to
+ * {@link AccessibilityNodeInfo}. This is all this controller does.
  */
 class QueryController {
 
@@ -88,7 +86,6 @@
      * Returns the last text selection reported by accessibility
      * event TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY. One way to cause
      * this event is using a DPad arrows to focus on UI elements.
-     * @return
      */
     public String getLastTraversedText() {
         mUiAutomatorBridge.waitForIdle();
@@ -115,7 +112,7 @@
         mPatternCounter = 0;
         mPatternIndexer = 0;
         mLogIndent = 0;
-        mLogParentIndent = 0;;
+        mLogParentIndent = 0;
     }
 
     /**
@@ -134,7 +131,7 @@
     /**
      * Main search method for translating By selectors to AccessibilityInfoNodes
      * @param selector
-     * @return
+     * @return AccessibilityNodeInfo
      */
     public AccessibilityNodeInfo findAccessibilityNodeInfo(UiSelector selector) {
         return findAccessibilityNodeInfo(selector, false);
@@ -212,7 +209,7 @@
      * @param selector
      * @param fromNode
      * @param isCounting
-     * @return
+     * @return AccessibilityNodeInfo
      */
     private AccessibilityNodeInfo translateCompoundSelector(UiSelector selector,
             AccessibilityNodeInfo fromNode, boolean isCounting) {
@@ -282,7 +279,6 @@
      * <p/>
      * @param selector
      * @param fromNode
-     * @param index
      * @return AccessibilityNodeInfo if found else null
      */
     private AccessibilityNodeInfo translateReqularSelector(UiSelector selector,
@@ -369,7 +365,7 @@
      * until the end of the tree.
      * @param subSelector
      * @param fromNode
-     * @param originalPattern
+     * @param isCounting
      * @return null of node is not found or if counting mode is true.
      * See {@link #translateCompoundSelector(UiSelector, AccessibilityNodeInfo, boolean)}
      */
diff --git a/uiautomator/library/src/com/android/uiautomator/core/Tracer.java b/uiautomator/library/src/com/android/uiautomator/core/Tracer.java
new file mode 100644
index 0000000..d574fc0
--- /dev/null
+++ b/uiautomator/library/src/com/android/uiautomator/core/Tracer.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2012 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.uiautomator.core;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Class that creates traces of the calls to the UiAutomator API and outputs the
+ * traces either to logcat or a logfile. Each public method in the UiAutomator
+ * that needs to be traced should include a call to Tracer.trace in the
+ * beginning. Tracing is turned off by defualt and needs to be enabled
+ * explicitly.
+ * @hide
+ */
+public class Tracer {
+    private static final String UNKNOWN_METHOD_STRING = "(unknown method)";
+    private static final String UIAUTOMATOR_PACKAGE = "com.android.uiautomator.core";
+    private static final int CALLER_LOCATION = 6;
+    private static final int METHOD_TO_TRACE_LOCATION = 5;
+    private static final int MIN_STACK_TRACE_LENGTH = 7;
+
+    /**
+     * Enum that determines where the trace output goes. It can go to either
+     * logcat, log file or both.
+     */
+    public enum Mode {
+        NONE,
+        FILE,
+        LOGCAT,
+        ALL
+    }
+
+    private interface TracerSink {
+        public void log(String message);
+
+        public void close();
+    }
+
+    private class FileSink implements TracerSink {
+        private PrintWriter mOut;
+        private SimpleDateFormat mDateFormat;
+
+        public FileSink(File file) throws FileNotFoundException {
+            mOut = new PrintWriter(file);
+            mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
+        }
+
+        public void log(String message) {
+            mOut.printf("%s %s\n", mDateFormat.format(new Date()), message);
+        }
+
+        public void close() {
+            mOut.close();
+        }
+    }
+
+    private class LogcatSink implements TracerSink {
+
+        private static final String LOGCAT_TAG = "UiAutomatorTrace";
+
+        public void log(String message) {
+            Log.i(LOGCAT_TAG, message);
+        }
+
+        public void close() {
+            // nothing is needed
+        }
+    }
+
+    private Mode mCurrentMode = Mode.NONE;
+    private List<TracerSink> mSinks = new ArrayList<TracerSink>();
+    private File mOutputFile;
+
+    private static Tracer mInstance = null;
+
+    /**
+     * Returns a reference to an instance of the tracer. Useful to set the
+     * parameters before the trace is collected.
+     *
+     * @return
+     */
+    public static Tracer getInstance() {
+        if (mInstance == null) {
+            mInstance = new Tracer();
+        }
+        return mInstance;
+    }
+
+    /**
+     * Sets where the trace output will go. Can be either be logcat or a file or
+     * both. Setting this to NONE will turn off tracing.
+     *
+     * @param mode
+     */
+    public void setOutputMode(Mode mode) {
+        closeSinks();
+        mCurrentMode = mode;
+        try {
+            switch (mode) {
+                case FILE:
+                    if (mOutputFile == null) {
+                        throw new IllegalArgumentException("Please provide a filename before " +
+                                "attempting write trace to a file");
+                    }
+                    mSinks.add(new FileSink(mOutputFile));
+                    break;
+                case LOGCAT:
+                    mSinks.add(new LogcatSink());
+                    break;
+                case ALL:
+                    mSinks.add(new LogcatSink());
+                    if (mOutputFile == null) {
+                        throw new IllegalArgumentException("Please provide a filename before " +
+                                "attempting write trace to a file");
+                    }
+                    mSinks.add(new FileSink(mOutputFile));
+                    break;
+                default:
+                    break;
+            }
+        } catch (FileNotFoundException e) {
+            Log.w("Tracer", "Could not open log file: " + e.getMessage());
+        }
+    }
+
+    private void closeSinks() {
+        for (TracerSink sink : mSinks) {
+            sink.close();
+        }
+        mSinks.clear();
+    }
+
+    /**
+     * Sets the name of the log file where tracing output will be written if the
+     * tracer is set to write to a file.
+     *
+     * @param filename name of the log file.
+     */
+    public void setOutputFilename(String filename) {
+        mOutputFile = new File(filename);
+    }
+
+    private void doTrace(Object[] arguments) {
+        if (mCurrentMode == Mode.NONE) {
+            return;
+        }
+
+        String caller = getCaller();
+        if (caller == null) {
+            return;
+        }
+
+        log(String.format("%s (%s)", caller, join(", ", arguments)));
+    }
+
+    private void log(String message) {
+        for (TracerSink sink : mSinks) {
+            sink.log(message);
+        }
+    }
+
+    /**
+     * Queries whether the tracing is enabled.
+     * @return true if tracing is enabled, false otherwise.
+     */
+    public boolean isTracingEnabled() {
+        return mCurrentMode != Mode.NONE;
+    }
+
+    /**
+     * Public methods in the UiAutomator should call this function to generate a
+     * trace. The trace will include the method thats is being called, it's
+     * arguments and where in the user's code the method is called from. If a
+     * public method is called internally from UIAutomator then this will not
+     * output a trace entry. Only calls from outise the UiAutomator package will
+     * produce output.
+     *
+     * Special note about array arguments. You can safely pass arrays of reference types
+     * to this function. Like String[] or Integer[]. The trace function will print their
+     * contents by calling toString() on each of the elements. This will not work for
+     * array of primitive types like int[] or float[]. Before passing them to this function
+     * convert them to arrays of reference types manually. Example: convert int[] to Integer[].
+     *
+     * @param arguments arguments of the method being traced.
+     */
+    public static void trace(Object... arguments) {
+        Tracer.getInstance().doTrace(arguments);
+    }
+
+    private static String join(String separator, Object[] strings) {
+        if (strings.length == 0)
+            return "";
+
+        StringBuilder builder = new StringBuilder(objectToString(strings[0]));
+        for (int i = 1; i < strings.length; i++) {
+            builder.append(separator);
+            builder.append(objectToString(strings[i]));
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Special toString method to handle arrays. If the argument is a normal object then this will
+     * return normal output of obj.toString(). If the argument is an array this will return a
+     * string representation of the elements of the array.
+     *
+     * This method will not work for arrays of primitive types. Arrays of primitive types are
+     * expected to be converted manually by the caller. If the array is not converter then
+     * this function will only output "[...]" instead of the contents of the array.
+     *
+     * @param obj object to convert to a string
+     * @return String representation of the object.
+     */
+    private static String objectToString(Object obj) {
+        if (obj.getClass().isArray()) {
+            if (obj instanceof Object[]) {
+                return Arrays.deepToString((Object[])obj);
+            } else {
+                return "[...]";
+            }
+        } else {
+            return obj.toString();
+        }
+    }
+
+    /**
+     * This method outputs which UiAutomator method was called and where in the
+     * user code it was called from. If it can't deside which method is called
+     * it will output "(unknown method)". If the method was called from inside
+     * the UiAutomator then it returns null.
+     *
+     * @return name of the method called and where it was called from. Null if
+     *         method was called from inside UiAutomator.
+     */
+    private static String getCaller() {
+        StackTraceElement stackTrace[] = Thread.currentThread().getStackTrace();
+        if (stackTrace.length < MIN_STACK_TRACE_LENGTH) {
+            return UNKNOWN_METHOD_STRING;
+        }
+
+        StackTraceElement caller = stackTrace[METHOD_TO_TRACE_LOCATION];
+        StackTraceElement previousCaller = stackTrace[CALLER_LOCATION];
+
+        if (previousCaller.getClassName().startsWith(UIAUTOMATOR_PACKAGE)) {
+            return null;
+        }
+
+        int indexOfDot = caller.getClassName().lastIndexOf('.');
+        if (indexOfDot < 0) {
+            indexOfDot = 0;
+        }
+
+        if (indexOfDot + 1 >= caller.getClassName().length()) {
+            return UNKNOWN_METHOD_STRING;
+        }
+
+        String shortClassName = caller.getClassName().substring(indexOfDot + 1);
+        return String.format("%s.%s from %s() at %s:%d", shortClassName, caller.getMethodName(),
+                previousCaller.getMethodName(), previousCaller.getFileName(),
+                previousCaller.getLineNumber());
+    }
+}
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiCollection.java b/uiautomator/library/src/com/android/uiautomator/core/UiCollection.java
index 20b6d9a..e15beb2 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiCollection.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiCollection.java
@@ -49,6 +49,7 @@
      */
     public UiObject getChildByDescription(UiSelector childPattern, String text)
             throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, text);
         if (text != null) {
             int count = getChildCount(childPattern);
             for (int x = 0; x < count; x++) {
@@ -82,6 +83,7 @@
      */
     public UiObject getChildByInstance(UiSelector childPattern, int instance)
             throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, instance);
         UiSelector patternSelector = UiSelector.patternBuilder(getSelector(),
                 UiSelector.patternBuilder(childPattern).instance(instance));
         return new UiObject(patternSelector);
@@ -105,7 +107,7 @@
      */
     public UiObject getChildByText(UiSelector childPattern, String text)
             throws UiObjectNotFoundException {
-
+        Tracer.trace(childPattern, text);
         if (text != null) {
             int count = getChildCount(childPattern);
             for (int x = 0; x < count; x++) {
@@ -135,6 +137,7 @@
      * @since API Level 16
      */
     public int getChildCount(UiSelector childPattern) {
+        Tracer.trace(childPattern);
         UiSelector patternSelector =
                 UiSelector.patternBuilder(getSelector(), UiSelector.patternBuilder(childPattern));
         return getQueryController().getPatternCount(patternSelector);
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java b/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
index 09d8af3..b668bea 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiDevice.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
@@ -112,6 +113,7 @@
      * @hide
      */
     public Point getDisplaySizeDp() {
+        Tracer.trace();
         Display display = getDefaultDisplay();
         Point p = new Point();
         display.getSize(p);
@@ -134,6 +136,7 @@
      * @since API Level 17
      */
     public String getProductName() {
+        Tracer.trace();
         return Build.PRODUCT;
     }
 
@@ -153,6 +156,7 @@
      * @since API Level 16
      */
     public String getLastTraversedText() {
+        Tracer.trace();
         return mUiAutomationBridge.getQueryController().getLastTraversedText();
     }
 
@@ -162,6 +166,7 @@
      * @since API Level 16
      */
     public void clearLastTraversedText() {
+        Tracer.trace();
         mUiAutomationBridge.getQueryController().clearLastTraversedText();
     }
 
@@ -171,6 +176,7 @@
      * @since API Level 16
      */
     public boolean pressMenu() {
+        Tracer.trace();
         waitForIdle();
         return mUiAutomationBridge.getInteractionController().sendKeyAndWaitForEvent(
                 KeyEvent.KEYCODE_MENU, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
@@ -183,6 +189,7 @@
      * @since API Level 16
      */
     public boolean pressBack() {
+        Tracer.trace();
         waitForIdle();
         return mUiAutomationBridge.getInteractionController().sendKeyAndWaitForEvent(
                 KeyEvent.KEYCODE_BACK, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
@@ -195,6 +202,7 @@
      * @since API Level 16
      */
     public boolean pressHome() {
+        Tracer.trace();
         waitForIdle();
         return mUiAutomationBridge.getInteractionController().sendKeyAndWaitForEvent(
                 KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
@@ -207,6 +215,7 @@
      * @since API Level 16
      */
     public boolean pressSearch() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_SEARCH);
     }
 
@@ -216,6 +225,7 @@
      * @since API Level 16
      */
     public boolean pressDPadCenter() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
     }
 
@@ -225,6 +235,7 @@
      * @since API Level 16
      */
     public boolean pressDPadDown() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
     }
 
@@ -234,6 +245,7 @@
      * @since API Level 16
      */
     public boolean pressDPadUp() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DPAD_UP);
     }
 
@@ -243,6 +255,7 @@
      * @since API Level 16
      */
     public boolean pressDPadLeft() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DPAD_LEFT);
     }
 
@@ -252,6 +265,7 @@
      * @since API Level 16
      */
     public boolean pressDPadRight() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DPAD_RIGHT);
     }
 
@@ -261,6 +275,7 @@
      * @since API Level 16
      */
     public boolean pressDelete() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_DEL);
     }
 
@@ -270,6 +285,7 @@
      * @since API Level 16
      */
     public boolean pressEnter() {
+        Tracer.trace();
         return pressKeyCode(KeyEvent.KEYCODE_ENTER);
     }
 
@@ -281,6 +297,7 @@
      * @since API Level 16
      */
     public boolean pressKeyCode(int keyCode) {
+        Tracer.trace(keyCode);
         waitForIdle();
         return mUiAutomationBridge.getInteractionController().sendKey(keyCode, 0);
     }
@@ -295,6 +312,7 @@
      * @since API Level 16
      */
     public boolean pressKeyCode(int keyCode, int metaState) {
+        Tracer.trace(keyCode, metaState);
         waitForIdle();
         return mUiAutomationBridge.getInteractionController().sendKey(keyCode, metaState);
     }
@@ -307,6 +325,7 @@
      * @since API Level 16
      */
     public boolean pressRecentApps() throws RemoteException {
+        Tracer.trace();
         waitForIdle();
         final IStatusBarService statusBar = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -325,6 +344,7 @@
      * @since API Level 16
      */
     public int getDisplayWidth() {
+        Tracer.trace();
         Display display = getDefaultDisplay();
         Point p = new Point();
         display.getSize(p);
@@ -338,6 +358,7 @@
      * @since API Level 16
      */
     public int getDisplayHeight() {
+        Tracer.trace();
         Display display = getDefaultDisplay();
         Point p = new Point();
         display.getSize(p);
@@ -353,6 +374,7 @@
      * @since API Level 16
      */
     public boolean click(int x, int y) {
+        Tracer.trace(x, y);
         if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
             return (false);
         }
@@ -373,6 +395,7 @@
      * @since API Level 16
      */
     public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
+        Tracer.trace(startX, startY, endX, endY, steps);
         return mUiAutomationBridge.getInteractionController()
                 .scrollSwipe(startX, startY, endX, endY, steps);
     }
@@ -387,6 +410,7 @@
      * @since API Level 16
      */
     public boolean swipe(Point[] segments, int segmentSteps) {
+        Tracer.trace(segments, segmentSteps);
         return mUiAutomationBridge.getInteractionController().swipe(segments, segmentSteps);
     }
 
@@ -396,6 +420,7 @@
      * @since API Level 16
      */
     public void waitForIdle() {
+        Tracer.trace();
         waitForIdle(DEFAULT_TIMEOUT_MILLIS);
     }
 
@@ -404,8 +429,9 @@
      * @param timeout in milliseconds
      * @since API Level 16
      */
-    public void waitForIdle(long time) {
-        mUiAutomationBridge.waitForIdle(time);
+    public void waitForIdle(long timeout) {
+        Tracer.trace(timeout);
+        mUiAutomationBridge.waitForIdle(timeout);
     }
 
     /**
@@ -416,6 +442,7 @@
      */
     @Deprecated
     public String getCurrentActivityName() {
+        Tracer.trace();
         return mUiAutomationBridge.getQueryController().getCurrentActivityName();
     }
 
@@ -425,6 +452,7 @@
      * @since API Level 16
      */
     public String getCurrentPackageName() {
+        Tracer.trace();
         return mUiAutomationBridge.getQueryController().getCurrentPackageName();
     }
 
@@ -437,6 +465,7 @@
      * @since API Level 16
      */
     public void registerWatcher(String name, UiWatcher watcher) {
+        Tracer.trace(name, watcher);
         if (mInWatcherContext) {
             throw new IllegalStateException("Cannot register new watcher from within another");
         }
@@ -448,10 +477,10 @@
      *
      * See {@link #registerWatcher(String, UiWatcher)}
      * @param name used to register the UiWatcher
-     * @throws UiAutomationException
      * @since API Level 16
      */
     public void removeWatcher(String name) {
+        Tracer.trace(name);
         if (mInWatcherContext) {
             throw new IllegalStateException("Cannot remove a watcher from within another");
         }
@@ -464,6 +493,7 @@
      * @since API Level 16
      */
     public void runWatchers() {
+        Tracer.trace();
         if (mInWatcherContext) {
             return;
         }
@@ -493,6 +523,7 @@
      * @since API Level 16
      */
     public void resetWatcherTriggers() {
+        Tracer.trace();
         mWatchersTriggers.clear();
     }
 
@@ -508,6 +539,7 @@
      * @since API Level 16
      */
     public boolean hasWatcherTriggered(String watcherName) {
+        Tracer.trace(watcherName);
         return mWatchersTriggers.contains(watcherName);
     }
 
@@ -519,6 +551,7 @@
      * @since API Level 16
      */
     public boolean hasAnyWatcherTriggered() {
+        Tracer.trace();
         return mWatchersTriggers.size() > 0;
     }
 
@@ -527,6 +560,7 @@
      * @param watcherName
      */
     private void setWatcherTriggered(String watcherName) {
+        Tracer.trace(watcherName);
         if (!hasWatcherTriggered(watcherName)) {
             mWatchersTriggers.add(watcherName);
         }
@@ -539,6 +573,7 @@
      * @since API Level 17
      */
     public boolean isNaturalOrientation() {
+        Tracer.trace();
         Display display = getDefaultDisplay();
         return display.getRotation() == Surface.ROTATION_0 ||
                 display.getRotation() == Surface.ROTATION_180;
@@ -546,10 +581,10 @@
 
     /**
      * Returns the current rotation of the display, as defined in {@link Surface}
-     * @return
      * @since API Level 17
      */
     public int getDisplayRotation() {
+        Tracer.trace();
         return getDefaultDisplay().getRotation();
     }
 
@@ -560,6 +595,7 @@
      * @since API Level 16
      */
     public void freezeRotation() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().freezeRotation();
     }
 
@@ -570,6 +606,7 @@
      * @throws RemoteException
      */
     public void unfreezeRotation() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().unfreezeRotation();
     }
 
@@ -583,6 +620,7 @@
      * @since API Level 17
      */
     public void setOrientationLeft() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationLeft();
     }
 
@@ -596,6 +634,7 @@
      * @since API Level 17
      */
     public void setOrientationRight() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationRight();
     }
 
@@ -609,6 +648,7 @@
      * @since API Level 17
      */
     public void setOrientationNatural() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationNatural();
     }
 
@@ -622,6 +662,7 @@
      * @since API Level 16
      */
     public void wakeUp() throws RemoteException {
+        Tracer.trace();
         if(getAutomatorBridge().getInteractionController().wakeDevice()) {
             // sync delay to allow the window manager to start accepting input
             // after the device is awakened.
@@ -637,6 +678,7 @@
      * @since API Level 16
      */
     public boolean isScreenOn() throws RemoteException {
+        Tracer.trace();
         return getAutomatorBridge().getInteractionController().isScreenOn();
     }
 
@@ -648,6 +690,7 @@
      * @since API Level 16
      */
     public void sleep() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().sleepDevice();
     }
 
@@ -659,6 +702,7 @@
      * @since API Level 16
      */
     public void dumpWindowHierarchy(String fileName) {
+        Tracer.trace(fileName);
         AccessibilityNodeInfo root =
                 getAutomatorBridge().getQueryController().getAccessibilityRootNode();
         if(root != null) {
@@ -683,6 +727,7 @@
      * @since API Level 16
      */
     public boolean waitForWindowUpdate(final String packageName, long timeout) {
+        Tracer.trace(packageName, timeout);
         if (packageName != null) {
             if (!packageName.equals(getCurrentPackageName())) {
                 return false;
@@ -744,6 +789,7 @@
      * @since API Level 17
      */
     public boolean takeScreenshot(File storePath) {
+        Tracer.trace(storePath);
         return takeScreenshot(storePath, 1.0f, 90);
     }
 
@@ -759,6 +805,7 @@
      * @since API Level 17
      */
     public boolean takeScreenshot(File storePath, float scale, int quality) {
+        Tracer.trace(storePath, scale, quality);
         // This is from com.android.systemui.screenshot.GlobalScreenshot#takeScreenshot
         // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
         // only in the natural orientation of the device :!)
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiObject.java b/uiautomator/library/src/com/android/uiautomator/core/UiObject.java
index 42819e2..2bf6455 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiObject.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiObject.java
@@ -77,6 +77,7 @@
      * @since API Level 16
      */
     public final UiSelector getSelector() {
+        Tracer.trace();
         return new UiSelector(mSelector);
     }
 
@@ -109,6 +110,7 @@
      * @since API Level 16
      */
     public UiObject getChild(UiSelector selector) throws UiObjectNotFoundException {
+        Tracer.trace(selector);
         return new UiObject(getSelector().childSelector(selector));
     }
 
@@ -124,6 +126,7 @@
      * @since API Level 16
      */
     public UiObject getFromParent(UiSelector selector) throws UiObjectNotFoundException {
+        Tracer.trace(selector);
         return new UiObject(getSelector().fromParent(selector));
     }
 
@@ -136,6 +139,7 @@
      * @since API Level 16
      */
     public int getChildCount() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -147,7 +151,6 @@
      * Uses the member UiSelector properties to find a matching UI element reported in
      * the accessibility hierarchy.
      *
-     * @param selector {@link UiSelector}
      * @param timeout in milliseconds
      * @return AccessibilityNodeInfo if found else null
      * @since API Level 16
@@ -181,8 +184,8 @@
 
     /**
      * Perform the action on the UI element that is represented by this UiObject. Also see
-     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
-     * {@link #scrollForward()}.
+     * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
+     * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}.
      *
      * @param steps indicates the number of injected move steps into the system. Steps are
      * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
@@ -191,6 +194,7 @@
      * @since API Level 16
      */
     public boolean swipeUp(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Rect rect = getVisibleBounds();
         if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
             return false; // too small to swipe
@@ -201,10 +205,11 @@
 
     /**
      * Perform the action on the UI element that is represented by this object, Also see
-     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
-     * {@link #scrollForward()}. This method will perform the swipe gesture over any
-     * surface. The targeted UI element does not need to have the attribute
-     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
+     * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
+     * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}. This method will
+     * perform the swipe gesture over any surface.  The targeted UI element does not need to have
+     * the attribute <code>scrollable</code> set to <code>true</code> for this operation to be
+     * performed.
      *
      * @param steps indicates the number of injected move steps into the system. Steps are
      * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
@@ -213,6 +218,7 @@
      * @since API Level 16
      */
     public boolean swipeDown(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Rect rect = getVisibleBounds();
         if(rect.height() <= SWIPE_MARGIN_LIMIT * 2)
             return false; // too small to swipe
@@ -223,10 +229,11 @@
 
     /**
      * Perform the action on the UI element that is represented by this object. Also see
-     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
-     * {@link #scrollForward()}. This method will perform the swipe gesture over any
-     * surface. The targeted UI element does not need to have the attribute
-     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
+     * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
+     * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}. This method will
+     * perform the swipe gesture over any surface. The targeted UI element does not need to have the
+     * attribute <code>scrollable</code> set to <code>true</code> for this operation to be
+     * performed.
      *
      * @param steps indicates the number of injected move steps into the system. Steps are
      * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
@@ -235,6 +242,7 @@
      * @since API Level 16
      */
     public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Rect rect = getVisibleBounds();
         if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
             return false; // too small to swipe
@@ -244,10 +252,11 @@
 
     /**
      * Perform the action on the UI element that is represented by this object. Also see
-     * {@link #scrollToBeginning(int)}, {@link #scrollToEnd(int)}, {@link #scrollBackward()},
-     * {@link #scrollForward()}. This method will perform the swipe gesture over any
-     * surface. The targeted UI element does not need to have the attribute
-     * <code>scrollable</code> set to <code>true</code> for this operation to be performed.
+     * {@link UiScrollable#scrollToBeginning(int)}, {@link UiScrollable#scrollToEnd(int)},
+     * {@link UiScrollable#scrollBackward()}, {@link UiScrollable#scrollForward()}. This method will
+     * perform the swipe gesture over any surface. The targeted UI element does not need to have the
+     * attribute <code>scrollable</code> set to <code>true</code> for this operation to be
+     * performed.
      *
      * @param steps indicates the number of injected move steps into the system. Steps are
      * injected about 5ms apart. So a 100 steps may take about 1/2 second to complete.
@@ -256,6 +265,7 @@
      * @since API Level 16
      */
     public boolean swipeRight(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Rect rect = getVisibleBounds();
         if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
             return false; // too small to swipe
@@ -299,7 +309,7 @@
      * adjustments should be made to the click coordinates.
      *
      * @param node
-     * @return
+     * @return The accessibility node info.
      */
     private AccessibilityNodeInfo getScrollableParent(AccessibilityNodeInfo node) {
         AccessibilityNodeInfo parent = node;
@@ -321,6 +331,7 @@
      * @since API Level 16
      */
     public boolean click() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -341,6 +352,7 @@
      * @since API Level 16
      */
     public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
+        Tracer.trace();
         return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
     }
 
@@ -360,6 +372,7 @@
      * @since API Level 16
      */
     public boolean clickAndWaitForNewWindow(long timeout) throws UiObjectNotFoundException {
+        Tracer.trace(timeout);
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -373,10 +386,11 @@
      * Clicks the top and left corner of the UI element
      *
      * @return true on success
-     * @throws Exception
+     * @throws UiObjectNotFoundException
      * @since API Level 16
      */
     public boolean clickTopLeft() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -393,6 +407,7 @@
      * @since API Level 16
      */
     public boolean longClickBottomRight() throws UiObjectNotFoundException  {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -405,10 +420,11 @@
      * Clicks the bottom and right corner of the UI element
      *
      * @return true on success
-     * @throws Exception
+     * @throws UiObjectNotFoundException
      * @since API Level 16
      */
     public boolean clickBottomRight() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -425,6 +441,7 @@
      * @since API Level 16
      */
     public boolean longClick() throws UiObjectNotFoundException  {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -441,6 +458,7 @@
      * @since API Level 16
      */
     public boolean longClickTopLeft() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -457,6 +475,7 @@
      * @since API Level 16
      */
     public String getText() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -474,6 +493,7 @@
      * @since API Level 16
      */
     public String getContentDescription() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -499,6 +519,7 @@
      * @since API Level 16
      */
     public boolean setText(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
         clearTextField();
         return getInteractionController().sendText(text);
     }
@@ -523,6 +544,7 @@
      * @since API Level 16
      */
     public void clearTextField() throws UiObjectNotFoundException {
+        Tracer.trace();
         // long click left + center
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
@@ -547,6 +569,7 @@
      * @since API Level 16
      */
     public boolean isChecked() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -562,6 +585,7 @@
      * @since API Level 16
      */
     public boolean isSelected() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -577,6 +601,7 @@
      * @since API Level 16
      */
     public boolean isCheckable() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -592,6 +617,7 @@
      * @since API Level 16
      */
     public boolean isEnabled() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -607,6 +633,7 @@
      * @since API Level 16
      */
     public boolean isClickable() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -622,6 +649,7 @@
      * @since API Level 16
      */
     public boolean isFocused() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -637,6 +665,7 @@
      * @since API Level 16
      */
     public boolean isFocusable() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -652,6 +681,7 @@
      * @since API Level 16
      */
     public boolean isScrollable() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -667,6 +697,7 @@
      * @since API Level 16
      */
     public boolean isLongClickable() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -682,6 +713,7 @@
      * @since API Level 16
      */
     public String getPackageName() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -697,10 +729,11 @@
      *
      * @return Rect
      * @throws UiObjectNotFoundException
-     * @see {@link #getBound()}
+     * @see {@link #getBounds()}
      * @since API Level 17
      */
     public Rect getVisibleBounds() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -716,6 +749,7 @@
      * @since API Level 16
      */
     public Rect getBounds() throws UiObjectNotFoundException {
+        Tracer.trace();
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
@@ -738,6 +772,7 @@
      * @since API Level 16
      */
     public boolean waitForExists(long timeout) {
+        Tracer.trace(timeout);
         if(findAccessibilityNodeInfo(timeout) != null) {
             return true;
         }
@@ -763,6 +798,7 @@
      * @since API Level 16
      */
     public boolean waitUntilGone(long timeout) {
+        Tracer.trace(timeout);
         long startMills = SystemClock.uptimeMillis();
         long currentMills = 0;
         while (currentMills <= timeout) {
@@ -787,6 +823,7 @@
      * @since API Level 16
      */
     public boolean exists() {
+        Tracer.trace();
         return waitForExists(0);
     }
 
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java b/uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java
index 607ae6f..c128ac2 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiScrollable.java
@@ -63,6 +63,7 @@
      * @since API Level 16
      */
     public UiScrollable setAsVerticalList() {
+        Tracer.trace();
         mIsVerticalList = true;
         return this;
     }
@@ -73,6 +74,7 @@
      * @since API Level 16
      */
     public UiScrollable setAsHorizontalList() {
+        Tracer.trace();
         mIsVerticalList = false;
         return this;
     }
@@ -110,6 +112,7 @@
     @Override
     public UiObject getChildByDescription(UiSelector childPattern, String text)
             throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, text);
         return getChildByDescription(childPattern, text, true);
     }
 
@@ -125,6 +128,7 @@
      */
     public UiObject getChildByDescription(UiSelector childPattern, String text,
             boolean allowScrollSearch) throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, text, allowScrollSearch);
         if (text != null) {
             if (allowScrollSearch) {
                 scrollIntoView(new UiSelector().descriptionContains(text));
@@ -148,6 +152,7 @@
     @Override
     public UiObject getChildByInstance(UiSelector childPattern, int instance)
             throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, instance);
         UiSelector patternSelector = UiSelector.patternBuilder(getSelector(),
                 UiSelector.patternBuilder(childPattern).instance(instance));
         return new UiObject(patternSelector);
@@ -172,6 +177,7 @@
     @Override
     public UiObject getChildByText(UiSelector childPattern, String text)
             throws UiObjectNotFoundException {
+        Tracer.trace(childPattern, text);
         return getChildByText(childPattern, text, true);
     }
 
@@ -187,7 +193,7 @@
      */
     public UiObject getChildByText(UiSelector childPattern, String text, boolean allowScrollSearch)
             throws UiObjectNotFoundException {
-
+        Tracer.trace(childPattern, text, allowScrollSearch);
         if (text != null) {
             if (allowScrollSearch) {
                 scrollIntoView(new UiSelector().text(text));
@@ -206,6 +212,7 @@
      * @since API Level 16
      */
     public boolean scrollDescriptionIntoView(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
         return scrollIntoView(new UiSelector().description(text));
     }
 
@@ -218,6 +225,7 @@
      * @since API Level 16
      */
     public boolean scrollIntoView(UiObject obj) throws UiObjectNotFoundException {
+        Tracer.trace(obj.getSelector());
         return scrollIntoView(obj.getSelector());
     }
 
@@ -230,6 +238,7 @@
      * @since API Level 16
      */
     public boolean scrollIntoView(UiSelector selector) throws UiObjectNotFoundException {
+        Tracer.trace(selector);
         // if we happen to be on top of the text we want then return here
         if (exists(getSelector().childSelector(selector))) {
             return (true);
@@ -261,34 +270,39 @@
      * @since API Level 16
      */
     public boolean scrollTextIntoView(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
         return scrollIntoView(new UiSelector().text(text));
     }
 
     /**
-     * {@link #getChildByDescription(String, boolean)} and {@link #getChildByText(String, boolean)}
-     * use an arguments that specifies if scrolling is allowed while searching for the UI element.
-     * The number of scrolls allowed to perform a search can be modified by this method.
-     * The current value can be read by calling {@link #getMaxSearchSwipes()}
+     * {@link #getChildByDescription(UiSelector, String)} and
+     * {@link #getChildByText(UiSelector, String)} use an arguments that specifies if scrolling is
+     * allowed while searching for the UI element.  The number of scrolls allowed to perform a
+     * search can be modified by this method.  The current value can be read by calling
+     * {@link #getMaxSearchSwipes()}
      *
      * @param swipes is the number of search swipes until abort
      * @return reference to itself
      * @since API Level 16
      */
     public UiScrollable setMaxSearchSwipes(int swipes) {
+        Tracer.trace(swipes);
         mMaxSearchSwipes = swipes;
         return this;
     }
 
     /**
-     * {@link #getChildByDescription(String, boolean)} and {@link #getChildByText(String, boolean)}
-     * use an arguments that specifies if scrolling is allowed while searching for the UI element.
-     * The number of scrolls currently allowed to perform a search can be read by this method.
+     * {@link #getChildByDescription(UiSelector, String)} and
+     * {@link #getChildByText(UiSelector, String)} use an arguments that specifies if scrolling is
+     * allowed while searching for the UI element.  The number of scrolls currently allowed to
+     * perform a search can be read by this method.
      * See {@link #setMaxSearchSwipes(int)}
      *
      * @return max value of the number of swipes currently allowed during a scroll search
      * @since API Level 16
      */
     public int getMaxSearchSwipes() {
+        Tracer.trace();
         return mMaxSearchSwipes;
     }
 
@@ -299,6 +313,7 @@
      * @since API Level 16
      */
     public boolean flingForward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollForward(FLING_STEPS);
     }
 
@@ -309,6 +324,7 @@
      * @since API Level 16
      */
     public boolean scrollForward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollForward(SCROLL_STEPS);
     }
 
@@ -324,12 +340,13 @@
      * @since API Level 16
      */
     public boolean scrollForward(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Log.d(LOG_TAG, "scrollForward() on selector = " + getSelector());
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if(node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
         }
-        Rect rect = new Rect();;
+        Rect rect = new Rect();
         node.getBoundsInScreen(rect);
 
         int downX = 0;
@@ -365,6 +382,7 @@
      * @since API Level 16
      */
     public boolean flingBackward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollBackward(FLING_STEPS);
     }
 
@@ -375,6 +393,7 @@
      * @since API Level 16
      */
     public boolean scrollBackward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollBackward(SCROLL_STEPS);
     }
 
@@ -390,12 +409,13 @@
      * @since API Level 16
      */
     public boolean scrollBackward(int steps) throws UiObjectNotFoundException {
+        Tracer.trace(steps);
         Log.d(LOG_TAG, "scrollBackward() on selector = " + getSelector());
         AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
         if (node == null) {
             throw new UiObjectNotFoundException(getSelector().toString());
         }
-        Rect rect = new Rect();;
+        Rect rect = new Rect();
         node.getBoundsInScreen(rect);
 
         int downX = 0;
@@ -436,6 +456,7 @@
      * @since API Level 16
      */
     public boolean scrollToBeginning(int maxSwipes, int steps) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes, steps);
         Log.d(LOG_TAG, "scrollToBeginning() on selector = " + getSelector());
         // protect against potential hanging and return after preset attempts
         for(int x = 0; x < maxSwipes; x++) {
@@ -454,6 +475,7 @@
      * @since API Level 16
      */
     public boolean scrollToBeginning(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToBeginning(maxSwipes, SCROLL_STEPS);
     }
 
@@ -465,6 +487,7 @@
      * @since API Level 16
      */
     public boolean flingToBeginning(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToBeginning(maxSwipes, FLING_STEPS);
     }
 
@@ -478,6 +501,7 @@
      * @since API Level 16
      */
     public boolean scrollToEnd(int maxSwipes, int steps) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes, steps);
         // protect against potential hanging and return after preset attempts
         for(int x = 0; x < maxSwipes; x++) {
             if(!scrollForward(steps)) {
@@ -488,13 +512,14 @@
     }
 
     /**
-     * See {@link UiScrollable#scrollToEnd(int, int)
+     * See {@link UiScrollable#scrollToEnd(int, int)}
      *
      * @param maxSwipes
      * @return true on scrolled else false
      * @since API Level 16
      */
     public boolean scrollToEnd(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToEnd(maxSwipes, SCROLL_STEPS);
     }
 
@@ -506,6 +531,7 @@
      * @since API Level 16
      */
     public boolean flingToEnd(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToEnd(maxSwipes, FLING_STEPS);
     }
 
@@ -524,6 +550,7 @@
      * @since API Level 16
      */
     public double getSwipeDeadZonePercentage() {
+        Tracer.trace();
         return mSwipeDeadZonePercentage;
     }
 
@@ -543,6 +570,7 @@
      * @since API Level 16
      */
     public UiScrollable setSwipeDeadZonePercentage(double swipeDeadZonePercentage) {
+        Tracer.trace(swipeDeadZonePercentage);
         mSwipeDeadZonePercentage = swipeDeadZonePercentage;
         return this;
     }
diff --git a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java b/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
index d240ce9..8963c38 100644
--- a/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
+++ b/uiautomator/library/src/com/android/uiautomator/core/UiSelector.java
@@ -121,7 +121,7 @@
      * The text for the widget must match exactly
      * with the string in your input argument.
      *
-     * @param regular expression
+     * @param regex a regular expression
      * @return UiSelector with the specified search criteria
      * @since API Level 17
      */
@@ -176,7 +176,7 @@
      * Set the search criteria to match the class property
      * for a widget (for example, "android.widget.Button").
      *
-     * @param regular expression
+     * @param regex a regular expression
      * @return UiSelector with the specified search criteria
      * @since API Level 17
      */
@@ -188,7 +188,7 @@
      * Set the search criteria to match the class property
      * for a widget (for example, "android.widget.Button").
      *
-     * @param class type
+     * @param type type
      * @return UiSelector with the specified search criteria
      * @since API Level 17
      */
@@ -228,7 +228,7 @@
      * for the widget must match exactly
      * with the string in your input argument.
      *
-     * @param regular expression
+     * @param regex a regular expression
      * @return UiSelector with the specified search criteria
      * @since API Level 17
      */
@@ -529,7 +529,7 @@
      * Set the search criteria to match the package name
      * of the application that contains the widget.
      *
-     * @param regular expression
+     * @param regex a regular expression
      * @return UiSelector with the specified search criteria
      * @since API Level 17
      */
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/IAutomationSupport.java b/uiautomator/library/src/com/android/uiautomator/testrunner/IAutomationSupport.java
index bea9e9a..f0c60d2 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/IAutomationSupport.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/IAutomationSupport.java
@@ -28,7 +28,8 @@
     /**
      * Allows the running test cases to send out interim status
      *
-     * @param bundle status report, consisting of key value pairs
+     * @param resultCode
+     * @param status status report, consisting of key value pairs
      * @since API Level 16
      */
     public void sendStatus(int resultCode, Bundle status);
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java b/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java
index ae27838..9fc0b0b 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/TestCaseCollector.java
@@ -16,7 +16,10 @@
 
 package com.android.uiautomator.testrunner;
 
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
 import junit.framework.TestCase;
+import junit.framework.TestResult;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -59,7 +62,7 @@
      *
      * The class name may be in "<class name>#<method name>" format
      *
-     * @param classNames classes must be subclass of {@link UiAutomatorTestCase}
+     * @param className classes must be subclass of {@link UiAutomatorTestCase}
      * @throws ClassNotFoundException
      */
     public void addTestClass(String className) throws ClassNotFoundException {
@@ -96,8 +99,7 @@
 
     /**
      * Gets the list of added test cases so far
-     *
-     * @return
+     * @return a list of {@link TestCase}
      */
     public List<TestCase> getTestCases() {
         return Collections.unmodifiableList(mTestCases);
@@ -112,12 +114,23 @@
             testCase.setName(method);
             mTestCases.add(testCase);
         } catch (InstantiationException e) {
-            throw new RuntimeException("Could not instantiate test class. Class: "
-                    + clazz.getName());
+            mTestCases.add(error(clazz, "InstantiationException: could not instantiate " +
+                    "test class. Class: " + clazz.getName()));
         } catch (IllegalAccessException e) {
-            throw new RuntimeException("Could not access test class. Class: " + clazz.getName());
+            mTestCases.add(error(clazz, "IllegalAccessException: could not instantiate " +
+                    "test class. Class: " + clazz.getName()));
         }
+    }
 
+    private UiAutomatorTestCase error(Class<?> clazz, final String message) {
+        UiAutomatorTestCase warning = new UiAutomatorTestCase() {
+            protected void runTest() {
+                fail(message);
+            }
+        };
+
+        warning.setName(clazz.getName());
+        return warning;
     }
 
     /**
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 926bff7..e7d961b 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -85,7 +85,7 @@
     /**
      * Provides support for running tests to report interim status
      *
-     * @return
+     * @return IAutomationSupport
      * @since API Level 16
      */
     public IAutomationSupport getAutomationSupport() {
diff --git a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
index 02553e6..4f41a5c 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -23,9 +23,11 @@
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.test.RepetitiveTest;
 import android.util.Log;
 
+import com.android.uiautomator.core.Tracer;
 import com.android.uiautomator.core.UiDevice;
 
 import junit.framework.AssertionFailedError;
@@ -103,41 +105,50 @@
         mUiDevice = UiDevice.getInstance();
         List<TestCase> testCases = collector.getTestCases();
         Bundle testRunOutput = new Bundle();
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        PrintStream writer = new PrintStream(byteArrayOutputStream);
-        try {
-            StringResultPrinter resultPrinter = new StringResultPrinter(writer);
 
-            TestResult testRunResult = new TestResult();
+        String traceType = mParams.getString("traceOutputMode");
+        if(traceType != null) {
+            Tracer.Mode mode = Tracer.Mode.valueOf(Tracer.Mode.class, traceType);
+            if (mode == Tracer.Mode.FILE || mode == Tracer.Mode.ALL) {
+                String filename = mParams.getString("traceLogFilename");
+                if (filename == null) {
+                    throw new RuntimeException("Name of log file not specified. " +
+                            "Please specify it using traceLogFilename parameter");
+                }
+                Tracer.getInstance().setOutputFilename(filename);
+            }
+            Tracer.getInstance().setOutputMode(mode);
+        }
+
+        TestResult testRunResult = new TestResult();
+        ResultReporter resultPrinter;
+        String outputFormat = mParams.getString("outputFormat");
+        if ("simple".equals(outputFormat)) {
+            resultPrinter = new SimpleResultPrinter(System.out, true);
+        } else {
+            resultPrinter = new WatcherResultPrinter(testCases.size());
+        }
+
+        long startTime = SystemClock.uptimeMillis();
+        try {
             // add test listeners
-            testRunResult.addListener(new WatcherResultPrinter(testCases.size()));
             testRunResult.addListener(resultPrinter);
             // add all custom listeners
             for (TestListener listener : mTestListeners) {
                 testRunResult.addListener(listener);
             }
-            long startTime = System.currentTimeMillis();
 
             // run tests for realz!
             for (TestCase testCase : testCases) {
                 prepareTestCase(testCase);
                 testCase.run(testRunResult);
             }
-            long runTime = System.currentTimeMillis() - startTime;
-
-            resultPrinter.print2(testRunResult, runTime);
         } catch (Throwable t) {
             // catch all exceptions so a more verbose error message can be outputted
-            writer.println(String.format("Test run aborted due to unexpected exception: %s",
-                            t.getMessage()));
-            t.printStackTrace(writer);
+            resultPrinter.printUnexpectedError(t);
         } finally {
-            testRunOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
-                    String.format("\nTest results for %s=%s",
-                    getClass().getSimpleName(),
-                    byteArrayOutputStream.toString()));
-            writer.close();
-            mAutomationSupport.sendStatus(Activity.RESULT_OK, testRunOutput);
+            long runTime = SystemClock.uptimeMillis() - startTime;
+            resultPrinter.print(testRunResult, runTime, testRunOutput);
         }
     }
 
@@ -198,8 +209,13 @@
         }
     }
 
+    private interface ResultReporter extends TestListener {
+        public void print(TestResult result, long runTime, Bundle testOutput);
+        public void printUnexpectedError(Throwable t);
+    }
+
     // Copy & pasted from InstrumentationTestRunner.WatcherResultPrinter
-    private class WatcherResultPrinter implements TestListener {
+    private class WatcherResultPrinter implements ResultReporter {
 
         private static final String REPORT_KEY_NUM_TOTAL = "numtests";
         private static final String REPORT_KEY_NAME_CLASS = "class";
@@ -219,10 +235,18 @@
         int mTestResultCode = 0;
         String mTestClass = null;
 
+        private SimpleResultPrinter mPrinter;
+        private ByteArrayOutputStream mStream;
+        private PrintStream mWriter;
+
         public WatcherResultPrinter(int numTests) {
             mResultTemplate = new Bundle();
             mResultTemplate.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
             mResultTemplate.putInt(REPORT_KEY_NUM_TOTAL, numTests);
+
+            mStream = new ByteArrayOutputStream();
+            mWriter = new PrintStream(mStream);
+            mPrinter = new SimpleResultPrinter(mWriter, false);
         }
 
         /**
@@ -263,6 +287,8 @@
 
             mAutomationSupport.sendStatus(REPORT_VALUE_RESULT_START, mTestResult);
             mTestResultCode = 0;
+
+            mPrinter.startTest(test);
         }
 
         @Override
@@ -273,6 +299,8 @@
             mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
                 String.format("\nError in %s:\n%s",
                     ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
+
+            mPrinter.addError(test, t);
         }
 
         @Override
@@ -283,6 +311,8 @@
             mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
                 String.format("\nFailure in %s:\n%s",
                     ((TestCase)test).getName(), BaseTestRunner.getFilteredTrace(t)));
+
+            mPrinter.addFailure(test, t);
         }
 
         @Override
@@ -291,25 +321,67 @@
                 mTestResult.putString(Instrumentation.REPORT_KEY_STREAMRESULT, ".");
             }
             mAutomationSupport.sendStatus(mTestResultCode, mTestResult);
+
+            mPrinter.endTest(test);
         }
 
+        public void print(TestResult result, long runTime, Bundle testOutput) {
+            mPrinter.print(result, runTime, testOutput);
+            testOutput.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
+                  String.format("\nTest results for %s=%s",
+                  getClass().getSimpleName(),
+                  mStream.toString()));
+            mWriter.close();
+            mAutomationSupport.sendStatus(Activity.RESULT_OK, testOutput);
+        }
+
+        public void printUnexpectedError(Throwable t) {
+            mWriter.println(String.format("Test run aborted due to unexpected exception: %s",
+                    t.getMessage()));
+            t.printStackTrace(mWriter);
+        }
     }
 
-    // copy pasted from InstrumentationTestRunner
-    private class StringResultPrinter extends ResultPrinter {
-
-        public StringResultPrinter(PrintStream writer) {
+    /**
+     * Class that produces the same output as JUnit when running from command line. Can be
+     * used when default UiAutomator output is too verbose.
+     */
+    private class SimpleResultPrinter extends ResultPrinter implements ResultReporter {
+        private boolean mFullOutput;
+        public SimpleResultPrinter(PrintStream writer, boolean fullOutput) {
             super(writer);
+            mFullOutput = fullOutput;
         }
 
-        synchronized void print2(TestResult result, long runTime) {
+        public void print(TestResult result, long runTime, Bundle testOutput) {
             printHeader(runTime);
+            if (mFullOutput) {
+                printErrors(result);
+                printFailures(result);
+            }
             printFooter(result);
         }
+
+        public void printUnexpectedError(Throwable t) {
+            if (mFullOutput) {
+                getWriter().printf("Test run aborted due to unexpected exeption: %s",
+                        t.getMessage());
+                t.printStackTrace(getWriter());
+            }
+        }
     }
 
     protected TestCaseCollector getTestCaseCollector(ClassLoader classLoader) {
-        return new TestCaseCollector(classLoader, new UiAutomatorTestCaseFilter());
+        return new TestCaseCollector(classLoader, getTestCaseFilter());
+    }
+
+    /**
+     * Returns an object which determines if the class and its methods should be
+     * accepted into the test suite.
+     * @return
+     */
+    public UiAutomatorTestCaseFilter getTestCaseFilter() {
+        return new UiAutomatorTestCaseFilter();
     }
 
     protected void addTestListener(TestListener listener) {