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