• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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