blob: 8a5be75b6c31d8cd105a4e4f0370a87982bb635d [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.view;
18
Siva Velusamy0d857b92015-04-22 10:23:56 -070019import android.annotation.NonNull;
John Reck5cca8f22018-12-10 17:06:22 -080020import android.annotation.Nullable;
21import android.annotation.TestApi;
Artur Satayevad9254c2019-12-10 17:47:54 +000022import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project10592532009-03-18 17:39:46 -070023import android.content.Context;
Romain Guyf9284692011-07-13 18:46:21 -070024import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.graphics.Bitmap;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070026import android.graphics.Canvas;
John Reck5cca8f22018-12-10 17:06:22 -080027import android.graphics.HardwareRenderer;
John Reck519ad482018-02-12 17:08:48 -080028import android.graphics.Picture;
John Reck32f140aa62018-10-04 15:08:24 -070029import android.graphics.RecordingCanvas;
Romain Guy223ff5c2010-03-02 17:07:47 -080030import android.graphics.Rect;
John Reck32f140aa62018-10-04 15:08:24 -070031import android.graphics.RenderNode;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070032import android.os.Debug;
Kristian Monsen97e8f622013-04-26 12:51:19 -070033import android.os.Handler;
John Reck5cca8f22018-12-10 17:06:22 -080034import android.os.Looper;
Sunny Goyal7a889e12019-10-11 13:23:42 -070035import android.os.Message;
Romain Guy223ff5c2010-03-02 17:07:47 -080036import android.os.RemoteException;
Romain Guyf9284692011-07-13 18:46:21 -070037import android.util.DisplayMetrics;
38import android.util.Log;
Jon Miranda042ad632014-09-03 17:57:35 -070039import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Neil Fuller5c3f8c62019-04-04 21:02:06 +010041import libcore.util.HexEncoding;
42
Romain Guyf9284692011-07-13 18:46:21 -070043import java.io.BufferedOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import java.io.BufferedWriter;
Romain Guyf9284692011-07-13 18:46:21 -070045import java.io.ByteArrayOutputStream;
46import java.io.DataOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import java.io.OutputStream;
Romain Guyf9284692011-07-13 18:46:21 -070049import java.io.OutputStreamWriter;
Sunny Goyal7a889e12019-10-11 13:23:42 -070050import java.lang.annotation.Annotation;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import java.lang.annotation.ElementType;
52import java.lang.annotation.Retention;
53import java.lang.annotation.RetentionPolicy;
Romain Guyf9284692011-07-13 18:46:21 -070054import java.lang.annotation.Target;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070055import java.lang.reflect.AccessibleObject;
Romain Guyf9284692011-07-13 18:46:21 -070056import java.lang.reflect.Field;
57import java.lang.reflect.InvocationTargetException;
Sunny Goyal7a889e12019-10-11 13:23:42 -070058import java.lang.reflect.Member;
Romain Guyf9284692011-07-13 18:46:21 -070059import java.lang.reflect.Method;
John Reck5cca8f22018-12-10 17:06:22 -080060import java.util.ArrayDeque;
Sunny Goyal7a889e12019-10-11 13:23:42 -070061import java.util.Arrays;
Romain Guyf9284692011-07-13 18:46:21 -070062import java.util.HashMap;
Kristian Monsen97e8f622013-04-26 12:51:19 -070063import java.util.concurrent.Callable;
Romain Guyf9284692011-07-13 18:46:21 -070064import java.util.concurrent.CountDownLatch;
Kristian Monsen97e8f622013-04-26 12:51:19 -070065import java.util.concurrent.ExecutionException;
John Reck5cca8f22018-12-10 17:06:22 -080066import java.util.concurrent.Executor;
Kristian Monsen97e8f622013-04-26 12:51:19 -070067import java.util.concurrent.FutureTask;
Romain Guyf9284692011-07-13 18:46:21 -070068import java.util.concurrent.TimeUnit;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070069import java.util.concurrent.TimeoutException;
Siva Velusamyf9455fa2013-01-17 18:01:52 -080070import java.util.concurrent.atomic.AtomicReference;
John Reck5cca8f22018-12-10 17:06:22 -080071import java.util.concurrent.locks.ReentrantLock;
72import java.util.function.Function;
Sunny Goyal7a889e12019-10-11 13:23:42 -070073import java.util.stream.Stream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074
75/**
76 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
77 */
78public class ViewDebug {
79 /**
Romain Guy13b90732012-05-21 12:13:31 -070080 * @deprecated This flag is now unused
Romain Guy13922e02009-05-12 17:56:14 -070081 */
Romain Guy13b90732012-05-21 12:13:31 -070082 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 public static final boolean TRACE_HIERARCHY = false;
84
85 /**
Romain Guy13b90732012-05-21 12:13:31 -070086 * @deprecated This flag is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 */
Romain Guy13b90732012-05-21 12:13:31 -070088 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 /**
Christopher Tate2c095f32010-10-04 14:13:40 -070092 * Enables detailed logging of drag/drop operations.
93 * @hide
94 */
Christopher Tate994ef922011-01-12 20:06:07 -080095 public static final boolean DEBUG_DRAG = false;
Christopher Tate2c095f32010-10-04 14:13:40 -070096
97 /**
Chong Zhang8e89b312015-09-09 15:09:30 -070098 * Enables detailed logging of task positioning operations.
99 * @hide
100 */
101 public static final boolean DEBUG_POSITIONING = false;
102
103 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 * This annotation can be used to mark fields and methods to be dumped by
105 * the view server. Only non-void methods with no arguments can be annotated
106 * by this annotation.
107 */
108 @Target({ ElementType.FIELD, ElementType.METHOD })
109 @Retention(RetentionPolicy.RUNTIME)
110 public @interface ExportedProperty {
111 /**
112 * When resolveId is true, and if the annotated field/method return value
113 * is an int, the value is converted to an Android's resource name.
114 *
115 * @return true if the property's value must be transformed into an Android
116 * resource name, false otherwise
117 */
118 boolean resolveId() default false;
119
120 /**
121 * A mapping can be defined to map int values to specific strings. For
122 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
123 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
124 * these human readable values:
125 *
126 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800127 * {@literal @}ViewDebug.ExportedProperty(mapping = {
128 * {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"),
129 * {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
130 * {@literal @}ViewDebug.IntToString(from = 8, to = "GONE")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 * })
132 * public int getVisibility() { ...
133 * <pre>
134 *
135 * @return An array of int to String mappings
136 *
137 * @see android.view.ViewDebug.IntToString
138 */
139 IntToString[] mapping() default { };
140
141 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700142 * A mapping can be defined to map array indices to specific strings.
143 * A mapping can be used to see human readable values for the indices
144 * of an array:
145 *
146 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800147 * {@literal @}ViewDebug.ExportedProperty(indexMapping = {
148 * {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"),
149 * {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"),
150 * {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND")
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700151 * })
152 * private int[] mElements;
153 * <pre>
154 *
155 * @return An array of int to String mappings
156 *
157 * @see android.view.ViewDebug.IntToString
158 * @see #mapping()
159 */
160 IntToString[] indexMapping() default { };
161
162 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700163 * A flags mapping can be defined to map flags encoded in an integer to
164 * specific strings. A mapping can be used to see human readable values
165 * for the flags of an integer:
166 *
167 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800168 * {@literal @}ViewDebug.ExportedProperty(flagMapping = {
169 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED,
170 * name = "ENABLED"),
171 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED,
172 * name = "DISABLED"),
Romain Guy809a7f62009-05-14 15:44:42 -0700173 * })
174 * private int mFlags;
175 * <pre>
176 *
177 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700178 *
Romain Guy809a7f62009-05-14 15:44:42 -0700179 * @return An array of int to String mappings
180 */
181 FlagToString[] flagMapping() default { };
182
183 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 * When deep export is turned on, this property is not dumped. Instead, the
185 * properties contained in this property are dumped. Each child property
186 * is prefixed with the name of this property.
187 *
188 * @return true if the properties of this property should be dumped
189 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700190 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 */
192 boolean deepExport() default false;
193
194 /**
195 * The prefix to use on child properties when deep export is enabled
196 *
197 * @return a prefix as a String
198 *
199 * @see #deepExport()
200 */
201 String prefix() default "";
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700202
203 /**
204 * Specifies the category the property falls into, such as measurement,
205 * layout, drawing, etc.
206 *
207 * @return the category as String
208 */
209 String category() default "";
Jon Miranda4597e982014-07-29 07:25:49 -0700210
211 /**
212 * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string.
213 *
214 * @return true if the supported values should be formatted as a hex string.
215 */
216 boolean formatToHexString() default false;
Jon Miranda836c0a82014-08-11 12:32:26 -0700217
218 /**
219 * Indicates whether or not the key to value mappings are held in adjacent indices.
220 *
221 * Note: Applies only to fields and methods that return String[].
222 *
223 * @return true if the key to value mappings are held in adjacent indices.
224 */
225 boolean hasAdjacentMapping() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 }
227
228 /**
229 * Defines a mapping from an int value to a String. Such a mapping can be used
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900230 * in an @ExportedProperty to provide more meaningful values to the end user.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 *
232 * @see android.view.ViewDebug.ExportedProperty
233 */
234 @Target({ ElementType.TYPE })
235 @Retention(RetentionPolicy.RUNTIME)
236 public @interface IntToString {
237 /**
238 * The original int value to map to a String.
239 *
240 * @return An arbitrary int value.
241 */
242 int from();
243
244 /**
245 * The String to use in place of the original int value.
246 *
247 * @return An arbitrary non-null String.
248 */
249 String to();
250 }
Romain Guy809a7f62009-05-14 15:44:42 -0700251
252 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900253 * Defines a mapping from a flag to a String. Such a mapping can be used
254 * in an @ExportedProperty to provide more meaningful values to the end user.
Romain Guy809a7f62009-05-14 15:44:42 -0700255 *
256 * @see android.view.ViewDebug.ExportedProperty
257 */
258 @Target({ ElementType.TYPE })
259 @Retention(RetentionPolicy.RUNTIME)
260 public @interface FlagToString {
261 /**
262 * The mask to apply to the original value.
263 *
264 * @return An arbitrary int value.
265 */
266 int mask();
267
268 /**
269 * The value to compare to the result of:
270 * <code>original value &amp; {@link #mask()}</code>.
271 *
272 * @return An arbitrary value.
273 */
274 int equals();
275
276 /**
277 * The String to use in place of the original int value.
278 *
279 * @return An arbitrary non-null String.
280 */
281 String name();
282
283 /**
284 * Indicates whether to output the flag when the test is true,
285 * or false. Defaults to true.
286 */
287 boolean outputIf() default true;
288 }
289
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 /**
291 * This annotation can be used to mark fields and methods to be dumped when
292 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700293 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 */
295 @Target({ ElementType.FIELD, ElementType.METHOD })
296 @Retention(RetentionPolicy.RUNTIME)
297 public @interface CapturedViewProperty {
298 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700299 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700301 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700303 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700305 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700307
John Reck926cf562012-06-14 10:00:31 -0700308 /**
309 * Allows a View to inject custom children into HierarchyViewer. For example,
310 * WebView uses this to add its internal layer tree as a child to itself
311 * @hide
312 */
313 public interface HierarchyHandler {
314 /**
315 * Dumps custom children to hierarchy viewer.
John Reckf2361152012-06-14 15:23:22 -0700316 * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
John Reck926cf562012-06-14 10:00:31 -0700317 * for the format
318 *
319 * An empty implementation should simply do nothing
320 *
321 * @param out The output writer
322 * @param level The indentation level
323 */
324 public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
325
326 /**
327 * Returns a View to enable grabbing screenshots from custom children
328 * returned in dumpViewHierarchyWithProperties.
329 *
330 * @param className The className of the view to find
331 * @param hashCode The hashCode of the view to find
332 * @return the View to capture from, or null if not found
333 */
334 public View findHierarchyView(String className, int hashCode);
335 }
336
Sunny Goyal7a889e12019-10-11 13:23:42 -0700337 private abstract static class PropertyInfo<T extends Annotation,
338 R extends AccessibleObject & Member> {
339
340 public final R member;
341 public final T property;
342 public final String name;
343 public final Class<?> returnType;
344
345 public String entrySuffix = "";
346 public String valueSuffix = "";
347
348 PropertyInfo(Class<T> property, R member, Class<?> returnType) {
349 this.member = member;
350 this.name = member.getName();
351 this.property = member.getAnnotation(property);
352 this.returnType = returnType;
353 }
354
355 public abstract Object invoke(Object target) throws Exception;
356
357 static <T extends Annotation> PropertyInfo<T, ?> forMethod(Method method,
358 Class<T> property) {
359 // Ensure the method return and parameter types can be resolved.
360 try {
361 if ((method.getReturnType() == Void.class)
362 || (method.getParameterTypes().length != 0)) {
363 return null;
364 }
365 } catch (NoClassDefFoundError e) {
366 return null;
367 }
368 if (!method.isAnnotationPresent(property)) {
369 return null;
370 }
371 method.setAccessible(true);
372
373 PropertyInfo info = new MethodPI(method, property);
374 info.entrySuffix = "()";
375 info.valueSuffix = ";";
376 return info;
377 }
378
379 static <T extends Annotation> PropertyInfo<T, ?> forField(Field field, Class<T> property) {
380 if (!field.isAnnotationPresent(property)) {
381 return null;
382 }
383 field.setAccessible(true);
384 return new FieldPI<>(field, property);
385 }
386 }
387
388 private static class MethodPI<T extends Annotation> extends PropertyInfo<T, Method> {
389
390 MethodPI(Method method, Class<T> property) {
391 super(property, method, method.getReturnType());
392 }
393
394 @Override
395 public Object invoke(Object target) throws Exception {
396 return member.invoke(target);
397 }
398 }
399
400 private static class FieldPI<T extends Annotation> extends PropertyInfo<T, Field> {
401
402 FieldPI(Field field, Class<T> property) {
403 super(property, field, field.getType());
404 }
405
406 @Override
407 public Object invoke(Object target) throws Exception {
408 return member.get(target);
409 }
410 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411
412 // Maximum delay in ms after which we stop trying to capture a View's drawing
Sunny Goyal7a889e12019-10-11 13:23:42 -0700413 private static final int CAPTURE_TIMEOUT = 6000;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414
415 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
416 private static final String REMOTE_COMMAND_DUMP = "DUMP";
Jon Miranda042ad632014-09-03 17:57:35 -0700417 private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
Sunny Goyalb217bde2019-11-12 16:23:07 -0800418 /**
419 * Similar to REMOTE_COMMAND_DUMP but uses ViewHierarchyEncoder instead of flat text
420 * @hide
421 */
422 public static final String REMOTE_COMMAND_DUMP_ENCODED = "DUMP_ENCODED";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
424 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700425 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800426 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
Chet Haaseed30fd82011-04-22 16:18:45 -0700427 private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428
Sunny Goyal7a889e12019-10-11 13:23:42 -0700429 private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
430 private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
431 sCapturedViewProperties;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700432
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 /**
Romain Guy13b90732012-05-21 12:13:31 -0700434 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 */
Romain Guy13b90732012-05-21 12:13:31 -0700436 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 public enum HierarchyTraceType {
438 INVALIDATE,
439 INVALIDATE_CHILD,
440 INVALIDATE_CHILD_IN_PARENT,
441 REQUEST_LAYOUT,
442 ON_LAYOUT,
443 ON_MEASURE,
444 DRAW,
445 BUILD_CACHE
446 }
447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 /**
Romain Guy13b90732012-05-21 12:13:31 -0700449 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 */
Romain Guy13b90732012-05-21 12:13:31 -0700451 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 public enum RecyclerTraceType {
453 NEW_VIEW,
454 BIND_VIEW,
455 RECYCLE_FROM_ACTIVE_HEAP,
456 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 MOVE_TO_SCRAP_HEAP,
458 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
459 }
460
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 /**
462 * Returns the number of instanciated Views.
463 *
464 * @return The number of Views instanciated in the current process.
465 *
466 * @hide
467 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100468 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 public static long getViewInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700470 return Debug.countInstancesOfClass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 }
472
473 /**
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700474 * Returns the number of instanciated ViewAncestors.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 *
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700476 * @return The number of ViewAncestors instanciated in the current process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 *
478 * @hide
479 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100480 @UnsupportedAppUsage
Romain Guy65b345f2011-07-27 18:51:50 -0700481 public static long getViewRootImplCount() {
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700482 return Debug.countInstancesOfClass(ViewRootImpl.class);
Romain Guya1f3e4a2009-06-04 15:10:46 -0700483 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484
485 /**
Romain Guy13b90732012-05-21 12:13:31 -0700486 * @deprecated This method is now unused and invoking it is a no-op
Romain Guyf9284692011-07-13 18:46:21 -0700487 */
Romain Guy13b90732012-05-21 12:13:31 -0700488 @Deprecated
489 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 public static void trace(View view, RecyclerTraceType type, int... parameters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 }
492
493 /**
Romain Guy13b90732012-05-21 12:13:31 -0700494 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 */
Romain Guy13b90732012-05-21 12:13:31 -0700496 @Deprecated
497 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 public static void startRecyclerTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 }
500
501 /**
Romain Guy13b90732012-05-21 12:13:31 -0700502 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 */
Romain Guy13b90732012-05-21 12:13:31 -0700504 @Deprecated
505 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 public static void stopRecyclerTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800507 }
508
509 /**
Romain Guy13b90732012-05-21 12:13:31 -0700510 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 */
Romain Guy13b90732012-05-21 12:13:31 -0700512 @Deprecated
513 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 public static void trace(View view, HierarchyTraceType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800515 }
516
517 /**
Romain Guy13b90732012-05-21 12:13:31 -0700518 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 */
Romain Guy13b90732012-05-21 12:13:31 -0700520 @Deprecated
521 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 public static void startHierarchyTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 }
524
525 /**
Romain Guy13b90732012-05-21 12:13:31 -0700526 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 */
Romain Guy13b90732012-05-21 12:13:31 -0700528 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 public static void stopHierarchyTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700531
Mathew Inwooda570dee2018-08-17 14:56:00 +0100532 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 static void dispatchCommand(View view, String command, String parameters,
534 OutputStream clientStream) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 // Paranoid but safe...
536 view = view.getRootView();
537
538 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
Siva Velusamy945bfb62013-01-06 16:03:12 -0800539 dump(view, false, true, clientStream);
Jon Miranda042ad632014-09-03 17:57:35 -0700540 } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
541 dumpTheme(view, clientStream);
Sunny Goyalb217bde2019-11-12 16:23:07 -0800542 } else if (REMOTE_COMMAND_DUMP_ENCODED.equalsIgnoreCase(command)) {
543 dumpEncoded(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800544 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
545 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 } else {
547 final String[] params = parameters.split(" ");
548 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
549 capture(view, clientStream, params[0]);
Chet Haaseed30fd82011-04-22 16:18:45 -0700550 } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
551 outputDisplayList(view, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
553 invalidate(view, params[0]);
554 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
555 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700556 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
557 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
559 }
560 }
561
Siva Velusamy945bfb62013-01-06 16:03:12 -0800562 /** @hide */
563 public static View findView(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700564 // Look by type/hashcode
565 if (parameter.indexOf('@') != -1) {
566 final String[] ids = parameter.split("@");
567 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800568 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800569
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700570 View view = root.getRootView();
571 if (view instanceof ViewGroup) {
572 return findView((ViewGroup) view, className, hashCode);
573 }
574 } else {
575 // Look by id
576 final int id = root.getResources().getIdentifier(parameter, null, null);
577 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 }
579
580 return null;
581 }
582
583 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700584 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 if (view != null) {
586 view.postInvalidate();
587 }
588 }
589
590 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700591 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800592 if (view != null) {
593 root.post(new Runnable() {
594 public void run() {
595 view.requestLayout();
596 }
597 });
598 }
599 }
600
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700601 private static void profile(View root, OutputStream clientStream, String parameter)
602 throws IOException {
603
604 final View view = findView(root, parameter);
605 BufferedWriter out = null;
606 try {
607 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
608
609 if (view != null) {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700610 profileViewAndChildren(view, out);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700611 } else {
612 out.write("-1 -1 -1");
613 out.newLine();
614 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700615 out.write("DONE.");
616 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700617 } catch (Exception e) {
618 android.util.Log.w("View", "Problem profiling the view:", e);
619 } finally {
620 if (out != null) {
621 out.close();
622 }
623 }
624 }
625
Siva Velusamy945bfb62013-01-06 16:03:12 -0800626 /** @hide */
627 public static void profileViewAndChildren(final View view, BufferedWriter out)
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700628 throws IOException {
Sunny Goyalc1043202017-10-16 14:00:44 -0700629 RenderNode node = RenderNode.create("ViewDebug", null);
630 profileViewAndChildren(view, node, out, true);
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700631 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700632
Sunny Goyalc1043202017-10-16 14:00:44 -0700633 private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
634 boolean root) throws IOException {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700635 long durationMeasure =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700636 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700637 ? profileViewMeasure(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700638 long durationLayout =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700639 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700640 ? profileViewLayout(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700641 long durationDraw =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700642 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700643 ? profileViewDraw(view, node) : 0;
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700644
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700645 out.write(String.valueOf(durationMeasure));
646 out.write(' ');
647 out.write(String.valueOf(durationLayout));
648 out.write(' ');
649 out.write(String.valueOf(durationDraw));
650 out.newLine();
651 if (view instanceof ViewGroup) {
652 ViewGroup group = (ViewGroup) view;
653 final int count = group.getChildCount();
654 for (int i = 0; i < count; i++) {
Sunny Goyalc1043202017-10-16 14:00:44 -0700655 profileViewAndChildren(group.getChildAt(i), node, out, false);
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700656 }
657 }
658 }
659
Sunny Goyalc1043202017-10-16 14:00:44 -0700660 private static long profileViewMeasure(final View view) {
661 return profileViewOperation(view, new ViewOperation() {
662 @Override
663 public void pre() {
664 forceLayout(view);
665 }
666
667 private void forceLayout(View view) {
668 view.forceLayout();
669 if (view instanceof ViewGroup) {
670 ViewGroup group = (ViewGroup) view;
671 final int count = group.getChildCount();
672 for (int i = 0; i < count; i++) {
673 forceLayout(group.getChildAt(i));
674 }
675 }
676 }
677
678 @Override
679 public void run() {
680 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
681 }
682 });
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700683 }
684
Sunny Goyalc1043202017-10-16 14:00:44 -0700685 private static long profileViewLayout(View view) {
686 return profileViewOperation(view,
687 () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
688 }
689
690 private static long profileViewDraw(View view, RenderNode node) {
691 DisplayMetrics dm = view.getResources().getDisplayMetrics();
692 if (dm == null) {
693 return 0;
694 }
695
696 if (view.isHardwareAccelerated()) {
John Recke57475e2019-02-20 17:39:52 -0800697 RecordingCanvas canvas = node.beginRecording(dm.widthPixels, dm.heightPixels);
Sunny Goyalc1043202017-10-16 14:00:44 -0700698 try {
699 return profileViewOperation(view, () -> view.draw(canvas));
700 } finally {
John Recke57475e2019-02-20 17:39:52 -0800701 node.endRecording();
Sunny Goyalc1043202017-10-16 14:00:44 -0700702 }
703 } else {
704 Bitmap bitmap = Bitmap.createBitmap(
705 dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
706 Canvas canvas = new Canvas(bitmap);
707 try {
708 return profileViewOperation(view, () -> view.draw(canvas));
709 } finally {
710 canvas.setBitmap(null);
711 bitmap.recycle();
712 }
713 }
714 }
715
716 interface ViewOperation {
717 default void pre() {}
718
719 void run();
720 }
721
722 private static long profileViewOperation(View view, final ViewOperation operation) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700723 final CountDownLatch latch = new CountDownLatch(1);
724 final long[] duration = new long[1];
725
Sunny Goyalc1043202017-10-16 14:00:44 -0700726 view.post(() -> {
727 try {
728 operation.pre();
729 long start = Debug.threadCpuTimeNanos();
730 //noinspection unchecked
731 operation.run();
732 duration[0] = Debug.threadCpuTimeNanos() - start;
733 } finally {
734 latch.countDown();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700735 }
736 });
737
738 try {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700739 if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
740 Log.w("View", "Could not complete the profiling of the view " + view);
741 return -1;
742 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700743 } catch (InterruptedException e) {
744 Log.w("View", "Could not complete the profiling of the view " + view);
745 Thread.currentThread().interrupt();
746 return -1;
747 }
748
749 return duration[0];
750 }
751
Siva Velusamy945bfb62013-01-06 16:03:12 -0800752 /** @hide */
753 public static void captureLayers(View root, final DataOutputStream clientStream)
Romain Guy223ff5c2010-03-02 17:07:47 -0800754 throws IOException {
755
756 try {
757 Rect outRect = new Rect();
758 try {
759 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
760 } catch (RemoteException e) {
761 // Ignore
762 }
Jon Miranda4597e982014-07-29 07:25:49 -0700763
Romain Guy223ff5c2010-03-02 17:07:47 -0800764 clientStream.writeInt(outRect.width());
765 clientStream.writeInt(outRect.height());
Jon Miranda4597e982014-07-29 07:25:49 -0700766
Romain Guy65554f22010-03-22 18:58:21 -0700767 captureViewLayer(root, clientStream, true);
Jon Miranda4597e982014-07-29 07:25:49 -0700768
Romain Guy223ff5c2010-03-02 17:07:47 -0800769 clientStream.write(2);
770 } finally {
771 clientStream.close();
772 }
773 }
774
Romain Guy65554f22010-03-22 18:58:21 -0700775 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -0800776 throws IOException {
777
Romain Guy65554f22010-03-22 18:58:21 -0700778 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
779
Dianne Hackborn4702a852012-08-17 15:18:29 -0700780 if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
Romain Guy223ff5c2010-03-02 17:07:47 -0800781 final int id = view.getId();
782 String name = view.getClass().getSimpleName();
783 if (id != View.NO_ID) {
784 name = resolveId(view.getContext(), id).toString();
785 }
Jon Miranda4597e982014-07-29 07:25:49 -0700786
Romain Guy223ff5c2010-03-02 17:07:47 -0800787 clientStream.write(1);
788 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -0700789 clientStream.writeByte(localVisible ? 1 : 0);
Jon Miranda4597e982014-07-29 07:25:49 -0700790
Romain Guy223ff5c2010-03-02 17:07:47 -0800791 int[] position = new int[2];
792 // XXX: Should happen on the UI thread
793 view.getLocationInWindow(position);
Jon Miranda4597e982014-07-29 07:25:49 -0700794
Romain Guy223ff5c2010-03-02 17:07:47 -0800795 clientStream.writeInt(position[0]);
796 clientStream.writeInt(position[1]);
797 clientStream.flush();
Jon Miranda4597e982014-07-29 07:25:49 -0700798
Romain Guy223ff5c2010-03-02 17:07:47 -0800799 Bitmap b = performViewCapture(view, true);
800 if (b != null) {
801 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
802 b.getHeight() * 2);
803 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
804 clientStream.writeInt(arrayOut.size());
805 arrayOut.writeTo(clientStream);
806 }
807 clientStream.flush();
808 }
809
810 if (view instanceof ViewGroup) {
811 ViewGroup group = (ViewGroup) view;
812 int count = group.getChildCount();
813
814 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -0700815 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -0800816 }
817 }
Chet Haase68bf5bd2013-09-05 16:27:28 -0700818
819 if (view.mOverlay != null) {
820 ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
821 captureViewLayer(overlayContainer, clientStream, localVisible);
822 }
Romain Guy223ff5c2010-03-02 17:07:47 -0800823 }
824
Chet Haaseed30fd82011-04-22 16:18:45 -0700825 private static void outputDisplayList(View root, String parameter) throws IOException {
826 final View view = findView(root, parameter);
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700827 view.getViewRootImpl().outputDisplayList(view);
Chet Haaseed30fd82011-04-22 16:18:45 -0700828 }
829
Siva Velusamy945bfb62013-01-06 16:03:12 -0800830 /** @hide */
831 public static void outputDisplayList(View root, View target) {
832 root.getViewRootImpl().outputDisplayList(target);
833 }
834
John Reck5cca8f22018-12-10 17:06:22 -0800835 private static class PictureCallbackHandler implements AutoCloseable,
836 HardwareRenderer.PictureCapturedCallback, Runnable {
837 private final HardwareRenderer mRenderer;
838 private final Function<Picture, Boolean> mCallback;
839 private final Executor mExecutor;
840 private final ReentrantLock mLock = new ReentrantLock(false);
841 private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
842 private boolean mStopListening;
843 private Thread mRenderThread;
844
845 private PictureCallbackHandler(HardwareRenderer renderer,
846 Function<Picture, Boolean> callback, Executor executor) {
847 mRenderer = renderer;
848 mCallback = callback;
849 mExecutor = executor;
850 mRenderer.setPictureCaptureCallback(this);
851 }
852
853 @Override
854 public void close() {
855 mLock.lock();
856 mStopListening = true;
857 mLock.unlock();
858 mRenderer.setPictureCaptureCallback(null);
859 }
860
861 @Override
862 public void onPictureCaptured(Picture picture) {
863 mLock.lock();
864 if (mStopListening) {
865 mLock.unlock();
866 mRenderer.setPictureCaptureCallback(null);
867 return;
868 }
869 if (mRenderThread == null) {
870 mRenderThread = Thread.currentThread();
871 }
872 Picture toDestroy = null;
873 if (mQueue.size() == 3) {
874 toDestroy = mQueue.removeLast();
875 }
876 mQueue.add(picture);
877 mLock.unlock();
878 if (toDestroy == null) {
879 mExecutor.execute(this);
880 } else {
881 toDestroy.close();
882 }
883 }
884
885 @Override
886 public void run() {
887 mLock.lock();
888 final Picture picture = mQueue.poll();
889 final boolean isStopped = mStopListening;
890 mLock.unlock();
891 if (Thread.currentThread() == mRenderThread) {
892 close();
893 throw new IllegalStateException(
894 "ViewDebug#startRenderingCommandsCapture must be given an executor that "
895 + "invokes asynchronously");
896 }
897 if (isStopped) {
898 picture.close();
899 return;
900 }
901 final boolean keepReceiving = mCallback.apply(picture);
902 if (!keepReceiving) {
903 close();
904 }
905 }
906 }
907
908 /**
909 * Begins capturing the entire rendering commands for the view tree referenced by the given
910 * view. The view passed may be any View in the tree as long as it is attached. That is,
911 * {@link View#isAttachedToWindow()} must be true.
912 *
913 * Every time a frame is rendered a Picture will be passed to the given callback via the given
914 * executor. As long as the callback returns 'true' it will continue to receive new frames.
915 * The system will only invoke the callback at a rate that the callback is able to keep up with.
916 * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
917 * then the callback will only receive 33% of the frames produced.
918 *
919 * This method must be called on the same thread as the View tree.
920 *
921 * @param tree The View tree to capture the rendering commands.
922 * @param callback The callback to invoke on every frame produced. Should return true to
923 * continue receiving new frames, false to stop capturing.
924 * @param executor The executor to invoke the callback on. Recommend using a background thread
925 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
926 * exception will be thrown.
927 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
928 * that the callback may continue to receive another frame or two depending on thread timings.
929 * Returns null if the capture stream cannot be started, such as if there's no
930 * HardwareRenderer for the given view tree.
931 * @hide
John Reckd9425652019-02-15 12:32:31 -0800932 * @deprecated use {@link #startRenderingCommandsCapture(View, Executor, Callable)} instead.
John Reck5cca8f22018-12-10 17:06:22 -0800933 */
934 @TestApi
935 @Nullable
John Reckd9425652019-02-15 12:32:31 -0800936 @Deprecated
John Reck5cca8f22018-12-10 17:06:22 -0800937 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
938 Function<Picture, Boolean> callback) {
939 final View.AttachInfo attachInfo = tree.mAttachInfo;
940 if (attachInfo == null) {
941 throw new IllegalArgumentException("Given view isn't attached");
942 }
943 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
944 throw new IllegalStateException("Called on the wrong thread."
945 + " Must be called on the thread that owns the given View");
946 }
947 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
948 if (renderer != null) {
949 return new PictureCallbackHandler(renderer, callback, executor);
950 }
951 return null;
952 }
953
John Reck8d0da1a2019-10-03 13:20:08 -0700954 private static class StreamingPictureCallbackHandler implements AutoCloseable,
955 HardwareRenderer.PictureCapturedCallback, Runnable {
956 private final HardwareRenderer mRenderer;
957 private final Callable<OutputStream> mCallback;
958 private final Executor mExecutor;
959 private final ReentrantLock mLock = new ReentrantLock(false);
960 private final ArrayDeque<byte[]> mQueue = new ArrayDeque<>(3);
961 private final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
962 private boolean mStopListening;
963 private Thread mRenderThread;
964
965 private StreamingPictureCallbackHandler(HardwareRenderer renderer,
966 Callable<OutputStream> callback, Executor executor) {
967 mRenderer = renderer;
968 mCallback = callback;
969 mExecutor = executor;
970 mRenderer.setPictureCaptureCallback(this);
971 }
972
973 @Override
974 public void close() {
975 mLock.lock();
976 mStopListening = true;
977 mLock.unlock();
978 mRenderer.setPictureCaptureCallback(null);
979 }
980
981 @Override
982 public void onPictureCaptured(Picture picture) {
983 mLock.lock();
984 if (mStopListening) {
985 mLock.unlock();
986 mRenderer.setPictureCaptureCallback(null);
987 return;
988 }
989 if (mRenderThread == null) {
990 mRenderThread = Thread.currentThread();
991 }
992 boolean needsInvoke = true;
993 if (mQueue.size() == 3) {
994 mQueue.removeLast();
995 needsInvoke = false;
996 }
997 picture.writeToStream(mByteStream);
998 mQueue.add(mByteStream.toByteArray());
999 mByteStream.reset();
1000 mLock.unlock();
1001
1002 if (needsInvoke) {
1003 mExecutor.execute(this);
1004 }
1005 }
1006
1007 @Override
1008 public void run() {
1009 mLock.lock();
1010 final byte[] picture = mQueue.poll();
1011 final boolean isStopped = mStopListening;
1012 mLock.unlock();
1013 if (Thread.currentThread() == mRenderThread) {
1014 close();
1015 throw new IllegalStateException(
1016 "ViewDebug#startRenderingCommandsCapture must be given an executor that "
1017 + "invokes asynchronously");
1018 }
1019 if (isStopped) {
1020 return;
1021 }
1022 OutputStream stream = null;
1023 try {
1024 stream = mCallback.call();
1025 } catch (Exception ex) {
1026 Log.w("ViewDebug", "Aborting rendering commands capture "
1027 + "because callback threw exception", ex);
1028 }
1029 if (stream != null) {
1030 try {
1031 stream.write(picture);
1032 } catch (IOException ex) {
1033 Log.w("ViewDebug", "Aborting rendering commands capture "
1034 + "due to IOException writing to output stream", ex);
1035 }
1036 } else {
1037 close();
1038 }
1039 }
1040 }
1041
John Reckd9425652019-02-15 12:32:31 -08001042 /**
1043 * Begins capturing the entire rendering commands for the view tree referenced by the given
1044 * view. The view passed may be any View in the tree as long as it is attached. That is,
1045 * {@link View#isAttachedToWindow()} must be true.
1046 *
1047 * Every time a frame is rendered the callback will be invoked on the given executor to
1048 * provide an OutputStream to serialize to. As long as the callback returns a valid
1049 * OutputStream the capturing will continue. The system will only invoke the callback at a rate
1050 * that the callback & OutputStream is able to keep up with. That is, if it takes 48ms for the
1051 * callback & serialization to complete and there is a 60fps animation running
1052 * then the callback will only receive 33% of the frames produced.
1053 *
1054 * This method must be called on the same thread as the View tree.
1055 *
1056 * @param tree The View tree to capture the rendering commands.
1057 * @param callback The callback to invoke on every frame produced. Should return an
1058 * OutputStream to write the data to. Return null to cancel capture. The
1059 * same stream may be returned each time as the serialized data contains
1060 * start & end markers. The callback will not be invoked while a previous
1061 * serialization is being performed, so if a single continuous stream is being
1062 * used it is valid for the callback to write its own metadata to that stream
1063 * in response to callback invocation.
1064 * @param executor The executor to invoke the callback on. Recommend using a background thread
1065 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
1066 * exception will be thrown.
1067 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
1068 * that the callback may continue to receive another frame or two depending on thread timings.
1069 * Returns null if the capture stream cannot be started, such as if there's no
1070 * HardwareRenderer for the given view tree.
1071 * @hide
1072 */
1073 @TestApi
1074 @Nullable
1075 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
1076 Callable<OutputStream> callback) {
1077 final View.AttachInfo attachInfo = tree.mAttachInfo;
1078 if (attachInfo == null) {
1079 throw new IllegalArgumentException("Given view isn't attached");
1080 }
1081 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
1082 throw new IllegalStateException("Called on the wrong thread."
1083 + " Must be called on the thread that owns the given View");
1084 }
1085 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
1086 if (renderer != null) {
John Reck8d0da1a2019-10-03 13:20:08 -07001087 return new StreamingPictureCallbackHandler(renderer, callback, executor);
John Reckd9425652019-02-15 12:32:31 -08001088 }
1089 return null;
1090 }
1091
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 private static void capture(View root, final OutputStream clientStream, String parameter)
1093 throws IOException {
1094
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001095 final View captureView = findView(root, parameter);
Siva Velusamy945bfb62013-01-06 16:03:12 -08001096 capture(root, clientStream, captureView);
1097 }
1098
1099 /** @hide */
1100 public static void capture(View root, final OutputStream clientStream, View captureView)
1101 throws IOException {
Romain Guy223ff5c2010-03-02 17:07:47 -08001102 Bitmap b = performViewCapture(captureView, false);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001103
1104 if (b == null) {
Romain Guy223ff5c2010-03-02 17:07:47 -08001105 Log.w("View", "Failed to create capture bitmap!");
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001106 // Send an empty one so that it doesn't get stuck waiting for
1107 // something.
Dianne Hackborndde331c2012-08-03 14:01:57 -07001108 b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
1109 1, 1, Bitmap.Config.ARGB_8888);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001110 }
1111
1112 BufferedOutputStream out = null;
1113 try {
1114 out = new BufferedOutputStream(clientStream, 32 * 1024);
1115 b.compress(Bitmap.CompressFormat.PNG, 100, out);
1116 out.flush();
1117 } finally {
1118 if (out != null) {
1119 out.close();
1120 }
1121 b.recycle();
Romain Guy223ff5c2010-03-02 17:07:47 -08001122 }
1123 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001124
Chet Haase68bf5bd2013-09-05 16:27:28 -07001125 private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001127 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001128 final Bitmap[] cache = new Bitmap[1];
1129
Sunny Goyald1b287e2018-01-04 09:37:22 -08001130 captureView.post(() -> {
1131 try {
1132 CanvasProvider provider = captureView.isHardwareAccelerated()
1133 ? new HardwareCanvasProvider() : new SoftwareCanvasProvider();
1134 cache[0] = captureView.createSnapshot(provider, skipChildren);
1135 } catch (OutOfMemoryError e) {
1136 Log.w("View", "Out of memory for bitmap");
1137 } finally {
1138 latch.countDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 }
1140 });
1141
1142 try {
1143 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -08001144 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001146 Log.w("View", "Could not complete the capture of the view " + captureView);
1147 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 }
1149 }
Jon Miranda4597e982014-07-29 07:25:49 -07001150
Romain Guy223ff5c2010-03-02 17:07:47 -08001151 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 }
1153
Siva Velusamy945bfb62013-01-06 16:03:12 -08001154 /**
1155 * Dumps the view hierarchy starting from the given view.
Siva Velusamy0d857b92015-04-22 10:23:56 -07001156 * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
Siva Velusamy945bfb62013-01-06 16:03:12 -08001157 * @hide
1158 */
Aurimas Liutikas514c5ef2016-05-24 15:22:55 -07001159 @Deprecated
Mathew Inwooda570dee2018-08-17 14:56:00 +01001160 @UnsupportedAppUsage
Siva Velusamy945bfb62013-01-06 16:03:12 -08001161 public static void dump(View root, boolean skipChildren, boolean includeProperties,
1162 OutputStream clientStream) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 BufferedWriter out = null;
1164 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001165 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 View view = root.getRootView();
1167 if (view instanceof ViewGroup) {
1168 ViewGroup group = (ViewGroup) view;
Siva Velusamy945bfb62013-01-06 16:03:12 -08001169 dumpViewHierarchy(group.getContext(), group, out, 0,
1170 skipChildren, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 }
1172 out.write("DONE.");
1173 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001174 } catch (Exception e) {
1175 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 } finally {
1177 if (out != null) {
1178 out.close();
1179 }
1180 }
1181 }
1182
Jon Miranda042ad632014-09-03 17:57:35 -07001183 /**
Siva Velusamy0d857b92015-04-22 10:23:56 -07001184 * Dumps the view hierarchy starting from the given view.
1185 * Rather than using reflection, it uses View's encode method to obtain all the properties.
1186 * @hide
1187 */
1188 public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
1189 throws InterruptedException {
1190 final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
1191 final CountDownLatch latch = new CountDownLatch(1);
1192
1193 view.post(new Runnable() {
1194 @Override
1195 public void run() {
Manu Cornet4d052b32016-09-15 13:03:28 -07001196 encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
1197 encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
Siva Velusamy0d857b92015-04-22 10:23:56 -07001198 view.encode(encoder);
1199 latch.countDown();
1200 }
1201 });
1202
1203 latch.await(2, TimeUnit.SECONDS);
1204 encoder.endStream();
1205 }
1206
Sunny Goyalb217bde2019-11-12 16:23:07 -08001207 private static void dumpEncoded(@NonNull final View view, @NonNull OutputStream out)
1208 throws IOException {
1209 ByteArrayOutputStream baOut = new ByteArrayOutputStream();
1210
1211 final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(baOut);
Sunny Goyalc444b512020-02-25 09:57:50 -08001212 encoder.setUserPropertiesEnabled(false);
Sunny Goyalb217bde2019-11-12 16:23:07 -08001213 encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
1214 encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
1215 view.encode(encoder);
1216 encoder.endStream();
1217 out.write(baOut.toByteArray());
1218 }
1219
Siva Velusamy0d857b92015-04-22 10:23:56 -07001220 /**
Jon Miranda042ad632014-09-03 17:57:35 -07001221 * Dumps the theme attributes from the given View.
1222 * @hide
1223 */
1224 public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
1225 BufferedWriter out = null;
1226 try {
1227 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
1228 String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
1229 view.getContext().getTheme());
1230 if (attributes != null) {
1231 for (int i = 0; i < attributes.length; i += 2) {
1232 if (attributes[i] != null) {
1233 out.write(attributes[i] + "\n");
1234 out.write(attributes[i + 1] + "\n");
1235 }
1236 }
1237 }
1238 out.write("DONE.");
1239 out.newLine();
1240 } catch (Exception e) {
1241 android.util.Log.w("View", "Problem dumping View Theme:", e);
1242 } finally {
1243 if (out != null) {
1244 out.close();
1245 }
1246 }
1247 }
1248
1249 /**
1250 * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
1251 *
1252 * @param resources Resources to resolve attributes from.
1253 * @param theme Theme to dump.
1254 * @return a String array containing pairs of adjacent Theme attribute data: name followed by
1255 * its value.
1256 *
1257 * @hide
1258 */
1259 private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
1260 TypedValue outValue = new TypedValue();
1261 String nullString = "null";
1262 int i = 0;
1263 int[] attributes = theme.getAllAttributes();
1264 String[] data = new String[attributes.length * 2];
1265 for (int attributeId : attributes) {
1266 try {
1267 data[i] = resources.getResourceName(attributeId);
1268 data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
1269 outValue.coerceToString().toString() : nullString;
1270 i += 2;
Jon Miranda7c744bd2014-09-09 17:06:31 -07001271
1272 // attempt to replace reference data with its name
1273 if (outValue.type == TypedValue.TYPE_REFERENCE) {
1274 data[i - 1] = resources.getResourceName(outValue.resourceId);
1275 }
Jon Miranda042ad632014-09-03 17:57:35 -07001276 } catch (Resources.NotFoundException e) {
1277 // ignore resources we can't resolve
1278 }
1279 }
1280 return data;
1281 }
1282
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 private static View findView(ViewGroup group, String className, int hashCode) {
1284 if (isRequestedView(group, className, hashCode)) {
1285 return group;
1286 }
1287
1288 final int count = group.getChildCount();
1289 for (int i = 0; i < count; i++) {
1290 final View view = group.getChildAt(i);
1291 if (view instanceof ViewGroup) {
1292 final View found = findView((ViewGroup) view, className, hashCode);
1293 if (found != null) {
1294 return found;
1295 }
1296 } else if (isRequestedView(view, className, hashCode)) {
1297 return view;
1298 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001299 if (view.mOverlay != null) {
1300 final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
1301 className, hashCode);
1302 if (found != null) {
1303 return found;
1304 }
1305 }
John Reck926cf562012-06-14 10:00:31 -07001306 if (view instanceof HierarchyHandler) {
1307 final View found = ((HierarchyHandler)view)
1308 .findHierarchyView(className, hashCode);
1309 if (found != null) {
1310 return found;
1311 }
1312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001314 return null;
1315 }
1316
1317 private static boolean isRequestedView(View view, String className, int hashCode) {
Chet Haase68bf5bd2013-09-05 16:27:28 -07001318 if (view.hashCode() == hashCode) {
1319 String viewClassName = view.getClass().getName();
1320 if (className.equals("ViewOverlay")) {
1321 return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
1322 } else {
1323 return className.equals(viewClassName);
1324 }
1325 }
1326 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 }
1328
Siva Velusamy945bfb62013-01-06 16:03:12 -08001329 private static void dumpViewHierarchy(Context context, ViewGroup group,
1330 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001331 cacheExportedProperties(group.getClass());
1332 if (!skipChildren) {
1333 cacheExportedPropertiesForChildren(group);
1334 }
1335 // Try to use the handler provided by the view
1336 Handler handler = group.getHandler();
1337 // Fall back on using the main thread
1338 if (handler == null) {
1339 handler = new Handler(Looper.getMainLooper());
1340 }
1341
1342 if (handler.getLooper() == Looper.myLooper()) {
1343 dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
1344 includeProperties);
1345 } else {
1346 FutureTask task = new FutureTask(() ->
1347 dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
1348 includeProperties), null);
1349 Message msg = Message.obtain(handler, task);
1350 msg.setAsynchronous(true);
1351 handler.sendMessage(msg);
1352 while (true) {
1353 try {
1354 task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
1355 return;
1356 } catch (InterruptedException e) {
1357 // try again
1358 } catch (ExecutionException | TimeoutException e) {
1359 // Something unexpected happened.
1360 throw new RuntimeException(e);
1361 }
1362 }
1363 }
1364 }
1365
1366 private static void cacheExportedPropertiesForChildren(ViewGroup group) {
1367 final int count = group.getChildCount();
1368 for (int i = 0; i < count; i++) {
1369 final View view = group.getChildAt(i);
1370 cacheExportedProperties(view.getClass());
1371 if (view instanceof ViewGroup) {
1372 cacheExportedPropertiesForChildren((ViewGroup) view);
1373 }
1374 }
1375 }
1376
1377 private static void cacheExportedProperties(Class<?> klass) {
1378 if (sExportProperties != null && sExportProperties.containsKey(klass)) {
1379 return;
1380 }
1381 do {
1382 for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
1383 if (!info.returnType.isPrimitive() && info.property.deepExport()) {
1384 cacheExportedProperties(info.returnType);
1385 }
1386 }
1387 klass = klass.getSuperclass();
1388 } while (klass != Object.class);
1389 }
1390
1391
1392 private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
1393 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001394 if (!dumpView(context, group, out, level, includeProperties)) {
1395 return;
1396 }
1397
1398 if (skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 return;
1400 }
1401
1402 final int count = group.getChildCount();
1403 for (int i = 0; i < count; i++) {
1404 final View view = group.getChildAt(i);
1405 if (view instanceof ViewGroup) {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001406 dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
1407 skipChildren, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001408 } else {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001409 dumpView(context, view, out, level + 1, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001410 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001411 if (view.mOverlay != null) {
1412 ViewOverlay overlay = view.getOverlay();
1413 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
Sunny Goyal7a889e12019-10-11 13:23:42 -07001414 dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
1415 skipChildren, includeProperties);
Chet Haase68bf5bd2013-09-05 16:27:28 -07001416 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417 }
John Reck926cf562012-06-14 10:00:31 -07001418 if (group instanceof HierarchyHandler) {
1419 ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
1420 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 }
1422
Siva Velusamy945bfb62013-01-06 16:03:12 -08001423 private static boolean dumpView(Context context, View view,
1424 BufferedWriter out, int level, boolean includeProperties) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001425
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426 try {
1427 for (int i = 0; i < level; i++) {
1428 out.write(' ');
1429 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001430 String className = view.getClass().getName();
1431 if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
1432 className = "ViewOverlay";
1433 }
1434 out.write(className);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001435 out.write('@');
1436 out.write(Integer.toHexString(view.hashCode()));
1437 out.write(' ');
Siva Velusamy945bfb62013-01-06 16:03:12 -08001438 if (includeProperties) {
1439 dumpViewProperties(context, view, out);
1440 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001441 out.newLine();
1442 } catch (IOException e) {
1443 Log.w("View", "Error while dumping hierarchy tree");
1444 return false;
1445 }
1446 return true;
1447 }
1448
Sunny Goyal7a889e12019-10-11 13:23:42 -07001449 private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
1450 Method[] methods, Field[] fields, Class<T> property) {
1451 return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
1452 Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
1453 .flatMap(Function.identity())
1454 .filter(i -> i != null)
1455 .toArray(PropertyInfo[]::new);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001456 }
1457
Sunny Goyal7a889e12019-10-11 13:23:42 -07001458 private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
1459 if (sExportProperties == null) {
1460 sExportProperties = new HashMap<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001461 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001462 final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
1463 PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
1464
1465 if (properties == null) {
1466 properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false),
1467 klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class);
1468 map.put(klass, properties);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001469 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001470 return properties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 }
1472
The Android Open Source Project10592532009-03-18 17:39:46 -07001473 private static void dumpViewProperties(Context context, Object view,
1474 BufferedWriter out) throws IOException {
1475
1476 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001477 }
1478
The Android Open Source Project10592532009-03-18 17:39:46 -07001479 private static void dumpViewProperties(Context context, Object view,
1480 BufferedWriter out, String prefix) throws IOException {
1481
Romain Guydfab3632012-10-03 14:53:25 -07001482 if (view == null) {
1483 out.write(prefix + "=4,null ");
1484 return;
1485 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001486
Romain Guydfab3632012-10-03 14:53:25 -07001487 Class<?> klass = view.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488 do {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001489 writeExportedProperties(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001490 klass = klass.getSuperclass();
1491 } while (klass != Object.class);
1492 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001493
Jon Miranda4597e982014-07-29 07:25:49 -07001494 private static String formatIntToHexString(int value) {
1495 return "0x" + Integer.toHexString(value).toUpperCase();
1496 }
1497
Sunny Goyal7a889e12019-10-11 13:23:42 -07001498 private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
The Android Open Source Project10592532009-03-18 17:39:46 -07001499 Class<?> klass, String prefix) throws IOException {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001500 for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 //noinspection EmptyCatchBlock
Sunny Goyal7a889e12019-10-11 13:23:42 -07001502 Object value;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 try {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001504 value = info.invoke(view);
1505 } catch (Exception e) {
1506 // ignore
1507 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001509
Sunny Goyal7a889e12019-10-11 13:23:42 -07001510 String categoryPrefix =
1511 info.property.category().length() != 0 ? info.property.category() + ":" : "";
The Android Open Source Project10592532009-03-18 17:39:46 -07001512
Sunny Goyal7a889e12019-10-11 13:23:42 -07001513 if (info.returnType == int.class || info.returnType == byte.class) {
1514 if (info.property.resolveId() && context != null) {
1515 final int id = (Integer) value;
1516 value = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517
Sunny Goyal7a889e12019-10-11 13:23:42 -07001518 } else if (info.property.formatToHexString()) {
1519 if (info.returnType == int.class) {
1520 value = formatIntToHexString((Integer) value);
1521 } else if (info.returnType == byte.class) {
1522 value = "0x"
1523 + HexEncoding.encodeToString((Byte) value, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001524 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001525 } else {
1526 final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
1527 if (flagsMapping.length > 0) {
1528 final int intValue = (Integer) value;
1529 final String valuePrefix =
1530 categoryPrefix + prefix + info.name + '_';
1531 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
Jon Miranda836c0a82014-08-11 12:32:26 -07001532 }
1533
Sunny Goyal7a889e12019-10-11 13:23:42 -07001534 final ViewDebug.IntToString[] mapping = info.property.mapping();
1535 if (mapping.length > 0) {
1536 final int intValue = (Integer) value;
1537 boolean mapped = false;
1538 int mappingCount = mapping.length;
1539 for (int j = 0; j < mappingCount; j++) {
1540 final ViewDebug.IntToString mapper = mapping[j];
1541 if (mapper.from() == intValue) {
1542 value = mapper.to();
1543 mapped = true;
1544 break;
1545 }
1546 }
1547
1548 if (!mapped) {
1549 value = intValue;
1550 }
1551 }
1552 }
1553 } else if (info.returnType == int[].class) {
1554 final int[] array = (int[]) value;
1555 final String valuePrefix = categoryPrefix + prefix + info.name + '_';
1556 exportUnrolledArray(context, out, info.property, array, valuePrefix,
1557 info.entrySuffix);
1558
1559 continue;
1560 } else if (info.returnType == String[].class) {
1561 final String[] array = (String[]) value;
1562 if (info.property.hasAdjacentMapping() && array != null) {
1563 for (int j = 0; j < array.length; j += 2) {
1564 if (array[j] != null) {
1565 writeEntry(out, categoryPrefix + prefix, array[j],
1566 info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
1567 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 }
1569 }
1570
Sunny Goyal7a889e12019-10-11 13:23:42 -07001571 continue;
1572 } else if (!info.returnType.isPrimitive()) {
1573 if (info.property.deepExport()) {
1574 dumpViewProperties(context, value, out, prefix + info.property.prefix());
1575 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001577 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001578
1579 writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001580 }
1581 }
1582
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001583 private static void writeEntry(BufferedWriter out, String prefix, String name,
1584 String suffix, Object value) throws IOException {
1585
1586 out.write(prefix);
1587 out.write(name);
1588 out.write(suffix);
1589 out.write("=");
1590 writeValue(out, value);
1591 out.write(' ');
1592 }
1593
Romain Guy809a7f62009-05-14 15:44:42 -07001594 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1595 int intValue, String prefix) throws IOException {
1596
1597 final int count = mapping.length;
1598 for (int j = 0; j < count; j++) {
1599 final FlagToString flagMapping = mapping[j];
1600 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001601 final int maskResult = intValue & flagMapping.mask();
1602 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001603 if ((test && ifTrue) || (!test && !ifTrue)) {
1604 final String name = flagMapping.name();
Jon Miranda4597e982014-07-29 07:25:49 -07001605 final String value = formatIntToHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001606 writeEntry(out, prefix, name, "", value);
1607 }
1608 }
1609 }
1610
Jorim Jaggi484851b2017-09-22 16:03:27 +02001611 /**
1612 * Converts an integer from a field that is mapped with {@link IntToString} to its string
1613 * representation.
1614 *
1615 * @param clazz The class the field is defined on.
1616 * @param field The field on which the {@link ExportedProperty} is defined on.
1617 * @param integer The value to convert.
1618 * @return The value converted into its string representation.
1619 * @hide
1620 */
1621 public static String intToString(Class<?> clazz, String field, int integer) {
1622 final IntToString[] mapping = getMapping(clazz, field);
1623 if (mapping == null) {
1624 return Integer.toString(integer);
1625 }
1626 final int count = mapping.length;
1627 for (int j = 0; j < count; j++) {
1628 final IntToString map = mapping[j];
1629 if (map.from() == integer) {
1630 return map.to();
1631 }
1632 }
1633 return Integer.toString(integer);
1634 }
1635
1636 /**
1637 * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
1638 * representation.
1639 *
1640 * @param clazz The class the field is defined on.
1641 * @param field The field on which the {@link ExportedProperty} is defined on.
1642 * @param flags The flags to convert.
1643 * @return The flags converted into their string representations.
1644 * @hide
1645 */
1646 public static String flagsToString(Class<?> clazz, String field, int flags) {
1647 final FlagToString[] mapping = getFlagMapping(clazz, field);
1648 if (mapping == null) {
1649 return Integer.toHexString(flags);
1650 }
1651 final StringBuilder result = new StringBuilder();
1652 final int count = mapping.length;
1653 for (int j = 0; j < count; j++) {
1654 final FlagToString flagMapping = mapping[j];
1655 final boolean ifTrue = flagMapping.outputIf();
1656 final int maskResult = flags & flagMapping.mask();
1657 final boolean test = maskResult == flagMapping.equals();
1658 if (test && ifTrue) {
1659 final String name = flagMapping.name();
1660 result.append(name).append(' ');
1661 }
1662 }
Jorim Jaggi2d3954f2017-09-28 16:56:46 +02001663 if (result.length() > 0) {
1664 result.deleteCharAt(result.length() - 1);
1665 }
Jorim Jaggi484851b2017-09-22 16:03:27 +02001666 return result.toString();
1667 }
1668
1669 private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
1670 try {
1671 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
1672 .flagMapping();
1673 } catch (NoSuchFieldException e) {
1674 return null;
1675 }
1676 }
1677
1678 private static IntToString[] getMapping(Class<?> clazz, String field) {
1679 try {
1680 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
1681 } catch (NoSuchFieldException e) {
1682 return null;
1683 }
1684 }
1685
The Android Open Source Project10592532009-03-18 17:39:46 -07001686 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001687 ExportedProperty property, int[] array, String prefix, String suffix)
1688 throws IOException {
1689
1690 final IntToString[] indexMapping = property.indexMapping();
1691 final boolean hasIndexMapping = indexMapping.length > 0;
1692
1693 final IntToString[] mapping = property.mapping();
1694 final boolean hasMapping = mapping.length > 0;
1695
The Android Open Source Project10592532009-03-18 17:39:46 -07001696 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001697 final int valuesCount = array.length;
1698
1699 for (int j = 0; j < valuesCount; j++) {
1700 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001701 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001702
1703 final int intValue = array[j];
1704
1705 name = String.valueOf(j);
1706 if (hasIndexMapping) {
1707 int mappingCount = indexMapping.length;
1708 for (int k = 0; k < mappingCount; k++) {
1709 final IntToString mapped = indexMapping[k];
1710 if (mapped.from() == j) {
1711 name = mapped.to();
1712 break;
1713 }
1714 }
1715 }
1716
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001717 if (hasMapping) {
1718 int mappingCount = mapping.length;
1719 for (int k = 0; k < mappingCount; k++) {
1720 final IntToString mapped = mapping[k];
1721 if (mapped.from() == intValue) {
1722 value = mapped.to();
1723 break;
1724 }
1725 }
1726 }
1727
1728 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001729 if (value == null) value = (String) resolveId(context, intValue);
1730 } else {
1731 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001732 }
1733
1734 writeEntry(out, prefix, name, suffix, value);
1735 }
1736 }
1737
Romain Guy237c1ce2009-12-08 11:30:25 -08001738 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001739 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001740 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001741 if (id >= 0) {
1742 try {
1743 fieldValue = resources.getResourceTypeName(id) + '/' +
1744 resources.getResourceEntryName(id);
1745 } catch (Resources.NotFoundException e) {
Jon Miranda4597e982014-07-29 07:25:49 -07001746 fieldValue = "id/" + formatIntToHexString(id);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001747 }
1748 } else {
1749 fieldValue = "NO_ID";
1750 }
1751 return fieldValue;
1752 }
1753
1754 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1755 if (value != null) {
Romain Guy97723b22012-09-27 23:36:34 -07001756 String output = "[EXCEPTION]";
1757 try {
1758 output = value.toString().replace("\n", "\\n");
1759 } finally {
1760 out.write(String.valueOf(output.length()));
1761 out.write(",");
1762 out.write(output);
1763 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001764 } else {
1765 out.write("4,null");
1766 }
1767 }
1768
Sunny Goyal7a889e12019-10-11 13:23:42 -07001769 private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
1770 Class<?> klass) {
1771 if (sCapturedViewProperties == null) {
1772 sCapturedViewProperties = new HashMap<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001774 final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
1775 sCapturedViewProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776
Sunny Goyal7a889e12019-10-11 13:23:42 -07001777 PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
1778 if (infos == null) {
1779 infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
1780 CapturedViewProperty.class);
1781 map.put(klass, infos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001783 return infos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001784 }
1785
Sunny Goyal7a889e12019-10-11 13:23:42 -07001786 private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 if (obj == null) {
1788 return "null";
1789 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001790
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001791 StringBuilder sb = new StringBuilder();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001792
Sunny Goyal7a889e12019-10-11 13:23:42 -07001793 for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001794 try {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001795 Object methodValue = pi.invoke(obj);
Romain Guya1f3e4a2009-06-04 15:10:46 -07001796
Sunny Goyal7a889e12019-10-11 13:23:42 -07001797 if (pi.property.retrieveReturn()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001798 //we are interested in the second level data only
Sunny Goyal7a889e12019-10-11 13:23:42 -07001799 sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
1800 pi.name + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001801 } else {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001802 sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001803
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001804 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001805 final String value = methodValue.toString().replace("\n", "\\n");
1806 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 } else {
1808 sb.append("null");
1809 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001810 sb.append(pi.valueSuffix).append(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001811 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001812 } catch (Exception e) {
1813 //It is OK here, we simply ignore this property
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001814 }
1815 }
1816 return sb.toString();
1817 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001818
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001819 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001820 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001821 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001822 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001823 * @param tag for log
1824 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001825 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001826 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001827 Class<?> klass = view.getClass();
1828 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
Sunny Goyal7a889e12019-10-11 13:23:42 -07001829 sb.append(exportCapturedViewProperties(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001830 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001831 }
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001832
1833 /**
1834 * Invoke a particular method on given view.
1835 * The given method is always invoked on the UI thread. The caller thread will stall until the
1836 * method invocation is complete. Returns an object equal to the result of the method
1837 * invocation, null if the method is declared to return void
1838 * @throws Exception if the method invocation caused any exception
1839 * @hide
1840 */
1841 public static Object invokeViewMethod(final View view, final Method method,
1842 final Object[] args) {
1843 final CountDownLatch latch = new CountDownLatch(1);
1844 final AtomicReference<Object> result = new AtomicReference<Object>();
1845 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1846
1847 view.post(new Runnable() {
1848 @Override
1849 public void run() {
1850 try {
1851 result.set(method.invoke(view, args));
1852 } catch (InvocationTargetException e) {
1853 exception.set(e.getCause());
1854 } catch (Exception e) {
1855 exception.set(e);
1856 }
1857
1858 latch.countDown();
1859 }
1860 });
1861
1862 try {
1863 latch.await();
1864 } catch (InterruptedException e) {
1865 throw new RuntimeException(e);
1866 }
1867
1868 if (exception.get() != null) {
1869 throw new RuntimeException(exception.get());
1870 }
1871
1872 return result.get();
1873 }
1874
1875 /**
1876 * @hide
1877 */
1878 public static void setLayoutParameter(final View view, final String param, final int value)
1879 throws NoSuchFieldException, IllegalAccessException {
1880 final ViewGroup.LayoutParams p = view.getLayoutParams();
1881 final Field f = p.getClass().getField(param);
1882 if (f.getType() != int.class) {
1883 throw new RuntimeException("Only integer layout parameters can be set. Field "
Jon Miranda4597e982014-07-29 07:25:49 -07001884 + param + " is of type " + f.getType().getSimpleName());
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001885 }
1886
1887 f.set(p, Integer.valueOf(value));
1888
1889 view.post(new Runnable() {
1890 @Override
1891 public void run() {
1892 view.setLayoutParams(p);
1893 }
1894 });
1895 }
Sunny Goyald1b287e2018-01-04 09:37:22 -08001896
1897 /**
1898 * @hide
1899 */
1900 public static class SoftwareCanvasProvider implements CanvasProvider {
1901
1902 private Canvas mCanvas;
1903 private Bitmap mBitmap;
1904 private boolean mEnabledHwBitmapsInSwMode;
1905
1906 @Override
1907 public Canvas getCanvas(View view, int width, int height) {
1908 mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
1909 width, height, Bitmap.Config.ARGB_8888);
1910 if (mBitmap == null) {
1911 throw new OutOfMemoryError();
1912 }
1913 mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
1914
1915 if (view.mAttachInfo != null) {
1916 mCanvas = view.mAttachInfo.mCanvas;
1917 }
1918 if (mCanvas == null) {
1919 mCanvas = new Canvas();
1920 }
1921 mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
1922 mCanvas.setBitmap(mBitmap);
1923 return mCanvas;
1924 }
1925
1926 @Override
1927 public Bitmap createBitmap() {
1928 mCanvas.setBitmap(null);
1929 mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
1930 return mBitmap;
1931 }
1932 }
1933
1934 /**
1935 * @hide
1936 */
1937 public static class HardwareCanvasProvider implements CanvasProvider {
John Reck519ad482018-02-12 17:08:48 -08001938 private Picture mPicture;
Sunny Goyald1b287e2018-01-04 09:37:22 -08001939
1940 @Override
1941 public Canvas getCanvas(View view, int width, int height) {
John Reck519ad482018-02-12 17:08:48 -08001942 mPicture = new Picture();
1943 return mPicture.beginRecording(width, height);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001944 }
1945
1946 @Override
1947 public Bitmap createBitmap() {
John Reck519ad482018-02-12 17:08:48 -08001948 mPicture.endRecording();
1949 return Bitmap.createBitmap(mPicture);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001950 }
1951 }
1952
1953 /**
1954 * @hide
1955 */
1956 public interface CanvasProvider {
1957
1958 /**
1959 * Returns a canvas which can be used to draw {@param view}
1960 */
1961 Canvas getCanvas(View view, int width, int height);
1962
1963 /**
1964 * Creates a bitmap from previously returned canvas
1965 * @return
1966 */
1967 Bitmap createBitmap();
1968 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001969}