| /* |
| * 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()); |
| } |
| } |