• 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 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, long flags)245     public static String flagsToString(Class<?> clazz, String prefix, long 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.getType().equals(long.class))
253                     && field.getName().startsWith(prefix)) {
254                 final long value = getFieldValue(field);
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             }
263         }
264         if (flags != 0 || res.length() == 0) {
265             res.append(Long.toHexString(flags));
266         } else {
267             res.deleteCharAt(res.length() - 1);
268         }
269         return res.toString();
270     }
271 
getFieldValue(Field field)272     private static long getFieldValue(Field field) {
273         // Field could be int or long
274         try {
275             final long longValue = field.getLong(null);
276             if (longValue != 0) {
277                 return longValue;
278             }
279             final int intValue = field.getInt(null);
280             if (intValue != 0) {
281                 return intValue;
282             }
283         } catch (IllegalAccessException ignored) {
284         }
285         return 0;
286     }
287 
288     /**
289      * Gets human-readable representation of constants (static final values).
290      *
291      * @hide
292      */
constantToString(Class<?> clazz, String prefix, int value)293     public static String constantToString(Class<?> clazz, String prefix, int value) {
294         for (Field field : clazz.getDeclaredFields()) {
295             final int modifiers = field.getModifiers();
296             try {
297                 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
298                         && field.getType().equals(int.class) && field.getName().startsWith(prefix)
299                         && field.getInt(null) == value) {
300                     return constNameWithoutPrefix(prefix, field);
301                 }
302             } catch (IllegalAccessException ignored) {
303             }
304         }
305         return prefix + Integer.toString(value);
306     }
307 
constNameWithoutPrefix(String prefix, Field field)308     private static String constNameWithoutPrefix(String prefix, Field field) {
309         return field.getName().substring(prefix.length());
310     }
311 
312     /**
313      * Returns method names from current stack trace, where {@link StackTraceElement#getClass}
314      * starts with the given classes name
315      *
316      * @hide
317      */
callersWithin(Class<?> cls, int offset)318     public static List<String> callersWithin(Class<?> cls, int offset) {
319         List<String> result = Arrays.stream(Thread.currentThread().getStackTrace())
320                 .skip(offset + 3)
321                 .filter(st -> st.getClassName().startsWith(cls.getName()))
322                 .map(StackTraceElement::getMethodName)
323                 .collect(Collectors.toList());
324         Collections.reverse(result);
325         return result;
326     }
327 }
328