public class

Tracer

extends java.lang.Object

 java.lang.Object

↳androidx.test.uiautomator.Tracer

Overview

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.

Summary

Constructors
publicTracer()

Methods
public static TracergetInstance()

Returns a reference to an instance of the tracer.

public booleanisTracingEnabled()

Queries whether the tracing is enabled.

public voidsetOutputFilename(java.lang.String filename)

Sets the name of the log file where tracing output will be written if the tracer is set to write to a file.

public voidsetOutputMode(Tracer.Mode mode)

Sets where the trace output will go.

public static voidtrace(java.lang.Object arguments[])

Public methods in the UiAutomator should call this function to generate a trace.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public Tracer()

Methods

public static Tracer getInstance()

Returns a reference to an instance of the tracer. Useful to set the parameters before the trace is collected.

Returns:

public void setOutputMode(Tracer.Mode mode)

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.

Parameters:

mode:

public void setOutputFilename(java.lang.String filename)

Sets the name of the log file where tracing output will be written if the tracer is set to write to a file.

Parameters:

filename: name of the log file.

public boolean isTracingEnabled()

Queries whether the tracing is enabled.

Returns:

true if tracing is enabled, false otherwise.

public static void trace(java.lang.Object arguments[])

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[].

Parameters:

arguments: arguments of the method being traced.

Source

/*
 * 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 androidx.test.uiautomator;

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 = "androidx.test.uiautomator";
    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());
    }
}