1 /* 2 * Copyright (C) 2007 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 17 package android.util; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 22 import java.io.PrintWriter; 23 import java.lang.reflect.Field; 24 import java.lang.reflect.InvocationTargetException; 25 import java.lang.reflect.Method; 26 import java.lang.reflect.Modifier; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.List; 30 import java.util.Locale; 31 import java.util.stream.Collectors; 32 33 /** 34 * <p>Various utilities for debugging and logging.</p> 35 */ 36 @android.ravenwood.annotation.RavenwoodKeepWholeClass 37 public class DebugUtils { DebugUtils()38 /** @hide */ public DebugUtils() {} 39 40 /** 41 * <p>Filters objects against the <code>ANDROID_OBJECT_FILTER</code> 42 * environment variable. This environment variable can filter objects 43 * based on their class name and attribute values.</p> 44 * 45 * <p>Here is the syntax for <code>ANDROID_OBJECT_FILTER</code>:</p> 46 * 47 * <p><code>ClassName@attribute1=value1@attribute2=value2...</code></p> 48 * 49 * <p>Examples:</p> 50 * <ul> 51 * <li>Select TextView instances: <code>TextView</code></li> 52 * <li>Select TextView instances of text "Loading" and bottom offset of 22: 53 * <code>TextView@text=Loading.*@bottom=22</code></li> 54 * </ul> 55 * 56 * <p>The class name and the values are regular expressions.</p> 57 * 58 * <p>This class is useful for debugging and logging purpose:</p> 59 * <pre> 60 * if (DEBUG) { 61 * if (DebugUtils.isObjectSelected(childView) && LOGV_ENABLED) { 62 * Log.v(TAG, "Object " + childView + " logged!"); 63 * } 64 * } 65 * </pre> 66 * 67 * <p><strong>NOTE</strong>: This method is very expensive as it relies 68 * heavily on regular expressions and reflection. Calls to this method 69 * should always be stripped out of the release binaries and avoided 70 * as much as possible in debug mode.</p> 71 * 72 * @param object any object to match against the ANDROID_OBJECT_FILTER 73 * environement variable 74 * @return true if object is selected by the ANDROID_OBJECT_FILTER 75 * environment variable, false otherwise 76 */ isObjectSelected(Object object)77 public static boolean isObjectSelected(Object object) { 78 boolean match = false; 79 String s = System.getenv("ANDROID_OBJECT_FILTER"); 80 if (s != null && s.length() > 0) { 81 String[] selectors = s.split("@"); 82 // first selector == class name 83 if (object.getClass().getSimpleName().matches(selectors[0])) { 84 // check potential attributes 85 for (int i = 1; i < selectors.length; i++) { 86 String[] pair = selectors[i].split("="); 87 Class<?> klass = object.getClass(); 88 try { 89 Method declaredMethod = null; 90 Class<?> parent = klass; 91 do { 92 declaredMethod = parent.getDeclaredMethod("get" + 93 pair[0].substring(0, 1).toUpperCase(Locale.ROOT) + 94 pair[0].substring(1), 95 (Class[]) null); 96 } while ((parent = klass.getSuperclass()) != null && 97 declaredMethod == null); 98 99 if (declaredMethod != null) { 100 Object value = declaredMethod 101 .invoke(object, (Object[])null); 102 match |= (value != null ? 103 value.toString() : "null").matches(pair[1]); 104 } 105 } catch (NoSuchMethodException e) { 106 e.printStackTrace(); 107 } catch (IllegalAccessException e) { 108 e.printStackTrace(); 109 } catch (InvocationTargetException e) { 110 e.printStackTrace(); 111 } 112 } 113 } 114 } 115 return match; 116 } 117 118 /** @hide */ 119 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) buildShortClassTag(Object cls, StringBuilder out)120 public static void buildShortClassTag(Object cls, StringBuilder out) { 121 if (cls == null) { 122 out.append("null"); 123 } else { 124 String simpleName = cls.getClass().getSimpleName(); 125 if (simpleName == null || simpleName.isEmpty()) { 126 simpleName = cls.getClass().getName(); 127 int end = simpleName.lastIndexOf('.'); 128 if (end > 0) { 129 simpleName = simpleName.substring(end+1); 130 } 131 } 132 out.append(simpleName); 133 out.append('{'); 134 out.append(Integer.toHexString(System.identityHashCode(cls))); 135 } 136 } 137 138 /** @hide */ printSizeValue(PrintWriter pw, long number)139 public static void printSizeValue(PrintWriter pw, long number) { 140 float result = number; 141 String suffix = ""; 142 if (result > 900) { 143 suffix = "KB"; 144 result = result / 1024; 145 } 146 if (result > 900) { 147 suffix = "MB"; 148 result = result / 1024; 149 } 150 if (result > 900) { 151 suffix = "GB"; 152 result = result / 1024; 153 } 154 if (result > 900) { 155 suffix = "TB"; 156 result = result / 1024; 157 } 158 if (result > 900) { 159 suffix = "PB"; 160 result = result / 1024; 161 } 162 String value; 163 if (result < 1) { 164 value = String.format("%.2f", result); 165 } else if (result < 10) { 166 value = String.format("%.1f", result); 167 } else if (result < 100) { 168 value = String.format("%.0f", result); 169 } else { 170 value = String.format("%.0f", result); 171 } 172 pw.print(value); 173 pw.print(suffix); 174 } 175 176 /** @hide */ sizeValueToString(long number, StringBuilder outBuilder)177 public static String sizeValueToString(long number, StringBuilder outBuilder) { 178 if (outBuilder == null) { 179 outBuilder = new StringBuilder(32); 180 } 181 float result = number; 182 String suffix = ""; 183 if (result > 900) { 184 suffix = "KB"; 185 result = result / 1024; 186 } 187 if (result > 900) { 188 suffix = "MB"; 189 result = result / 1024; 190 } 191 if (result > 900) { 192 suffix = "GB"; 193 result = result / 1024; 194 } 195 if (result > 900) { 196 suffix = "TB"; 197 result = result / 1024; 198 } 199 if (result > 900) { 200 suffix = "PB"; 201 result = result / 1024; 202 } 203 String value; 204 if (result < 1) { 205 value = String.format("%.2f", result); 206 } else if (result < 10) { 207 value = String.format("%.1f", result); 208 } else if (result < 100) { 209 value = String.format("%.0f", result); 210 } else { 211 value = String.format("%.0f", result); 212 } 213 outBuilder.append(value); 214 outBuilder.append(suffix); 215 return outBuilder.toString(); 216 } 217 218 /** 219 * Use prefixed constants (static final values) on given class to turn value 220 * into human-readable string. 221 * 222 * @hide 223 */ valueToString(Class<?> clazz, String prefix, int value)224 public static String valueToString(Class<?> clazz, String prefix, int value) { 225 for (Field field : clazz.getDeclaredFields()) { 226 final int modifiers = field.getModifiers(); 227 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) 228 && field.getType().equals(int.class) && field.getName().startsWith(prefix)) { 229 try { 230 if (value == field.getInt(null)) { 231 return constNameWithoutPrefix(prefix, field); 232 } 233 } catch (IllegalAccessException ignored) { 234 } 235 } 236 } 237 return Integer.toString(value); 238 } 239 240 /** 241 * Use prefixed constants (static final values) on given class to turn flags 242 * into human-readable string. 243 * 244 * @hide 245 */ flagsToString(Class<?> clazz, String prefix, long flags)246 public static String flagsToString(Class<?> clazz, String prefix, long flags) { 247 final StringBuilder res = new StringBuilder(); 248 boolean flagsWasZero = flags == 0; 249 250 for (Field field : clazz.getDeclaredFields()) { 251 final int modifiers = field.getModifiers(); 252 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) 253 && (field.getType().equals(int.class) || field.getType().equals(long.class)) 254 && field.getName().startsWith(prefix)) { 255 final long value = getFieldValue(field); 256 if (value == 0 && flagsWasZero) { 257 return constNameWithoutPrefix(prefix, field); 258 } 259 if (value != 0 && (flags & value) == value) { 260 flags &= ~value; 261 res.append(constNameWithoutPrefix(prefix, field)).append('|'); 262 } 263 } 264 } 265 if (flags != 0 || res.length() == 0) { 266 res.append(Long.toHexString(flags)); 267 } else { 268 res.deleteCharAt(res.length() - 1); 269 } 270 return res.toString(); 271 } 272 getFieldValue(Field field)273 private static long getFieldValue(Field field) { 274 // Field could be int or long 275 try { 276 final long longValue = field.getLong(null); 277 if (longValue != 0) { 278 return longValue; 279 } 280 final int intValue = field.getInt(null); 281 if (intValue != 0) { 282 return intValue; 283 } 284 } catch (IllegalAccessException ignored) { 285 } 286 return 0; 287 } 288 289 /** 290 * Gets human-readable representation of constants (static final values). 291 * 292 * @hide 293 */ constantToString(Class<?> clazz, String prefix, int value)294 public static String constantToString(Class<?> clazz, String prefix, int value) { 295 for (Field field : clazz.getDeclaredFields()) { 296 final int modifiers = field.getModifiers(); 297 try { 298 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) 299 && field.getType().equals(int.class) && field.getName().startsWith(prefix) 300 && field.getInt(null) == value) { 301 return constNameWithoutPrefix(prefix, field); 302 } 303 } catch (IllegalAccessException ignored) { 304 } 305 } 306 return prefix + Integer.toString(value); 307 } 308 constNameWithoutPrefix(String prefix, Field field)309 private static String constNameWithoutPrefix(String prefix, Field field) { 310 return field.getName().substring(prefix.length()); 311 } 312 313 /** 314 * Returns method names from current stack trace, where {@link StackTraceElement#getClass} 315 * starts with the given classes name 316 * 317 * @hide 318 */ callersWithin(Class<?> cls, int offset)319 public static List<String> callersWithin(Class<?> cls, int offset) { 320 List<String> result = Arrays.stream(Thread.currentThread().getStackTrace()) 321 .skip(offset + 3) 322 .filter(st -> st.getClassName().startsWith(cls.getName())) 323 .map(StackTraceElement::getMethodName) 324 .collect(Collectors.toList()); 325 Collections.reverse(result); 326 return result; 327 } 328 } 329