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