Added tracing of UiAutomator calls.

Bug: 7565311

Change-Id: I4e426c68ce929c1dd0c8ee47b053045fa58e63aa
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 7afc485..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);
     }
 
@@ -405,6 +430,7 @@
      * @since API Level 16
      */
     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");
         }
@@ -451,6 +480,7 @@
      * @since API Level 16
      */
     public void removeWatcher(String name) {
+        Tracer.trace(name);
         if (mInWatcherContext) {
             throw new IllegalStateException("Cannot remove a watcher from within another");
         }
@@ -463,6 +493,7 @@
      * @since API Level 16
      */
     public void runWatchers() {
+        Tracer.trace();
         if (mInWatcherContext) {
             return;
         }
@@ -492,6 +523,7 @@
      * @since API Level 16
      */
     public void resetWatcherTriggers() {
+        Tracer.trace();
         mWatchersTriggers.clear();
     }
 
@@ -507,6 +539,7 @@
      * @since API Level 16
      */
     public boolean hasWatcherTriggered(String watcherName) {
+        Tracer.trace(watcherName);
         return mWatchersTriggers.contains(watcherName);
     }
 
@@ -518,6 +551,7 @@
      * @since API Level 16
      */
     public boolean hasAnyWatcherTriggered() {
+        Tracer.trace();
         return mWatchersTriggers.size() > 0;
     }
 
@@ -526,6 +560,7 @@
      * @param watcherName
      */
     private void setWatcherTriggered(String watcherName) {
+        Tracer.trace(watcherName);
         if (!hasWatcherTriggered(watcherName)) {
             mWatchersTriggers.add(watcherName);
         }
@@ -538,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;
@@ -548,6 +584,7 @@
      * @since API Level 17
      */
     public int getDisplayRotation() {
+        Tracer.trace();
         return getDefaultDisplay().getRotation();
     }
 
@@ -558,6 +595,7 @@
      * @since API Level 16
      */
     public void freezeRotation() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().freezeRotation();
     }
 
@@ -568,6 +606,7 @@
      * @throws RemoteException
      */
     public void unfreezeRotation() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().unfreezeRotation();
     }
 
@@ -581,6 +620,7 @@
      * @since API Level 17
      */
     public void setOrientationLeft() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationLeft();
     }
 
@@ -594,6 +634,7 @@
      * @since API Level 17
      */
     public void setOrientationRight() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationRight();
     }
 
@@ -607,6 +648,7 @@
      * @since API Level 17
      */
     public void setOrientationNatural() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().setRotationNatural();
     }
 
@@ -620,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.
@@ -635,6 +678,7 @@
      * @since API Level 16
      */
     public boolean isScreenOn() throws RemoteException {
+        Tracer.trace();
         return getAutomatorBridge().getInteractionController().isScreenOn();
     }
 
@@ -646,6 +690,7 @@
      * @since API Level 16
      */
     public void sleep() throws RemoteException {
+        Tracer.trace();
         getAutomatorBridge().getInteractionController().sleepDevice();
     }
 
@@ -657,6 +702,7 @@
      * @since API Level 16
      */
     public void dumpWindowHierarchy(String fileName) {
+        Tracer.trace(fileName);
         AccessibilityNodeInfo root =
                 getAutomatorBridge().getQueryController().getAccessibilityRootNode();
         if(root != null) {
@@ -681,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;
@@ -742,6 +789,7 @@
      * @since API Level 17
      */
     public boolean takeScreenshot(File storePath) {
+        Tracer.trace(storePath);
         return takeScreenshot(storePath, 1.0f, 90);
     }
 
@@ -757,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 adacf39..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());
@@ -190,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
@@ -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
@@ -236,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
@@ -258,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
@@ -323,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());
@@ -343,6 +352,7 @@
      * @since API Level 16
      */
     public boolean clickAndWaitForNewWindow() throws UiObjectNotFoundException {
+        Tracer.trace();
         return clickAndWaitForNewWindow(WAIT_FOR_WINDOW_TMEOUT);
     }
 
@@ -362,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());
@@ -379,6 +390,7 @@
      * @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());
@@ -395,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());
@@ -411,6 +424,7 @@
      * @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());
@@ -427,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());
@@ -443,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());
@@ -459,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());
@@ -476,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());
@@ -501,6 +519,7 @@
      * @since API Level 16
      */
     public boolean setText(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
         clearTextField();
         return getInteractionController().sendText(text);
     }
@@ -525,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) {
@@ -549,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());
@@ -564,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());
@@ -579,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());
@@ -594,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());
@@ -609,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());
@@ -624,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());
@@ -639,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());
@@ -654,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());
@@ -669,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());
@@ -684,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());
@@ -703,6 +733,7 @@
      * @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());
@@ -718,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());
@@ -740,6 +772,7 @@
      * @since API Level 16
      */
     public boolean waitForExists(long timeout) {
+        Tracer.trace(timeout);
         if(findAccessibilityNodeInfo(timeout) != null) {
             return true;
         }
@@ -765,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) {
@@ -789,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 710c559..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,6 +270,7 @@
      * @since API Level 16
      */
     public boolean scrollTextIntoView(String text) throws UiObjectNotFoundException {
+        Tracer.trace(text);
         return scrollIntoView(new UiSelector().text(text));
     }
 
@@ -276,6 +286,7 @@
      * @since API Level 16
      */
     public UiScrollable setMaxSearchSwipes(int swipes) {
+        Tracer.trace(swipes);
         mMaxSearchSwipes = swipes;
         return this;
     }
@@ -291,6 +302,7 @@
      * @since API Level 16
      */
     public int getMaxSearchSwipes() {
+        Tracer.trace();
         return mMaxSearchSwipes;
     }
 
@@ -301,6 +313,7 @@
      * @since API Level 16
      */
     public boolean flingForward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollForward(FLING_STEPS);
     }
 
@@ -311,6 +324,7 @@
      * @since API Level 16
      */
     public boolean scrollForward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollForward(SCROLL_STEPS);
     }
 
@@ -326,6 +340,7 @@
      * @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) {
@@ -367,6 +382,7 @@
      * @since API Level 16
      */
     public boolean flingBackward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollBackward(FLING_STEPS);
     }
 
@@ -377,6 +393,7 @@
      * @since API Level 16
      */
     public boolean scrollBackward() throws UiObjectNotFoundException {
+        Tracer.trace();
         return scrollBackward(SCROLL_STEPS);
     }
 
@@ -392,6 +409,7 @@
      * @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) {
@@ -438,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++) {
@@ -456,6 +475,7 @@
      * @since API Level 16
      */
     public boolean scrollToBeginning(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToBeginning(maxSwipes, SCROLL_STEPS);
     }
 
@@ -467,6 +487,7 @@
      * @since API Level 16
      */
     public boolean flingToBeginning(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToBeginning(maxSwipes, FLING_STEPS);
     }
 
@@ -480,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)) {
@@ -497,6 +519,7 @@
      * @since API Level 16
      */
     public boolean scrollToEnd(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToEnd(maxSwipes, SCROLL_STEPS);
     }
 
@@ -508,6 +531,7 @@
      * @since API Level 16
      */
     public boolean flingToEnd(int maxSwipes) throws UiObjectNotFoundException {
+        Tracer.trace(maxSwipes);
         return scrollToEnd(maxSwipes, FLING_STEPS);
     }
 
@@ -526,6 +550,7 @@
      * @since API Level 16
      */
     public double getSwipeDeadZonePercentage() {
+        Tracer.trace();
         return mSwipeDeadZonePercentage;
     }
 
@@ -545,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/testrunner/UiAutomatorTestRunner.java b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
index 02553e6..a376aa0 100644
--- a/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
+++ b/uiautomator/library/src/com/android/uiautomator/testrunner/UiAutomatorTestRunner.java
@@ -26,6 +26,8 @@
 import android.test.RepetitiveTest;
 import android.util.Log;
 
+import com.android.uiautomator.core.Tracer;
+import com.android.uiautomator.core.Tracer.Mode;
 import com.android.uiautomator.core.UiDevice;
 
 import junit.framework.AssertionFailedError;
@@ -105,6 +107,21 @@
         Bundle testRunOutput = new Bundle();
         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         PrintStream writer = new PrintStream(byteArrayOutputStream);
+
+        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);
+        }
+
         try {
             StringResultPrinter resultPrinter = new StringResultPrinter(writer);