blob: 2f44fe039cf5f4a5c21015498c2e64b90155f503 [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);
1212 encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
1213 encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
1214 view.encode(encoder);
1215 encoder.endStream();
1216 out.write(baOut.toByteArray());
1217 }
1218
Siva Velusamy0d857b92015-04-22 10:23:56 -07001219 /**
Jon Miranda042ad632014-09-03 17:57:35 -07001220 * Dumps the theme attributes from the given View.
1221 * @hide
1222 */
1223 public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
1224 BufferedWriter out = null;
1225 try {
1226 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
1227 String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
1228 view.getContext().getTheme());
1229 if (attributes != null) {
1230 for (int i = 0; i < attributes.length; i += 2) {
1231 if (attributes[i] != null) {
1232 out.write(attributes[i] + "\n");
1233 out.write(attributes[i + 1] + "\n");
1234 }
1235 }
1236 }
1237 out.write("DONE.");
1238 out.newLine();
1239 } catch (Exception e) {
1240 android.util.Log.w("View", "Problem dumping View Theme:", e);
1241 } finally {
1242 if (out != null) {
1243 out.close();
1244 }
1245 }
1246 }
1247
1248 /**
1249 * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
1250 *
1251 * @param resources Resources to resolve attributes from.
1252 * @param theme Theme to dump.
1253 * @return a String array containing pairs of adjacent Theme attribute data: name followed by
1254 * its value.
1255 *
1256 * @hide
1257 */
1258 private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
1259 TypedValue outValue = new TypedValue();
1260 String nullString = "null";
1261 int i = 0;
1262 int[] attributes = theme.getAllAttributes();
1263 String[] data = new String[attributes.length * 2];
1264 for (int attributeId : attributes) {
1265 try {
1266 data[i] = resources.getResourceName(attributeId);
1267 data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
1268 outValue.coerceToString().toString() : nullString;
1269 i += 2;
Jon Miranda7c744bd2014-09-09 17:06:31 -07001270
1271 // attempt to replace reference data with its name
1272 if (outValue.type == TypedValue.TYPE_REFERENCE) {
1273 data[i - 1] = resources.getResourceName(outValue.resourceId);
1274 }
Jon Miranda042ad632014-09-03 17:57:35 -07001275 } catch (Resources.NotFoundException e) {
1276 // ignore resources we can't resolve
1277 }
1278 }
1279 return data;
1280 }
1281
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 private static View findView(ViewGroup group, String className, int hashCode) {
1283 if (isRequestedView(group, className, hashCode)) {
1284 return group;
1285 }
1286
1287 final int count = group.getChildCount();
1288 for (int i = 0; i < count; i++) {
1289 final View view = group.getChildAt(i);
1290 if (view instanceof ViewGroup) {
1291 final View found = findView((ViewGroup) view, className, hashCode);
1292 if (found != null) {
1293 return found;
1294 }
1295 } else if (isRequestedView(view, className, hashCode)) {
1296 return view;
1297 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001298 if (view.mOverlay != null) {
1299 final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
1300 className, hashCode);
1301 if (found != null) {
1302 return found;
1303 }
1304 }
John Reck926cf562012-06-14 10:00:31 -07001305 if (view instanceof HierarchyHandler) {
1306 final View found = ((HierarchyHandler)view)
1307 .findHierarchyView(className, hashCode);
1308 if (found != null) {
1309 return found;
1310 }
1311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 return null;
1314 }
1315
1316 private static boolean isRequestedView(View view, String className, int hashCode) {
Chet Haase68bf5bd2013-09-05 16:27:28 -07001317 if (view.hashCode() == hashCode) {
1318 String viewClassName = view.getClass().getName();
1319 if (className.equals("ViewOverlay")) {
1320 return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
1321 } else {
1322 return className.equals(viewClassName);
1323 }
1324 }
1325 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326 }
1327
Siva Velusamy945bfb62013-01-06 16:03:12 -08001328 private static void dumpViewHierarchy(Context context, ViewGroup group,
1329 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001330 cacheExportedProperties(group.getClass());
1331 if (!skipChildren) {
1332 cacheExportedPropertiesForChildren(group);
1333 }
1334 // Try to use the handler provided by the view
1335 Handler handler = group.getHandler();
1336 // Fall back on using the main thread
1337 if (handler == null) {
1338 handler = new Handler(Looper.getMainLooper());
1339 }
1340
1341 if (handler.getLooper() == Looper.myLooper()) {
1342 dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
1343 includeProperties);
1344 } else {
1345 FutureTask task = new FutureTask(() ->
1346 dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
1347 includeProperties), null);
1348 Message msg = Message.obtain(handler, task);
1349 msg.setAsynchronous(true);
1350 handler.sendMessage(msg);
1351 while (true) {
1352 try {
1353 task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
1354 return;
1355 } catch (InterruptedException e) {
1356 // try again
1357 } catch (ExecutionException | TimeoutException e) {
1358 // Something unexpected happened.
1359 throw new RuntimeException(e);
1360 }
1361 }
1362 }
1363 }
1364
1365 private static void cacheExportedPropertiesForChildren(ViewGroup group) {
1366 final int count = group.getChildCount();
1367 for (int i = 0; i < count; i++) {
1368 final View view = group.getChildAt(i);
1369 cacheExportedProperties(view.getClass());
1370 if (view instanceof ViewGroup) {
1371 cacheExportedPropertiesForChildren((ViewGroup) view);
1372 }
1373 }
1374 }
1375
1376 private static void cacheExportedProperties(Class<?> klass) {
1377 if (sExportProperties != null && sExportProperties.containsKey(klass)) {
1378 return;
1379 }
1380 do {
1381 for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
1382 if (!info.returnType.isPrimitive() && info.property.deepExport()) {
1383 cacheExportedProperties(info.returnType);
1384 }
1385 }
1386 klass = klass.getSuperclass();
1387 } while (klass != Object.class);
1388 }
1389
1390
1391 private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
1392 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001393 if (!dumpView(context, group, out, level, includeProperties)) {
1394 return;
1395 }
1396
1397 if (skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 return;
1399 }
1400
1401 final int count = group.getChildCount();
1402 for (int i = 0; i < count; i++) {
1403 final View view = group.getChildAt(i);
1404 if (view instanceof ViewGroup) {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001405 dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
1406 skipChildren, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001407 } else {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001408 dumpView(context, view, out, level + 1, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001409 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001410 if (view.mOverlay != null) {
1411 ViewOverlay overlay = view.getOverlay();
1412 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
Sunny Goyal7a889e12019-10-11 13:23:42 -07001413 dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
1414 skipChildren, includeProperties);
Chet Haase68bf5bd2013-09-05 16:27:28 -07001415 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416 }
John Reck926cf562012-06-14 10:00:31 -07001417 if (group instanceof HierarchyHandler) {
1418 ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
1419 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 }
1421
Siva Velusamy945bfb62013-01-06 16:03:12 -08001422 private static boolean dumpView(Context context, View view,
1423 BufferedWriter out, int level, boolean includeProperties) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001424
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001425 try {
1426 for (int i = 0; i < level; i++) {
1427 out.write(' ');
1428 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001429 String className = view.getClass().getName();
1430 if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
1431 className = "ViewOverlay";
1432 }
1433 out.write(className);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001434 out.write('@');
1435 out.write(Integer.toHexString(view.hashCode()));
1436 out.write(' ');
Siva Velusamy945bfb62013-01-06 16:03:12 -08001437 if (includeProperties) {
1438 dumpViewProperties(context, view, out);
1439 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 out.newLine();
1441 } catch (IOException e) {
1442 Log.w("View", "Error while dumping hierarchy tree");
1443 return false;
1444 }
1445 return true;
1446 }
1447
Sunny Goyal7a889e12019-10-11 13:23:42 -07001448 private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
1449 Method[] methods, Field[] fields, Class<T> property) {
1450 return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
1451 Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
1452 .flatMap(Function.identity())
1453 .filter(i -> i != null)
1454 .toArray(PropertyInfo[]::new);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001455 }
1456
Sunny Goyal7a889e12019-10-11 13:23:42 -07001457 private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
1458 if (sExportProperties == null) {
1459 sExportProperties = new HashMap<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001461 final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
1462 PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
1463
1464 if (properties == null) {
1465 properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false),
1466 klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class);
1467 map.put(klass, properties);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001468 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001469 return properties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 }
1471
The Android Open Source Project10592532009-03-18 17:39:46 -07001472 private static void dumpViewProperties(Context context, Object view,
1473 BufferedWriter out) throws IOException {
1474
1475 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 }
1477
The Android Open Source Project10592532009-03-18 17:39:46 -07001478 private static void dumpViewProperties(Context context, Object view,
1479 BufferedWriter out, String prefix) throws IOException {
1480
Romain Guydfab3632012-10-03 14:53:25 -07001481 if (view == null) {
1482 out.write(prefix + "=4,null ");
1483 return;
1484 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001485
Romain Guydfab3632012-10-03 14:53:25 -07001486 Class<?> klass = view.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001487 do {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001488 writeExportedProperties(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489 klass = klass.getSuperclass();
1490 } while (klass != Object.class);
1491 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001492
Jon Miranda4597e982014-07-29 07:25:49 -07001493 private static String formatIntToHexString(int value) {
1494 return "0x" + Integer.toHexString(value).toUpperCase();
1495 }
1496
Sunny Goyal7a889e12019-10-11 13:23:42 -07001497 private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
The Android Open Source Project10592532009-03-18 17:39:46 -07001498 Class<?> klass, String prefix) throws IOException {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001499 for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 //noinspection EmptyCatchBlock
Sunny Goyal7a889e12019-10-11 13:23:42 -07001501 Object value;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 try {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001503 value = info.invoke(view);
1504 } catch (Exception e) {
1505 // ignore
1506 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001507 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508
Sunny Goyal7a889e12019-10-11 13:23:42 -07001509 String categoryPrefix =
1510 info.property.category().length() != 0 ? info.property.category() + ":" : "";
The Android Open Source Project10592532009-03-18 17:39:46 -07001511
Sunny Goyal7a889e12019-10-11 13:23:42 -07001512 if (info.returnType == int.class || info.returnType == byte.class) {
1513 if (info.property.resolveId() && context != null) {
1514 final int id = (Integer) value;
1515 value = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516
Sunny Goyal7a889e12019-10-11 13:23:42 -07001517 } else if (info.property.formatToHexString()) {
1518 if (info.returnType == int.class) {
1519 value = formatIntToHexString((Integer) value);
1520 } else if (info.returnType == byte.class) {
1521 value = "0x"
1522 + HexEncoding.encodeToString((Byte) value, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001523 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001524 } else {
1525 final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
1526 if (flagsMapping.length > 0) {
1527 final int intValue = (Integer) value;
1528 final String valuePrefix =
1529 categoryPrefix + prefix + info.name + '_';
1530 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
Jon Miranda836c0a82014-08-11 12:32:26 -07001531 }
1532
Sunny Goyal7a889e12019-10-11 13:23:42 -07001533 final ViewDebug.IntToString[] mapping = info.property.mapping();
1534 if (mapping.length > 0) {
1535 final int intValue = (Integer) value;
1536 boolean mapped = false;
1537 int mappingCount = mapping.length;
1538 for (int j = 0; j < mappingCount; j++) {
1539 final ViewDebug.IntToString mapper = mapping[j];
1540 if (mapper.from() == intValue) {
1541 value = mapper.to();
1542 mapped = true;
1543 break;
1544 }
1545 }
1546
1547 if (!mapped) {
1548 value = intValue;
1549 }
1550 }
1551 }
1552 } else if (info.returnType == int[].class) {
1553 final int[] array = (int[]) value;
1554 final String valuePrefix = categoryPrefix + prefix + info.name + '_';
1555 exportUnrolledArray(context, out, info.property, array, valuePrefix,
1556 info.entrySuffix);
1557
1558 continue;
1559 } else if (info.returnType == String[].class) {
1560 final String[] array = (String[]) value;
1561 if (info.property.hasAdjacentMapping() && array != null) {
1562 for (int j = 0; j < array.length; j += 2) {
1563 if (array[j] != null) {
1564 writeEntry(out, categoryPrefix + prefix, array[j],
1565 info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
1566 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 }
1568 }
1569
Sunny Goyal7a889e12019-10-11 13:23:42 -07001570 continue;
1571 } else if (!info.returnType.isPrimitive()) {
1572 if (info.property.deepExport()) {
1573 dumpViewProperties(context, value, out, prefix + info.property.prefix());
1574 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001577
1578 writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 }
1580 }
1581
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001582 private static void writeEntry(BufferedWriter out, String prefix, String name,
1583 String suffix, Object value) throws IOException {
1584
1585 out.write(prefix);
1586 out.write(name);
1587 out.write(suffix);
1588 out.write("=");
1589 writeValue(out, value);
1590 out.write(' ');
1591 }
1592
Romain Guy809a7f62009-05-14 15:44:42 -07001593 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1594 int intValue, String prefix) throws IOException {
1595
1596 final int count = mapping.length;
1597 for (int j = 0; j < count; j++) {
1598 final FlagToString flagMapping = mapping[j];
1599 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001600 final int maskResult = intValue & flagMapping.mask();
1601 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001602 if ((test && ifTrue) || (!test && !ifTrue)) {
1603 final String name = flagMapping.name();
Jon Miranda4597e982014-07-29 07:25:49 -07001604 final String value = formatIntToHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001605 writeEntry(out, prefix, name, "", value);
1606 }
1607 }
1608 }
1609
Jorim Jaggi484851b2017-09-22 16:03:27 +02001610 /**
1611 * Converts an integer from a field that is mapped with {@link IntToString} to its string
1612 * representation.
1613 *
1614 * @param clazz The class the field is defined on.
1615 * @param field The field on which the {@link ExportedProperty} is defined on.
1616 * @param integer The value to convert.
1617 * @return The value converted into its string representation.
1618 * @hide
1619 */
1620 public static String intToString(Class<?> clazz, String field, int integer) {
1621 final IntToString[] mapping = getMapping(clazz, field);
1622 if (mapping == null) {
1623 return Integer.toString(integer);
1624 }
1625 final int count = mapping.length;
1626 for (int j = 0; j < count; j++) {
1627 final IntToString map = mapping[j];
1628 if (map.from() == integer) {
1629 return map.to();
1630 }
1631 }
1632 return Integer.toString(integer);
1633 }
1634
1635 /**
1636 * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
1637 * representation.
1638 *
1639 * @param clazz The class the field is defined on.
1640 * @param field The field on which the {@link ExportedProperty} is defined on.
1641 * @param flags The flags to convert.
1642 * @return The flags converted into their string representations.
1643 * @hide
1644 */
1645 public static String flagsToString(Class<?> clazz, String field, int flags) {
1646 final FlagToString[] mapping = getFlagMapping(clazz, field);
1647 if (mapping == null) {
1648 return Integer.toHexString(flags);
1649 }
1650 final StringBuilder result = new StringBuilder();
1651 final int count = mapping.length;
1652 for (int j = 0; j < count; j++) {
1653 final FlagToString flagMapping = mapping[j];
1654 final boolean ifTrue = flagMapping.outputIf();
1655 final int maskResult = flags & flagMapping.mask();
1656 final boolean test = maskResult == flagMapping.equals();
1657 if (test && ifTrue) {
1658 final String name = flagMapping.name();
1659 result.append(name).append(' ');
1660 }
1661 }
Jorim Jaggi2d3954f2017-09-28 16:56:46 +02001662 if (result.length() > 0) {
1663 result.deleteCharAt(result.length() - 1);
1664 }
Jorim Jaggi484851b2017-09-22 16:03:27 +02001665 return result.toString();
1666 }
1667
1668 private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
1669 try {
1670 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
1671 .flagMapping();
1672 } catch (NoSuchFieldException e) {
1673 return null;
1674 }
1675 }
1676
1677 private static IntToString[] getMapping(Class<?> clazz, String field) {
1678 try {
1679 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
1680 } catch (NoSuchFieldException e) {
1681 return null;
1682 }
1683 }
1684
The Android Open Source Project10592532009-03-18 17:39:46 -07001685 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001686 ExportedProperty property, int[] array, String prefix, String suffix)
1687 throws IOException {
1688
1689 final IntToString[] indexMapping = property.indexMapping();
1690 final boolean hasIndexMapping = indexMapping.length > 0;
1691
1692 final IntToString[] mapping = property.mapping();
1693 final boolean hasMapping = mapping.length > 0;
1694
The Android Open Source Project10592532009-03-18 17:39:46 -07001695 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001696 final int valuesCount = array.length;
1697
1698 for (int j = 0; j < valuesCount; j++) {
1699 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001700 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001701
1702 final int intValue = array[j];
1703
1704 name = String.valueOf(j);
1705 if (hasIndexMapping) {
1706 int mappingCount = indexMapping.length;
1707 for (int k = 0; k < mappingCount; k++) {
1708 final IntToString mapped = indexMapping[k];
1709 if (mapped.from() == j) {
1710 name = mapped.to();
1711 break;
1712 }
1713 }
1714 }
1715
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001716 if (hasMapping) {
1717 int mappingCount = mapping.length;
1718 for (int k = 0; k < mappingCount; k++) {
1719 final IntToString mapped = mapping[k];
1720 if (mapped.from() == intValue) {
1721 value = mapped.to();
1722 break;
1723 }
1724 }
1725 }
1726
1727 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001728 if (value == null) value = (String) resolveId(context, intValue);
1729 } else {
1730 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001731 }
1732
1733 writeEntry(out, prefix, name, suffix, value);
1734 }
1735 }
1736
Romain Guy237c1ce2009-12-08 11:30:25 -08001737 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001738 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001739 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001740 if (id >= 0) {
1741 try {
1742 fieldValue = resources.getResourceTypeName(id) + '/' +
1743 resources.getResourceEntryName(id);
1744 } catch (Resources.NotFoundException e) {
Jon Miranda4597e982014-07-29 07:25:49 -07001745 fieldValue = "id/" + formatIntToHexString(id);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001746 }
1747 } else {
1748 fieldValue = "NO_ID";
1749 }
1750 return fieldValue;
1751 }
1752
1753 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1754 if (value != null) {
Romain Guy97723b22012-09-27 23:36:34 -07001755 String output = "[EXCEPTION]";
1756 try {
1757 output = value.toString().replace("\n", "\\n");
1758 } finally {
1759 out.write(String.valueOf(output.length()));
1760 out.write(",");
1761 out.write(output);
1762 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001763 } else {
1764 out.write("4,null");
1765 }
1766 }
1767
Sunny Goyal7a889e12019-10-11 13:23:42 -07001768 private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
1769 Class<?> klass) {
1770 if (sCapturedViewProperties == null) {
1771 sCapturedViewProperties = new HashMap<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001772 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001773 final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
1774 sCapturedViewProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001775
Sunny Goyal7a889e12019-10-11 13:23:42 -07001776 PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
1777 if (infos == null) {
1778 infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
1779 CapturedViewProperty.class);
1780 map.put(klass, infos);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001781 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001782 return infos;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001783 }
1784
Sunny Goyal7a889e12019-10-11 13:23:42 -07001785 private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001786 if (obj == null) {
1787 return "null";
1788 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001790 StringBuilder sb = new StringBuilder();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001791
Sunny Goyal7a889e12019-10-11 13:23:42 -07001792 for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001793 try {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001794 Object methodValue = pi.invoke(obj);
Romain Guya1f3e4a2009-06-04 15:10:46 -07001795
Sunny Goyal7a889e12019-10-11 13:23:42 -07001796 if (pi.property.retrieveReturn()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001797 //we are interested in the second level data only
Sunny Goyal7a889e12019-10-11 13:23:42 -07001798 sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
1799 pi.name + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001800 } else {
Sunny Goyal7a889e12019-10-11 13:23:42 -07001801 sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001802
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001803 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001804 final String value = methodValue.toString().replace("\n", "\\n");
1805 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001806 } else {
1807 sb.append("null");
1808 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001809 sb.append(pi.valueSuffix).append(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001810 }
Sunny Goyal7a889e12019-10-11 13:23:42 -07001811 } catch (Exception e) {
1812 //It is OK here, we simply ignore this property
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001813 }
1814 }
1815 return sb.toString();
1816 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001817
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001818 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001819 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001820 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001821 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001822 * @param tag for log
1823 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001824 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001825 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001826 Class<?> klass = view.getClass();
1827 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
Sunny Goyal7a889e12019-10-11 13:23:42 -07001828 sb.append(exportCapturedViewProperties(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001829 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830 }
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001831
1832 /**
1833 * Invoke a particular method on given view.
1834 * The given method is always invoked on the UI thread. The caller thread will stall until the
1835 * method invocation is complete. Returns an object equal to the result of the method
1836 * invocation, null if the method is declared to return void
1837 * @throws Exception if the method invocation caused any exception
1838 * @hide
1839 */
1840 public static Object invokeViewMethod(final View view, final Method method,
1841 final Object[] args) {
1842 final CountDownLatch latch = new CountDownLatch(1);
1843 final AtomicReference<Object> result = new AtomicReference<Object>();
1844 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1845
1846 view.post(new Runnable() {
1847 @Override
1848 public void run() {
1849 try {
1850 result.set(method.invoke(view, args));
1851 } catch (InvocationTargetException e) {
1852 exception.set(e.getCause());
1853 } catch (Exception e) {
1854 exception.set(e);
1855 }
1856
1857 latch.countDown();
1858 }
1859 });
1860
1861 try {
1862 latch.await();
1863 } catch (InterruptedException e) {
1864 throw new RuntimeException(e);
1865 }
1866
1867 if (exception.get() != null) {
1868 throw new RuntimeException(exception.get());
1869 }
1870
1871 return result.get();
1872 }
1873
1874 /**
1875 * @hide
1876 */
1877 public static void setLayoutParameter(final View view, final String param, final int value)
1878 throws NoSuchFieldException, IllegalAccessException {
1879 final ViewGroup.LayoutParams p = view.getLayoutParams();
1880 final Field f = p.getClass().getField(param);
1881 if (f.getType() != int.class) {
1882 throw new RuntimeException("Only integer layout parameters can be set. Field "
Jon Miranda4597e982014-07-29 07:25:49 -07001883 + param + " is of type " + f.getType().getSimpleName());
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001884 }
1885
1886 f.set(p, Integer.valueOf(value));
1887
1888 view.post(new Runnable() {
1889 @Override
1890 public void run() {
1891 view.setLayoutParams(p);
1892 }
1893 });
1894 }
Sunny Goyald1b287e2018-01-04 09:37:22 -08001895
1896 /**
1897 * @hide
1898 */
1899 public static class SoftwareCanvasProvider implements CanvasProvider {
1900
1901 private Canvas mCanvas;
1902 private Bitmap mBitmap;
1903 private boolean mEnabledHwBitmapsInSwMode;
1904
1905 @Override
1906 public Canvas getCanvas(View view, int width, int height) {
1907 mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
1908 width, height, Bitmap.Config.ARGB_8888);
1909 if (mBitmap == null) {
1910 throw new OutOfMemoryError();
1911 }
1912 mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
1913
1914 if (view.mAttachInfo != null) {
1915 mCanvas = view.mAttachInfo.mCanvas;
1916 }
1917 if (mCanvas == null) {
1918 mCanvas = new Canvas();
1919 }
1920 mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
1921 mCanvas.setBitmap(mBitmap);
1922 return mCanvas;
1923 }
1924
1925 @Override
1926 public Bitmap createBitmap() {
1927 mCanvas.setBitmap(null);
1928 mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
1929 return mBitmap;
1930 }
1931 }
1932
1933 /**
1934 * @hide
1935 */
1936 public static class HardwareCanvasProvider implements CanvasProvider {
John Reck519ad482018-02-12 17:08:48 -08001937 private Picture mPicture;
Sunny Goyald1b287e2018-01-04 09:37:22 -08001938
1939 @Override
1940 public Canvas getCanvas(View view, int width, int height) {
John Reck519ad482018-02-12 17:08:48 -08001941 mPicture = new Picture();
1942 return mPicture.beginRecording(width, height);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001943 }
1944
1945 @Override
1946 public Bitmap createBitmap() {
John Reck519ad482018-02-12 17:08:48 -08001947 mPicture.endRecording();
1948 return Bitmap.createBitmap(mPicture);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001949 }
1950 }
1951
1952 /**
1953 * @hide
1954 */
1955 public interface CanvasProvider {
1956
1957 /**
1958 * Returns a canvas which can be used to draw {@param view}
1959 */
1960 Canvas getCanvas(View view, int width, int height);
1961
1962 /**
1963 * Creates a bitmap from previously returned canvas
1964 * @return
1965 */
1966 Bitmap createBitmap();
1967 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001968}