blob: bc5edf89b4b00e894f8aa9a97741651484028506 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.util;
18
Mathew Inwood4eb56ab2018-08-14 17:24:32 +010019import android.annotation.UnsupportedAppUsage;
Mathew Inwood31755f92018-12-20 13:53:36 +000020import android.os.Build;
Eugene Susla4ab95112018-12-17 14:45:11 -080021
Dianne Hackbornb9a5e4a2015-03-03 17:04:12 -080022import java.io.PrintWriter;
Jeff Sharkey48877892015-03-18 11:27:19 -070023import java.lang.reflect.Field;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import java.lang.reflect.InvocationTargetException;
Jeff Sharkey48877892015-03-18 11:27:19 -070025import java.lang.reflect.Method;
26import java.lang.reflect.Modifier;
Eugene Suslaa0812502019-04-15 13:19:09 -070027import java.util.Arrays;
28import java.util.Collections;
29import java.util.List;
Elliott Hughescb64d432013-08-02 10:00:44 -070030import java.util.Locale;
Eugene Suslaa0812502019-04-15 13:19:09 -070031import java.util.stream.Collectors;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
33/**
Jesse Wilsona0f8bc52011-02-24 10:44:33 -080034 * <p>Various utilities for debugging and logging.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035 */
36public class DebugUtils {
Jesse Wilsona0f8bc52011-02-24 10:44:33 -080037 /** @hide */ public DebugUtils() {}
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 /**
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>
Dave Bort34f1ca72009-04-13 17:18:45 -070059 * if (DEBUG) {
60 * if (DebugUtils.isObjectSelected(childView) && LOGV_ENABLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 * 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 */
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" +
Elliott Hughescb64d432013-08-02 10:00:44 -070092 pair[0].substring(0, 1).toUpperCase(Locale.ROOT) +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 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
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800117 /** @hide */
Mathew Inwood31755f92018-12-20 13:53:36 +0000118 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackborna2ea7472010-12-20 12:10:01 -0800119 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
Dianne Hackbornb9a5e4a2015-03-03 17:04:12 -0800137 /** @hide */
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 */
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 }
Jeff Sharkey48877892015-03-18 11:27:19 -0700216
217 /**
218 * Use prefixed constants (static final values) on given class to turn value
219 * into human-readable string.
220 *
221 * @hide
222 */
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)) {
Eugene Susla8f012412018-03-16 14:35:31 -0700230 return constNameWithoutPrefix(prefix, field);
Jeff Sharkey48877892015-03-18 11:27:19 -0700231 }
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 */
245 public static String flagsToString(Class<?> clazz, String prefix, int flags) {
246 final StringBuilder res = new StringBuilder();
Eugene Susla8f012412018-03-16 14:35:31 -0700247 boolean flagsWasZero = flags == 0;
Jeff Sharkey48877892015-03-18 11:27:19 -0700248
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);
Eugene Susla8f012412018-03-16 14:35:31 -0700255 if (value == 0 && flagsWasZero) {
256 return constNameWithoutPrefix(prefix, field);
257 }
Sudheer Shankac0f85c12019-10-12 15:01:44 -0700258 if (value != 0 && (flags & value) == value) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700259 flags &= ~value;
Eugene Susla8f012412018-03-16 14:35:31 -0700260 res.append(constNameWithoutPrefix(prefix, field)).append('|');
Jeff Sharkey48877892015-03-18 11:27:19 -0700261 }
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 }
Eugene Susla8f012412018-03-16 14:35:31 -0700273
274 private static String constNameWithoutPrefix(String prefix, Field field) {
275 return field.getName().substring(prefix.length());
276 }
Eugene Suslaa0812502019-04-15 13:19:09 -0700277
278 /**
279 * Returns method names from current stack trace, where {@link StackTraceElement#getClass}
280 * starts with the given classes name
281 *
282 * @hide
283 */
284 public static List<String> callersWithin(Class<?> cls, int offset) {
285 List<String> result = Arrays.stream(Thread.currentThread().getStackTrace())
286 .skip(offset + 3)
287 .filter(st -> st.getClassName().startsWith(cls.getName()))
288 .map(StackTraceElement::getMethodName)
289 .collect(Collectors.toList());
290 Collections.reverse(result);
291 return result;
292 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293}