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