1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.uiautomator.core; 17 18 import android.util.Log; 19 20 import java.io.File; 21 import java.io.FileNotFoundException; 22 import java.io.PrintWriter; 23 import java.text.SimpleDateFormat; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Date; 27 import java.util.List; 28 import java.util.Locale; 29 30 /** 31 * Class that creates traces of the calls to the UiAutomator API and outputs the 32 * traces either to logcat or a logfile. Each public method in the UiAutomator 33 * that needs to be traced should include a call to Tracer.trace in the 34 * beginning. Tracing is turned off by defualt and needs to be enabled 35 * explicitly. 36 * @hide 37 */ 38 public class Tracer { 39 private static final String UNKNOWN_METHOD_STRING = "(unknown method)"; 40 private static final String UIAUTOMATOR_PACKAGE = "com.android.uiautomator.core"; 41 private static final int CALLER_LOCATION = 6; 42 private static final int METHOD_TO_TRACE_LOCATION = 5; 43 private static final int MIN_STACK_TRACE_LENGTH = 7; 44 45 /** 46 * Enum that determines where the trace output goes. It can go to either 47 * logcat, log file or both. 48 */ 49 public enum Mode { 50 NONE, 51 FILE, 52 LOGCAT, 53 ALL 54 } 55 56 private interface TracerSink { log(String message)57 public void log(String message); 58 close()59 public void close(); 60 } 61 62 private class FileSink implements TracerSink { 63 private PrintWriter mOut; 64 private SimpleDateFormat mDateFormat; 65 FileSink(File file)66 public FileSink(File file) throws FileNotFoundException { 67 mOut = new PrintWriter(file); 68 mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 69 } 70 log(String message)71 public void log(String message) { 72 mOut.printf("%s %s\n", mDateFormat.format(new Date()), message); 73 } 74 close()75 public void close() { 76 mOut.close(); 77 } 78 } 79 80 private class LogcatSink implements TracerSink { 81 82 private static final String LOGCAT_TAG = "UiAutomatorTrace"; 83 log(String message)84 public void log(String message) { 85 Log.i(LOGCAT_TAG, message); 86 } 87 close()88 public void close() { 89 // nothing is needed 90 } 91 } 92 93 private Mode mCurrentMode = Mode.NONE; 94 private List<TracerSink> mSinks = new ArrayList<TracerSink>(); 95 private File mOutputFile; 96 97 private static Tracer mInstance = null; 98 99 /** 100 * Returns a reference to an instance of the tracer. Useful to set the 101 * parameters before the trace is collected. 102 * 103 * @return 104 */ getInstance()105 public static Tracer getInstance() { 106 if (mInstance == null) { 107 mInstance = new Tracer(); 108 } 109 return mInstance; 110 } 111 112 /** 113 * Sets where the trace output will go. Can be either be logcat or a file or 114 * both. Setting this to NONE will turn off tracing. 115 * 116 * @param mode 117 */ setOutputMode(Mode mode)118 public void setOutputMode(Mode mode) { 119 closeSinks(); 120 mCurrentMode = mode; 121 try { 122 switch (mode) { 123 case FILE: 124 if (mOutputFile == null) { 125 throw new IllegalArgumentException("Please provide a filename before " + 126 "attempting write trace to a file"); 127 } 128 mSinks.add(new FileSink(mOutputFile)); 129 break; 130 case LOGCAT: 131 mSinks.add(new LogcatSink()); 132 break; 133 case ALL: 134 mSinks.add(new LogcatSink()); 135 if (mOutputFile == null) { 136 throw new IllegalArgumentException("Please provide a filename before " + 137 "attempting write trace to a file"); 138 } 139 mSinks.add(new FileSink(mOutputFile)); 140 break; 141 default: 142 break; 143 } 144 } catch (FileNotFoundException e) { 145 Log.w("Tracer", "Could not open log file: " + e.getMessage()); 146 } 147 } 148 closeSinks()149 private void closeSinks() { 150 for (TracerSink sink : mSinks) { 151 sink.close(); 152 } 153 mSinks.clear(); 154 } 155 156 /** 157 * Sets the name of the log file where tracing output will be written if the 158 * tracer is set to write to a file. 159 * 160 * @param filename name of the log file. 161 */ setOutputFilename(String filename)162 public void setOutputFilename(String filename) { 163 mOutputFile = new File(filename); 164 } 165 doTrace(Object[] arguments)166 private void doTrace(Object[] arguments) { 167 if (mCurrentMode == Mode.NONE) { 168 return; 169 } 170 171 String caller = getCaller(); 172 if (caller == null) { 173 return; 174 } 175 176 log(String.format("%s (%s)", caller, join(", ", arguments))); 177 } 178 log(String message)179 private void log(String message) { 180 for (TracerSink sink : mSinks) { 181 sink.log(message); 182 } 183 } 184 185 /** 186 * Queries whether the tracing is enabled. 187 * @return true if tracing is enabled, false otherwise. 188 */ isTracingEnabled()189 public boolean isTracingEnabled() { 190 return mCurrentMode != Mode.NONE; 191 } 192 193 /** 194 * Public methods in the UiAutomator should call this function to generate a 195 * trace. The trace will include the method thats is being called, it's 196 * arguments and where in the user's code the method is called from. If a 197 * public method is called internally from UIAutomator then this will not 198 * output a trace entry. Only calls from outise the UiAutomator package will 199 * produce output. 200 * 201 * Special note about array arguments. You can safely pass arrays of reference types 202 * to this function. Like String[] or Integer[]. The trace function will print their 203 * contents by calling toString() on each of the elements. This will not work for 204 * array of primitive types like int[] or float[]. Before passing them to this function 205 * convert them to arrays of reference types manually. Example: convert int[] to Integer[]. 206 * 207 * @param arguments arguments of the method being traced. 208 */ trace(Object... arguments)209 public static void trace(Object... arguments) { 210 Tracer.getInstance().doTrace(arguments); 211 } 212 join(String separator, Object[] strings)213 private static String join(String separator, Object[] strings) { 214 if (strings.length == 0) 215 return ""; 216 217 StringBuilder builder = new StringBuilder(objectToString(strings[0])); 218 for (int i = 1; i < strings.length; i++) { 219 builder.append(separator); 220 builder.append(objectToString(strings[i])); 221 } 222 return builder.toString(); 223 } 224 225 /** 226 * Special toString method to handle arrays. If the argument is a normal object then this will 227 * return normal output of obj.toString(). If the argument is an array this will return a 228 * string representation of the elements of the array. 229 * 230 * This method will not work for arrays of primitive types. Arrays of primitive types are 231 * expected to be converted manually by the caller. If the array is not converter then 232 * this function will only output "[...]" instead of the contents of the array. 233 * 234 * @param obj object to convert to a string 235 * @return String representation of the object. 236 */ objectToString(Object obj)237 private static String objectToString(Object obj) { 238 if (obj.getClass().isArray()) { 239 if (obj instanceof Object[]) { 240 return Arrays.deepToString((Object[])obj); 241 } else { 242 return "[...]"; 243 } 244 } else { 245 return obj.toString(); 246 } 247 } 248 249 /** 250 * This method outputs which UiAutomator method was called and where in the 251 * user code it was called from. If it can't deside which method is called 252 * it will output "(unknown method)". If the method was called from inside 253 * the UiAutomator then it returns null. 254 * 255 * @return name of the method called and where it was called from. Null if 256 * method was called from inside UiAutomator. 257 */ getCaller()258 private static String getCaller() { 259 StackTraceElement stackTrace[] = Thread.currentThread().getStackTrace(); 260 if (stackTrace.length < MIN_STACK_TRACE_LENGTH) { 261 return UNKNOWN_METHOD_STRING; 262 } 263 264 StackTraceElement caller = stackTrace[METHOD_TO_TRACE_LOCATION]; 265 StackTraceElement previousCaller = stackTrace[CALLER_LOCATION]; 266 267 if (previousCaller.getClassName().startsWith(UIAUTOMATOR_PACKAGE)) { 268 return null; 269 } 270 271 int indexOfDot = caller.getClassName().lastIndexOf('.'); 272 if (indexOfDot < 0) { 273 indexOfDot = 0; 274 } 275 276 if (indexOfDot + 1 >= caller.getClassName().length()) { 277 return UNKNOWN_METHOD_STRING; 278 } 279 280 String shortClassName = caller.getClassName().substring(indexOfDot + 1); 281 return String.format("%s.%s from %s() at %s:%d", shortClassName, caller.getMethodName(), 282 previousCaller.getMethodName(), previousCaller.getFileName(), 283 previousCaller.getLineNumber()); 284 } 285 } 286