blob: c67fca59fe97dd473149ab29a811ae0cb1d93932 [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;
Mathew Inwooda570dee2018-08-17 14:56:00 +010022import android.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;
Romain Guy223ff5c2010-03-02 17:07:47 -080035import android.os.RemoteException;
Romain Guyf9284692011-07-13 18:46:21 -070036import android.util.DisplayMetrics;
37import android.util.Log;
Jon Miranda042ad632014-09-03 17:57:35 -070038import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
Neil Fullerb5d1c152019-04-04 21:02:06 +010040import libcore.util.HexEncoding;
41
Romain Guyf9284692011-07-13 18:46:21 -070042import java.io.BufferedOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import java.io.BufferedWriter;
Romain Guyf9284692011-07-13 18:46:21 -070044import java.io.ByteArrayOutputStream;
45import java.io.DataOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.io.OutputStream;
Romain Guyf9284692011-07-13 18:46:21 -070048import java.io.OutputStreamWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import java.lang.annotation.ElementType;
50import java.lang.annotation.Retention;
51import java.lang.annotation.RetentionPolicy;
Romain Guyf9284692011-07-13 18:46:21 -070052import java.lang.annotation.Target;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070053import java.lang.reflect.AccessibleObject;
Romain Guyf9284692011-07-13 18:46:21 -070054import java.lang.reflect.Field;
55import java.lang.reflect.InvocationTargetException;
56import java.lang.reflect.Method;
John Reck5cca8f22018-12-10 17:06:22 -080057import java.util.ArrayDeque;
Romain Guyf9284692011-07-13 18:46:21 -070058import java.util.ArrayList;
59import java.util.HashMap;
Kristian Monsen97e8f622013-04-26 12:51:19 -070060import java.util.concurrent.Callable;
61import java.util.concurrent.CancellationException;
Romain Guyf9284692011-07-13 18:46:21 -070062import java.util.concurrent.CountDownLatch;
Kristian Monsen97e8f622013-04-26 12:51:19 -070063import java.util.concurrent.ExecutionException;
John Reck5cca8f22018-12-10 17:06:22 -080064import java.util.concurrent.Executor;
Kristian Monsen97e8f622013-04-26 12:51:19 -070065import java.util.concurrent.FutureTask;
Romain Guyf9284692011-07-13 18:46:21 -070066import java.util.concurrent.TimeUnit;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070067import java.util.concurrent.TimeoutException;
Siva Velusamyf9455fa2013-01-17 18:01:52 -080068import java.util.concurrent.atomic.AtomicReference;
John Reck5cca8f22018-12-10 17:06:22 -080069import java.util.concurrent.locks.ReentrantLock;
70import java.util.function.Function;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
72/**
73 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
74 */
75public class ViewDebug {
76 /**
Romain Guy13b90732012-05-21 12:13:31 -070077 * @deprecated This flag is now unused
Romain Guy13922e02009-05-12 17:56:14 -070078 */
Romain Guy13b90732012-05-21 12:13:31 -070079 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 public static final boolean TRACE_HIERARCHY = false;
81
82 /**
Romain Guy13b90732012-05-21 12:13:31 -070083 * @deprecated This flag is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 */
Romain Guy13b90732012-05-21 12:13:31 -070085 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070087
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 /**
Christopher Tate2c095f32010-10-04 14:13:40 -070089 * Enables detailed logging of drag/drop operations.
90 * @hide
91 */
Christopher Tate994ef922011-01-12 20:06:07 -080092 public static final boolean DEBUG_DRAG = false;
Christopher Tate2c095f32010-10-04 14:13:40 -070093
94 /**
Chong Zhang8e89b312015-09-09 15:09:30 -070095 * Enables detailed logging of task positioning operations.
96 * @hide
97 */
98 public static final boolean DEBUG_POSITIONING = false;
99
100 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 * This annotation can be used to mark fields and methods to be dumped by
102 * the view server. Only non-void methods with no arguments can be annotated
103 * by this annotation.
104 */
105 @Target({ ElementType.FIELD, ElementType.METHOD })
106 @Retention(RetentionPolicy.RUNTIME)
107 public @interface ExportedProperty {
108 /**
109 * When resolveId is true, and if the annotated field/method return value
110 * is an int, the value is converted to an Android's resource name.
111 *
112 * @return true if the property's value must be transformed into an Android
113 * resource name, false otherwise
114 */
115 boolean resolveId() default false;
116
117 /**
118 * A mapping can be defined to map int values to specific strings. For
119 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
120 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
121 * these human readable values:
122 *
123 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800124 * {@literal @}ViewDebug.ExportedProperty(mapping = {
125 * {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"),
126 * {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
127 * {@literal @}ViewDebug.IntToString(from = 8, to = "GONE")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 * })
129 * public int getVisibility() { ...
130 * <pre>
131 *
132 * @return An array of int to String mappings
133 *
134 * @see android.view.ViewDebug.IntToString
135 */
136 IntToString[] mapping() default { };
137
138 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700139 * A mapping can be defined to map array indices to specific strings.
140 * A mapping can be used to see human readable values for the indices
141 * of an array:
142 *
143 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800144 * {@literal @}ViewDebug.ExportedProperty(indexMapping = {
145 * {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"),
146 * {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"),
147 * {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND")
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700148 * })
149 * private int[] mElements;
150 * <pre>
151 *
152 * @return An array of int to String mappings
153 *
154 * @see android.view.ViewDebug.IntToString
155 * @see #mapping()
156 */
157 IntToString[] indexMapping() default { };
158
159 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700160 * A flags mapping can be defined to map flags encoded in an integer to
161 * specific strings. A mapping can be used to see human readable values
162 * for the flags of an integer:
163 *
164 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800165 * {@literal @}ViewDebug.ExportedProperty(flagMapping = {
166 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED,
167 * name = "ENABLED"),
168 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED,
169 * name = "DISABLED"),
Romain Guy809a7f62009-05-14 15:44:42 -0700170 * })
171 * private int mFlags;
172 * <pre>
173 *
174 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700175 *
Romain Guy809a7f62009-05-14 15:44:42 -0700176 * @return An array of int to String mappings
177 */
178 FlagToString[] flagMapping() default { };
179
180 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 * When deep export is turned on, this property is not dumped. Instead, the
182 * properties contained in this property are dumped. Each child property
183 * is prefixed with the name of this property.
184 *
185 * @return true if the properties of this property should be dumped
186 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700187 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 */
189 boolean deepExport() default false;
190
191 /**
192 * The prefix to use on child properties when deep export is enabled
193 *
194 * @return a prefix as a String
195 *
196 * @see #deepExport()
197 */
198 String prefix() default "";
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700199
200 /**
201 * Specifies the category the property falls into, such as measurement,
202 * layout, drawing, etc.
203 *
204 * @return the category as String
205 */
206 String category() default "";
Jon Miranda4597e982014-07-29 07:25:49 -0700207
208 /**
209 * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string.
210 *
211 * @return true if the supported values should be formatted as a hex string.
212 */
213 boolean formatToHexString() default false;
Jon Miranda836c0a82014-08-11 12:32:26 -0700214
215 /**
216 * Indicates whether or not the key to value mappings are held in adjacent indices.
217 *
218 * Note: Applies only to fields and methods that return String[].
219 *
220 * @return true if the key to value mappings are held in adjacent indices.
221 */
222 boolean hasAdjacentMapping() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 }
224
225 /**
226 * Defines a mapping from an int value to a String. Such a mapping can be used
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900227 * in an @ExportedProperty to provide more meaningful values to the end user.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 *
229 * @see android.view.ViewDebug.ExportedProperty
230 */
231 @Target({ ElementType.TYPE })
232 @Retention(RetentionPolicy.RUNTIME)
233 public @interface IntToString {
234 /**
235 * The original int value to map to a String.
236 *
237 * @return An arbitrary int value.
238 */
239 int from();
240
241 /**
242 * The String to use in place of the original int value.
243 *
244 * @return An arbitrary non-null String.
245 */
246 String to();
247 }
Romain Guy809a7f62009-05-14 15:44:42 -0700248
249 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900250 * Defines a mapping from a flag to a String. Such a mapping can be used
251 * in an @ExportedProperty to provide more meaningful values to the end user.
Romain Guy809a7f62009-05-14 15:44:42 -0700252 *
253 * @see android.view.ViewDebug.ExportedProperty
254 */
255 @Target({ ElementType.TYPE })
256 @Retention(RetentionPolicy.RUNTIME)
257 public @interface FlagToString {
258 /**
259 * The mask to apply to the original value.
260 *
261 * @return An arbitrary int value.
262 */
263 int mask();
264
265 /**
266 * The value to compare to the result of:
267 * <code>original value &amp; {@link #mask()}</code>.
268 *
269 * @return An arbitrary value.
270 */
271 int equals();
272
273 /**
274 * The String to use in place of the original int value.
275 *
276 * @return An arbitrary non-null String.
277 */
278 String name();
279
280 /**
281 * Indicates whether to output the flag when the test is true,
282 * or false. Defaults to true.
283 */
284 boolean outputIf() default true;
285 }
286
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 /**
288 * This annotation can be used to mark fields and methods to be dumped when
289 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700290 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 */
292 @Target({ ElementType.FIELD, ElementType.METHOD })
293 @Retention(RetentionPolicy.RUNTIME)
294 public @interface CapturedViewProperty {
295 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700296 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700298 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700300 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700302 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700304
John Reck926cf562012-06-14 10:00:31 -0700305 /**
306 * Allows a View to inject custom children into HierarchyViewer. For example,
307 * WebView uses this to add its internal layer tree as a child to itself
308 * @hide
309 */
310 public interface HierarchyHandler {
311 /**
312 * Dumps custom children to hierarchy viewer.
John Reckf2361152012-06-14 15:23:22 -0700313 * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
John Reck926cf562012-06-14 10:00:31 -0700314 * for the format
315 *
316 * An empty implementation should simply do nothing
317 *
318 * @param out The output writer
319 * @param level The indentation level
320 */
321 public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
322
323 /**
324 * Returns a View to enable grabbing screenshots from custom children
325 * returned in dumpViewHierarchyWithProperties.
326 *
327 * @param className The className of the view to find
328 * @param hashCode The hashCode of the view to find
329 * @return the View to capture from, or null if not found
330 */
331 public View findHierarchyView(String className, int hashCode);
332 }
333
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
335 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
336
337 // Maximum delay in ms after which we stop trying to capture a View's drawing
338 private static final int CAPTURE_TIMEOUT = 4000;
339
340 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
341 private static final String REMOTE_COMMAND_DUMP = "DUMP";
Jon Miranda042ad632014-09-03 17:57:35 -0700342 private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
344 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700345 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800346 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
Chet Haaseed30fd82011-04-22 16:18:45 -0700347 private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348
349 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
350 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700351 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 /**
Romain Guy13b90732012-05-21 12:13:31 -0700354 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 */
Romain Guy13b90732012-05-21 12:13:31 -0700356 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 public enum HierarchyTraceType {
358 INVALIDATE,
359 INVALIDATE_CHILD,
360 INVALIDATE_CHILD_IN_PARENT,
361 REQUEST_LAYOUT,
362 ON_LAYOUT,
363 ON_MEASURE,
364 DRAW,
365 BUILD_CACHE
366 }
367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 /**
Romain Guy13b90732012-05-21 12:13:31 -0700369 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 */
Romain Guy13b90732012-05-21 12:13:31 -0700371 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 public enum RecyclerTraceType {
373 NEW_VIEW,
374 BIND_VIEW,
375 RECYCLE_FROM_ACTIVE_HEAP,
376 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 MOVE_TO_SCRAP_HEAP,
378 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
379 }
380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 /**
382 * Returns the number of instanciated Views.
383 *
384 * @return The number of Views instanciated in the current process.
385 *
386 * @hide
387 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100388 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 public static long getViewInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700390 return Debug.countInstancesOfClass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 }
392
393 /**
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700394 * Returns the number of instanciated ViewAncestors.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 *
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700396 * @return The number of ViewAncestors instanciated in the current process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 *
398 * @hide
399 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100400 @UnsupportedAppUsage
Romain Guy65b345f2011-07-27 18:51:50 -0700401 public static long getViewRootImplCount() {
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700402 return Debug.countInstancesOfClass(ViewRootImpl.class);
Romain Guya1f3e4a2009-06-04 15:10:46 -0700403 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404
405 /**
Romain Guy13b90732012-05-21 12:13:31 -0700406 * @deprecated This method is now unused and invoking it is a no-op
Romain Guyf9284692011-07-13 18:46:21 -0700407 */
Romain Guy13b90732012-05-21 12:13:31 -0700408 @Deprecated
409 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 public static void trace(View view, RecyclerTraceType type, int... parameters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 }
412
413 /**
Romain Guy13b90732012-05-21 12:13:31 -0700414 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 */
Romain Guy13b90732012-05-21 12:13:31 -0700416 @Deprecated
417 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800418 public static void startRecyclerTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420
421 /**
Romain Guy13b90732012-05-21 12:13:31 -0700422 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 */
Romain Guy13b90732012-05-21 12:13:31 -0700424 @Deprecated
425 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 public static void stopRecyclerTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800427 }
428
429 /**
Romain Guy13b90732012-05-21 12:13:31 -0700430 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 */
Romain Guy13b90732012-05-21 12:13:31 -0700432 @Deprecated
433 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 public static void trace(View view, HierarchyTraceType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 }
436
437 /**
Romain Guy13b90732012-05-21 12:13:31 -0700438 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 */
Romain Guy13b90732012-05-21 12:13:31 -0700440 @Deprecated
441 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 public static void startHierarchyTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 }
444
445 /**
Romain Guy13b90732012-05-21 12:13:31 -0700446 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 */
Romain Guy13b90732012-05-21 12:13:31 -0700448 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 public static void stopHierarchyTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700451
Mathew Inwooda570dee2018-08-17 14:56:00 +0100452 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 static void dispatchCommand(View view, String command, String parameters,
454 OutputStream clientStream) throws IOException {
455
456 // Paranoid but safe...
457 view = view.getRootView();
458
459 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
Siva Velusamy945bfb62013-01-06 16:03:12 -0800460 dump(view, false, true, clientStream);
Jon Miranda042ad632014-09-03 17:57:35 -0700461 } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
462 dumpTheme(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800463 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
464 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 } else {
466 final String[] params = parameters.split(" ");
467 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
468 capture(view, clientStream, params[0]);
Chet Haaseed30fd82011-04-22 16:18:45 -0700469 } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
470 outputDisplayList(view, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800471 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
472 invalidate(view, params[0]);
473 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
474 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700475 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
476 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477 }
478 }
479 }
480
Siva Velusamy945bfb62013-01-06 16:03:12 -0800481 /** @hide */
482 public static View findView(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700483 // Look by type/hashcode
484 if (parameter.indexOf('@') != -1) {
485 final String[] ids = parameter.split("@");
486 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800487 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700489 View view = root.getRootView();
490 if (view instanceof ViewGroup) {
491 return findView((ViewGroup) view, className, hashCode);
492 }
493 } else {
494 // Look by id
495 final int id = root.getResources().getIdentifier(parameter, null, null);
496 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 }
498
499 return null;
500 }
501
502 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700503 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 if (view != null) {
505 view.postInvalidate();
506 }
507 }
508
509 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700510 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 if (view != null) {
512 root.post(new Runnable() {
513 public void run() {
514 view.requestLayout();
515 }
516 });
517 }
518 }
519
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700520 private static void profile(View root, OutputStream clientStream, String parameter)
521 throws IOException {
522
523 final View view = findView(root, parameter);
524 BufferedWriter out = null;
525 try {
526 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
527
528 if (view != null) {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700529 profileViewAndChildren(view, out);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700530 } else {
531 out.write("-1 -1 -1");
532 out.newLine();
533 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700534 out.write("DONE.");
535 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700536 } catch (Exception e) {
537 android.util.Log.w("View", "Problem profiling the view:", e);
538 } finally {
539 if (out != null) {
540 out.close();
541 }
542 }
543 }
544
Siva Velusamy945bfb62013-01-06 16:03:12 -0800545 /** @hide */
546 public static void profileViewAndChildren(final View view, BufferedWriter out)
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700547 throws IOException {
Sunny Goyalc1043202017-10-16 14:00:44 -0700548 RenderNode node = RenderNode.create("ViewDebug", null);
549 profileViewAndChildren(view, node, out, true);
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700550 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700551
Sunny Goyalc1043202017-10-16 14:00:44 -0700552 private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
553 boolean root) throws IOException {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700554 long durationMeasure =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700555 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700556 ? profileViewMeasure(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700557 long durationLayout =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700558 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700559 ? profileViewLayout(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700560 long durationDraw =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700561 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700562 ? profileViewDraw(view, node) : 0;
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700563
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700564 out.write(String.valueOf(durationMeasure));
565 out.write(' ');
566 out.write(String.valueOf(durationLayout));
567 out.write(' ');
568 out.write(String.valueOf(durationDraw));
569 out.newLine();
570 if (view instanceof ViewGroup) {
571 ViewGroup group = (ViewGroup) view;
572 final int count = group.getChildCount();
573 for (int i = 0; i < count; i++) {
Sunny Goyalc1043202017-10-16 14:00:44 -0700574 profileViewAndChildren(group.getChildAt(i), node, out, false);
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700575 }
576 }
577 }
578
Sunny Goyalc1043202017-10-16 14:00:44 -0700579 private static long profileViewMeasure(final View view) {
580 return profileViewOperation(view, new ViewOperation() {
581 @Override
582 public void pre() {
583 forceLayout(view);
584 }
585
586 private void forceLayout(View view) {
587 view.forceLayout();
588 if (view instanceof ViewGroup) {
589 ViewGroup group = (ViewGroup) view;
590 final int count = group.getChildCount();
591 for (int i = 0; i < count; i++) {
592 forceLayout(group.getChildAt(i));
593 }
594 }
595 }
596
597 @Override
598 public void run() {
599 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
600 }
601 });
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700602 }
603
Sunny Goyalc1043202017-10-16 14:00:44 -0700604 private static long profileViewLayout(View view) {
605 return profileViewOperation(view,
606 () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
607 }
608
609 private static long profileViewDraw(View view, RenderNode node) {
610 DisplayMetrics dm = view.getResources().getDisplayMetrics();
611 if (dm == null) {
612 return 0;
613 }
614
615 if (view.isHardwareAccelerated()) {
John Recke57475e2019-02-20 17:39:52 -0800616 RecordingCanvas canvas = node.beginRecording(dm.widthPixels, dm.heightPixels);
Sunny Goyalc1043202017-10-16 14:00:44 -0700617 try {
618 return profileViewOperation(view, () -> view.draw(canvas));
619 } finally {
John Recke57475e2019-02-20 17:39:52 -0800620 node.endRecording();
Sunny Goyalc1043202017-10-16 14:00:44 -0700621 }
622 } else {
623 Bitmap bitmap = Bitmap.createBitmap(
624 dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
625 Canvas canvas = new Canvas(bitmap);
626 try {
627 return profileViewOperation(view, () -> view.draw(canvas));
628 } finally {
629 canvas.setBitmap(null);
630 bitmap.recycle();
631 }
632 }
633 }
634
635 interface ViewOperation {
636 default void pre() {}
637
638 void run();
639 }
640
641 private static long profileViewOperation(View view, final ViewOperation operation) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700642 final CountDownLatch latch = new CountDownLatch(1);
643 final long[] duration = new long[1];
644
Sunny Goyalc1043202017-10-16 14:00:44 -0700645 view.post(() -> {
646 try {
647 operation.pre();
648 long start = Debug.threadCpuTimeNanos();
649 //noinspection unchecked
650 operation.run();
651 duration[0] = Debug.threadCpuTimeNanos() - start;
652 } finally {
653 latch.countDown();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700654 }
655 });
656
657 try {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700658 if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
659 Log.w("View", "Could not complete the profiling of the view " + view);
660 return -1;
661 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700662 } catch (InterruptedException e) {
663 Log.w("View", "Could not complete the profiling of the view " + view);
664 Thread.currentThread().interrupt();
665 return -1;
666 }
667
668 return duration[0];
669 }
670
Siva Velusamy945bfb62013-01-06 16:03:12 -0800671 /** @hide */
672 public static void captureLayers(View root, final DataOutputStream clientStream)
Romain Guy223ff5c2010-03-02 17:07:47 -0800673 throws IOException {
674
675 try {
676 Rect outRect = new Rect();
677 try {
678 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
679 } catch (RemoteException e) {
680 // Ignore
681 }
Jon Miranda4597e982014-07-29 07:25:49 -0700682
Romain Guy223ff5c2010-03-02 17:07:47 -0800683 clientStream.writeInt(outRect.width());
684 clientStream.writeInt(outRect.height());
Jon Miranda4597e982014-07-29 07:25:49 -0700685
Romain Guy65554f22010-03-22 18:58:21 -0700686 captureViewLayer(root, clientStream, true);
Jon Miranda4597e982014-07-29 07:25:49 -0700687
Romain Guy223ff5c2010-03-02 17:07:47 -0800688 clientStream.write(2);
689 } finally {
690 clientStream.close();
691 }
692 }
693
Romain Guy65554f22010-03-22 18:58:21 -0700694 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -0800695 throws IOException {
696
Romain Guy65554f22010-03-22 18:58:21 -0700697 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
698
Dianne Hackborn4702a852012-08-17 15:18:29 -0700699 if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
Romain Guy223ff5c2010-03-02 17:07:47 -0800700 final int id = view.getId();
701 String name = view.getClass().getSimpleName();
702 if (id != View.NO_ID) {
703 name = resolveId(view.getContext(), id).toString();
704 }
Jon Miranda4597e982014-07-29 07:25:49 -0700705
Romain Guy223ff5c2010-03-02 17:07:47 -0800706 clientStream.write(1);
707 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -0700708 clientStream.writeByte(localVisible ? 1 : 0);
Jon Miranda4597e982014-07-29 07:25:49 -0700709
Romain Guy223ff5c2010-03-02 17:07:47 -0800710 int[] position = new int[2];
711 // XXX: Should happen on the UI thread
712 view.getLocationInWindow(position);
Jon Miranda4597e982014-07-29 07:25:49 -0700713
Romain Guy223ff5c2010-03-02 17:07:47 -0800714 clientStream.writeInt(position[0]);
715 clientStream.writeInt(position[1]);
716 clientStream.flush();
Jon Miranda4597e982014-07-29 07:25:49 -0700717
Romain Guy223ff5c2010-03-02 17:07:47 -0800718 Bitmap b = performViewCapture(view, true);
719 if (b != null) {
720 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
721 b.getHeight() * 2);
722 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
723 clientStream.writeInt(arrayOut.size());
724 arrayOut.writeTo(clientStream);
725 }
726 clientStream.flush();
727 }
728
729 if (view instanceof ViewGroup) {
730 ViewGroup group = (ViewGroup) view;
731 int count = group.getChildCount();
732
733 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -0700734 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -0800735 }
736 }
Chet Haase68bf5bd2013-09-05 16:27:28 -0700737
738 if (view.mOverlay != null) {
739 ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
740 captureViewLayer(overlayContainer, clientStream, localVisible);
741 }
Romain Guy223ff5c2010-03-02 17:07:47 -0800742 }
743
Chet Haaseed30fd82011-04-22 16:18:45 -0700744 private static void outputDisplayList(View root, String parameter) throws IOException {
745 final View view = findView(root, parameter);
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700746 view.getViewRootImpl().outputDisplayList(view);
Chet Haaseed30fd82011-04-22 16:18:45 -0700747 }
748
Siva Velusamy945bfb62013-01-06 16:03:12 -0800749 /** @hide */
750 public static void outputDisplayList(View root, View target) {
751 root.getViewRootImpl().outputDisplayList(target);
752 }
753
John Reck5cca8f22018-12-10 17:06:22 -0800754 private static class PictureCallbackHandler implements AutoCloseable,
755 HardwareRenderer.PictureCapturedCallback, Runnable {
756 private final HardwareRenderer mRenderer;
757 private final Function<Picture, Boolean> mCallback;
758 private final Executor mExecutor;
759 private final ReentrantLock mLock = new ReentrantLock(false);
760 private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
761 private boolean mStopListening;
762 private Thread mRenderThread;
763
764 private PictureCallbackHandler(HardwareRenderer renderer,
765 Function<Picture, Boolean> callback, Executor executor) {
766 mRenderer = renderer;
767 mCallback = callback;
768 mExecutor = executor;
769 mRenderer.setPictureCaptureCallback(this);
770 }
771
772 @Override
773 public void close() {
774 mLock.lock();
775 mStopListening = true;
776 mLock.unlock();
777 mRenderer.setPictureCaptureCallback(null);
778 }
779
780 @Override
781 public void onPictureCaptured(Picture picture) {
782 mLock.lock();
783 if (mStopListening) {
784 mLock.unlock();
785 mRenderer.setPictureCaptureCallback(null);
786 return;
787 }
788 if (mRenderThread == null) {
789 mRenderThread = Thread.currentThread();
790 }
791 Picture toDestroy = null;
792 if (mQueue.size() == 3) {
793 toDestroy = mQueue.removeLast();
794 }
795 mQueue.add(picture);
796 mLock.unlock();
797 if (toDestroy == null) {
798 mExecutor.execute(this);
799 } else {
800 toDestroy.close();
801 }
802 }
803
804 @Override
805 public void run() {
806 mLock.lock();
807 final Picture picture = mQueue.poll();
808 final boolean isStopped = mStopListening;
809 mLock.unlock();
810 if (Thread.currentThread() == mRenderThread) {
811 close();
812 throw new IllegalStateException(
813 "ViewDebug#startRenderingCommandsCapture must be given an executor that "
814 + "invokes asynchronously");
815 }
816 if (isStopped) {
817 picture.close();
818 return;
819 }
820 final boolean keepReceiving = mCallback.apply(picture);
821 if (!keepReceiving) {
822 close();
823 }
824 }
825 }
826
827 /**
828 * Begins capturing the entire rendering commands for the view tree referenced by the given
829 * view. The view passed may be any View in the tree as long as it is attached. That is,
830 * {@link View#isAttachedToWindow()} must be true.
831 *
832 * Every time a frame is rendered a Picture will be passed to the given callback via the given
833 * executor. As long as the callback returns 'true' it will continue to receive new frames.
834 * The system will only invoke the callback at a rate that the callback is able to keep up with.
835 * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
836 * then the callback will only receive 33% of the frames produced.
837 *
838 * This method must be called on the same thread as the View tree.
839 *
840 * @param tree The View tree to capture the rendering commands.
841 * @param callback The callback to invoke on every frame produced. Should return true to
842 * continue receiving new frames, false to stop capturing.
843 * @param executor The executor to invoke the callback on. Recommend using a background thread
844 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
845 * exception will be thrown.
846 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
847 * that the callback may continue to receive another frame or two depending on thread timings.
848 * Returns null if the capture stream cannot be started, such as if there's no
849 * HardwareRenderer for the given view tree.
850 * @hide
John Reckd9425652019-02-15 12:32:31 -0800851 * @deprecated use {@link #startRenderingCommandsCapture(View, Executor, Callable)} instead.
John Reck5cca8f22018-12-10 17:06:22 -0800852 */
853 @TestApi
854 @Nullable
John Reckd9425652019-02-15 12:32:31 -0800855 @Deprecated
John Reck5cca8f22018-12-10 17:06:22 -0800856 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
857 Function<Picture, Boolean> callback) {
858 final View.AttachInfo attachInfo = tree.mAttachInfo;
859 if (attachInfo == null) {
860 throw new IllegalArgumentException("Given view isn't attached");
861 }
862 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
863 throw new IllegalStateException("Called on the wrong thread."
864 + " Must be called on the thread that owns the given View");
865 }
866 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
867 if (renderer != null) {
868 return new PictureCallbackHandler(renderer, callback, executor);
869 }
870 return null;
871 }
872
John Reck8d0da1a2019-10-03 13:20:08 -0700873 private static class StreamingPictureCallbackHandler implements AutoCloseable,
874 HardwareRenderer.PictureCapturedCallback, Runnable {
875 private final HardwareRenderer mRenderer;
876 private final Callable<OutputStream> mCallback;
877 private final Executor mExecutor;
878 private final ReentrantLock mLock = new ReentrantLock(false);
879 private final ArrayDeque<byte[]> mQueue = new ArrayDeque<>(3);
880 private final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
881 private boolean mStopListening;
882 private Thread mRenderThread;
883
884 private StreamingPictureCallbackHandler(HardwareRenderer renderer,
885 Callable<OutputStream> callback, Executor executor) {
886 mRenderer = renderer;
887 mCallback = callback;
888 mExecutor = executor;
889 mRenderer.setPictureCaptureCallback(this);
890 }
891
892 @Override
893 public void close() {
894 mLock.lock();
895 mStopListening = true;
896 mLock.unlock();
897 mRenderer.setPictureCaptureCallback(null);
898 }
899
900 @Override
901 public void onPictureCaptured(Picture picture) {
902 mLock.lock();
903 if (mStopListening) {
904 mLock.unlock();
905 mRenderer.setPictureCaptureCallback(null);
906 return;
907 }
908 if (mRenderThread == null) {
909 mRenderThread = Thread.currentThread();
910 }
911 boolean needsInvoke = true;
912 if (mQueue.size() == 3) {
913 mQueue.removeLast();
914 needsInvoke = false;
915 }
916 picture.writeToStream(mByteStream);
917 mQueue.add(mByteStream.toByteArray());
918 mByteStream.reset();
919 mLock.unlock();
920
921 if (needsInvoke) {
922 mExecutor.execute(this);
923 }
924 }
925
926 @Override
927 public void run() {
928 mLock.lock();
929 final byte[] picture = mQueue.poll();
930 final boolean isStopped = mStopListening;
931 mLock.unlock();
932 if (Thread.currentThread() == mRenderThread) {
933 close();
934 throw new IllegalStateException(
935 "ViewDebug#startRenderingCommandsCapture must be given an executor that "
936 + "invokes asynchronously");
937 }
938 if (isStopped) {
939 return;
940 }
941 OutputStream stream = null;
942 try {
943 stream = mCallback.call();
944 } catch (Exception ex) {
945 Log.w("ViewDebug", "Aborting rendering commands capture "
946 + "because callback threw exception", ex);
947 }
948 if (stream != null) {
949 try {
950 stream.write(picture);
951 } catch (IOException ex) {
952 Log.w("ViewDebug", "Aborting rendering commands capture "
953 + "due to IOException writing to output stream", ex);
954 }
955 } else {
956 close();
957 }
958 }
959 }
960
John Reckd9425652019-02-15 12:32:31 -0800961 /**
962 * Begins capturing the entire rendering commands for the view tree referenced by the given
963 * view. The view passed may be any View in the tree as long as it is attached. That is,
964 * {@link View#isAttachedToWindow()} must be true.
965 *
966 * Every time a frame is rendered the callback will be invoked on the given executor to
967 * provide an OutputStream to serialize to. As long as the callback returns a valid
968 * OutputStream the capturing will continue. The system will only invoke the callback at a rate
969 * that the callback & OutputStream is able to keep up with. That is, if it takes 48ms for the
970 * callback & serialization to complete and there is a 60fps animation running
971 * then the callback will only receive 33% of the frames produced.
972 *
973 * This method must be called on the same thread as the View tree.
974 *
975 * @param tree The View tree to capture the rendering commands.
976 * @param callback The callback to invoke on every frame produced. Should return an
977 * OutputStream to write the data to. Return null to cancel capture. The
978 * same stream may be returned each time as the serialized data contains
979 * start & end markers. The callback will not be invoked while a previous
980 * serialization is being performed, so if a single continuous stream is being
981 * used it is valid for the callback to write its own metadata to that stream
982 * in response to callback invocation.
983 * @param executor The executor to invoke the callback on. Recommend using a background thread
984 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
985 * exception will be thrown.
986 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
987 * that the callback may continue to receive another frame or two depending on thread timings.
988 * Returns null if the capture stream cannot be started, such as if there's no
989 * HardwareRenderer for the given view tree.
990 * @hide
991 */
992 @TestApi
993 @Nullable
994 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
995 Callable<OutputStream> callback) {
996 final View.AttachInfo attachInfo = tree.mAttachInfo;
997 if (attachInfo == null) {
998 throw new IllegalArgumentException("Given view isn't attached");
999 }
1000 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
1001 throw new IllegalStateException("Called on the wrong thread."
1002 + " Must be called on the thread that owns the given View");
1003 }
1004 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
1005 if (renderer != null) {
John Reck8d0da1a2019-10-03 13:20:08 -07001006 return new StreamingPictureCallbackHandler(renderer, callback, executor);
John Reckd9425652019-02-15 12:32:31 -08001007 }
1008 return null;
1009 }
1010
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 private static void capture(View root, final OutputStream clientStream, String parameter)
1012 throws IOException {
1013
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001014 final View captureView = findView(root, parameter);
Siva Velusamy945bfb62013-01-06 16:03:12 -08001015 capture(root, clientStream, captureView);
1016 }
1017
1018 /** @hide */
1019 public static void capture(View root, final OutputStream clientStream, View captureView)
1020 throws IOException {
Romain Guy223ff5c2010-03-02 17:07:47 -08001021 Bitmap b = performViewCapture(captureView, false);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001022
1023 if (b == null) {
Romain Guy223ff5c2010-03-02 17:07:47 -08001024 Log.w("View", "Failed to create capture bitmap!");
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001025 // Send an empty one so that it doesn't get stuck waiting for
1026 // something.
Dianne Hackborndde331c2012-08-03 14:01:57 -07001027 b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
1028 1, 1, Bitmap.Config.ARGB_8888);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001029 }
1030
1031 BufferedOutputStream out = null;
1032 try {
1033 out = new BufferedOutputStream(clientStream, 32 * 1024);
1034 b.compress(Bitmap.CompressFormat.PNG, 100, out);
1035 out.flush();
1036 } finally {
1037 if (out != null) {
1038 out.close();
1039 }
1040 b.recycle();
Romain Guy223ff5c2010-03-02 17:07:47 -08001041 }
1042 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001043
Chet Haase68bf5bd2013-09-05 16:27:28 -07001044 private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001046 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 final Bitmap[] cache = new Bitmap[1];
1048
Sunny Goyald1b287e2018-01-04 09:37:22 -08001049 captureView.post(() -> {
1050 try {
1051 CanvasProvider provider = captureView.isHardwareAccelerated()
1052 ? new HardwareCanvasProvider() : new SoftwareCanvasProvider();
1053 cache[0] = captureView.createSnapshot(provider, skipChildren);
1054 } catch (OutOfMemoryError e) {
1055 Log.w("View", "Out of memory for bitmap");
1056 } finally {
1057 latch.countDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 }
1059 });
1060
1061 try {
1062 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -08001063 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001064 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001065 Log.w("View", "Could not complete the capture of the view " + captureView);
1066 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 }
1068 }
Jon Miranda4597e982014-07-29 07:25:49 -07001069
Romain Guy223ff5c2010-03-02 17:07:47 -08001070 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 }
1072
Siva Velusamy945bfb62013-01-06 16:03:12 -08001073 /**
1074 * Dumps the view hierarchy starting from the given view.
Siva Velusamy0d857b92015-04-22 10:23:56 -07001075 * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
Siva Velusamy945bfb62013-01-06 16:03:12 -08001076 * @hide
1077 */
Aurimas Liutikas514c5ef2016-05-24 15:22:55 -07001078 @Deprecated
Mathew Inwooda570dee2018-08-17 14:56:00 +01001079 @UnsupportedAppUsage
Siva Velusamy945bfb62013-01-06 16:03:12 -08001080 public static void dump(View root, boolean skipChildren, boolean includeProperties,
1081 OutputStream clientStream) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 BufferedWriter out = null;
1083 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001084 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 View view = root.getRootView();
1086 if (view instanceof ViewGroup) {
1087 ViewGroup group = (ViewGroup) view;
Siva Velusamy945bfb62013-01-06 16:03:12 -08001088 dumpViewHierarchy(group.getContext(), group, out, 0,
1089 skipChildren, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 }
1091 out.write("DONE.");
1092 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001093 } catch (Exception e) {
1094 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001095 } finally {
1096 if (out != null) {
1097 out.close();
1098 }
1099 }
1100 }
1101
Jon Miranda042ad632014-09-03 17:57:35 -07001102 /**
Siva Velusamy0d857b92015-04-22 10:23:56 -07001103 * Dumps the view hierarchy starting from the given view.
1104 * Rather than using reflection, it uses View's encode method to obtain all the properties.
1105 * @hide
1106 */
1107 public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
1108 throws InterruptedException {
1109 final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
1110 final CountDownLatch latch = new CountDownLatch(1);
1111
1112 view.post(new Runnable() {
1113 @Override
1114 public void run() {
Manu Cornet4d052b32016-09-15 13:03:28 -07001115 encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
1116 encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
Siva Velusamy0d857b92015-04-22 10:23:56 -07001117 view.encode(encoder);
1118 latch.countDown();
1119 }
1120 });
1121
1122 latch.await(2, TimeUnit.SECONDS);
1123 encoder.endStream();
1124 }
1125
1126 /**
Jon Miranda042ad632014-09-03 17:57:35 -07001127 * Dumps the theme attributes from the given View.
1128 * @hide
1129 */
1130 public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
1131 BufferedWriter out = null;
1132 try {
1133 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
1134 String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
1135 view.getContext().getTheme());
1136 if (attributes != null) {
1137 for (int i = 0; i < attributes.length; i += 2) {
1138 if (attributes[i] != null) {
1139 out.write(attributes[i] + "\n");
1140 out.write(attributes[i + 1] + "\n");
1141 }
1142 }
1143 }
1144 out.write("DONE.");
1145 out.newLine();
1146 } catch (Exception e) {
1147 android.util.Log.w("View", "Problem dumping View Theme:", e);
1148 } finally {
1149 if (out != null) {
1150 out.close();
1151 }
1152 }
1153 }
1154
1155 /**
1156 * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
1157 *
1158 * @param resources Resources to resolve attributes from.
1159 * @param theme Theme to dump.
1160 * @return a String array containing pairs of adjacent Theme attribute data: name followed by
1161 * its value.
1162 *
1163 * @hide
1164 */
1165 private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
1166 TypedValue outValue = new TypedValue();
1167 String nullString = "null";
1168 int i = 0;
1169 int[] attributes = theme.getAllAttributes();
1170 String[] data = new String[attributes.length * 2];
1171 for (int attributeId : attributes) {
1172 try {
1173 data[i] = resources.getResourceName(attributeId);
1174 data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
1175 outValue.coerceToString().toString() : nullString;
1176 i += 2;
Jon Miranda7c744bd2014-09-09 17:06:31 -07001177
1178 // attempt to replace reference data with its name
1179 if (outValue.type == TypedValue.TYPE_REFERENCE) {
1180 data[i - 1] = resources.getResourceName(outValue.resourceId);
1181 }
Jon Miranda042ad632014-09-03 17:57:35 -07001182 } catch (Resources.NotFoundException e) {
1183 // ignore resources we can't resolve
1184 }
1185 }
1186 return data;
1187 }
1188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 private static View findView(ViewGroup group, String className, int hashCode) {
1190 if (isRequestedView(group, className, hashCode)) {
1191 return group;
1192 }
1193
1194 final int count = group.getChildCount();
1195 for (int i = 0; i < count; i++) {
1196 final View view = group.getChildAt(i);
1197 if (view instanceof ViewGroup) {
1198 final View found = findView((ViewGroup) view, className, hashCode);
1199 if (found != null) {
1200 return found;
1201 }
1202 } else if (isRequestedView(view, className, hashCode)) {
1203 return view;
1204 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001205 if (view.mOverlay != null) {
1206 final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
1207 className, hashCode);
1208 if (found != null) {
1209 return found;
1210 }
1211 }
John Reck926cf562012-06-14 10:00:31 -07001212 if (view instanceof HierarchyHandler) {
1213 final View found = ((HierarchyHandler)view)
1214 .findHierarchyView(className, hashCode);
1215 if (found != null) {
1216 return found;
1217 }
1218 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 return null;
1221 }
1222
1223 private static boolean isRequestedView(View view, String className, int hashCode) {
Chet Haase68bf5bd2013-09-05 16:27:28 -07001224 if (view.hashCode() == hashCode) {
1225 String viewClassName = view.getClass().getName();
1226 if (className.equals("ViewOverlay")) {
1227 return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
1228 } else {
1229 return className.equals(viewClassName);
1230 }
1231 }
1232 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 }
1234
Siva Velusamy945bfb62013-01-06 16:03:12 -08001235 private static void dumpViewHierarchy(Context context, ViewGroup group,
1236 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
1237 if (!dumpView(context, group, out, level, includeProperties)) {
1238 return;
1239 }
1240
1241 if (skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001242 return;
1243 }
1244
1245 final int count = group.getChildCount();
1246 for (int i = 0; i < count; i++) {
1247 final View view = group.getChildAt(i);
1248 if (view instanceof ViewGroup) {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001249 dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
1250 includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 } else {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001252 dumpView(context, view, out, level + 1, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001254 if (view.mOverlay != null) {
1255 ViewOverlay overlay = view.getOverlay();
1256 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
1257 dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
1258 includeProperties);
1259 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001260 }
John Reck926cf562012-06-14 10:00:31 -07001261 if (group instanceof HierarchyHandler) {
1262 ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
1263 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 }
1265
Siva Velusamy945bfb62013-01-06 16:03:12 -08001266 private static boolean dumpView(Context context, View view,
1267 BufferedWriter out, int level, boolean includeProperties) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269 try {
1270 for (int i = 0; i < level; i++) {
1271 out.write(' ');
1272 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001273 String className = view.getClass().getName();
1274 if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
1275 className = "ViewOverlay";
1276 }
1277 out.write(className);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 out.write('@');
1279 out.write(Integer.toHexString(view.hashCode()));
1280 out.write(' ');
Siva Velusamy945bfb62013-01-06 16:03:12 -08001281 if (includeProperties) {
1282 dumpViewProperties(context, view, out);
1283 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284 out.newLine();
1285 } catch (IOException e) {
1286 Log.w("View", "Error while dumping hierarchy tree");
1287 return false;
1288 }
1289 return true;
1290 }
1291
1292 private static Field[] getExportedPropertyFields(Class<?> klass) {
1293 if (sFieldsForClasses == null) {
1294 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
1295 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001296 if (sAnnotations == null) {
1297 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1298 }
1299
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
1301
1302 Field[] fields = map.get(klass);
1303 if (fields != null) {
1304 return fields;
1305 }
1306
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001307 try {
1308 final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
1309 final ArrayList<Field> foundFields = new ArrayList<Field>();
1310 for (final Field field : declaredFields) {
1311 // Fields which can't be resolved have a null type.
1312 if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
1313 field.setAccessible(true);
1314 foundFields.add(field);
1315 sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
1316 }
Alan Viverette99562fb2014-10-14 14:48:52 -07001317 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001318 fields = foundFields.toArray(new Field[foundFields.size()]);
1319 map.put(klass, fields);
1320 } catch (NoClassDefFoundError e) {
1321 throw new AssertionError(e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001322 }
1323
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324 return fields;
1325 }
1326
1327 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1328 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001329 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001331 if (sAnnotations == null) {
1332 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1333 }
1334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001335 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
1336
1337 Method[] methods = map.get(klass);
1338 if (methods != null) {
1339 return methods;
1340 }
1341
Mathieu Chartiera8a65162015-04-17 12:49:57 -07001342 methods = klass.getDeclaredMethodsUnchecked(false);
Romain Guya1f3e4a2009-06-04 15:10:46 -07001343
Alan Viverette99562fb2014-10-14 14:48:52 -07001344 final ArrayList<Method> foundMethods = new ArrayList<Method>();
Mathieu Chartiera8a65162015-04-17 12:49:57 -07001345 for (final Method method : methods) {
Alan Viverette99562fb2014-10-14 14:48:52 -07001346 // Ensure the method return and parameter types can be resolved.
1347 try {
1348 method.getReturnType();
1349 method.getParameterTypes();
1350 } catch (NoClassDefFoundError e) {
1351 continue;
1352 }
1353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001354 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001355 method.isAnnotationPresent(ExportedProperty.class) &&
1356 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001357 method.setAccessible(true);
1358 foundMethods.add(method);
Romain Guy88b4f152011-05-19 16:15:46 -07001359 sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001360 }
1361 }
1362
1363 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1364 map.put(klass, methods);
1365
1366 return methods;
1367 }
1368
The Android Open Source Project10592532009-03-18 17:39:46 -07001369 private static void dumpViewProperties(Context context, Object view,
1370 BufferedWriter out) throws IOException {
1371
1372 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 }
1374
The Android Open Source Project10592532009-03-18 17:39:46 -07001375 private static void dumpViewProperties(Context context, Object view,
1376 BufferedWriter out, String prefix) throws IOException {
1377
Romain Guydfab3632012-10-03 14:53:25 -07001378 if (view == null) {
1379 out.write(prefix + "=4,null ");
1380 return;
1381 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382
Romain Guydfab3632012-10-03 14:53:25 -07001383 Class<?> klass = view.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001385 exportFields(context, view, out, klass, prefix);
1386 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001387 klass = klass.getSuperclass();
1388 } while (klass != Object.class);
1389 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001390
Kristian Monsen97e8f622013-04-26 12:51:19 -07001391 private static Object callMethodOnAppropriateTheadBlocking(final Method method,
1392 final Object object) throws IllegalAccessException, InvocationTargetException,
1393 TimeoutException {
1394 if (!(object instanceof View)) {
1395 return method.invoke(object, (Object[]) null);
1396 }
1397
1398 final View view = (View) object;
1399 Callable<Object> callable = new Callable<Object>() {
Jon Miranda4597e982014-07-29 07:25:49 -07001400 @Override
1401 public Object call() throws IllegalAccessException, InvocationTargetException {
1402 return method.invoke(view, (Object[]) null);
1403 }
Kristian Monsen97e8f622013-04-26 12:51:19 -07001404 };
1405 FutureTask<Object> future = new FutureTask<Object>(callable);
1406 // Try to use the handler provided by the view
1407 Handler handler = view.getHandler();
1408 // Fall back on using the main thread
1409 if (handler == null) {
1410 handler = new Handler(android.os.Looper.getMainLooper());
1411 }
1412 handler.post(future);
1413 while (true) {
1414 try {
1415 return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
1416 } catch (ExecutionException e) {
1417 Throwable t = e.getCause();
1418 if (t instanceof IllegalAccessException) {
1419 throw (IllegalAccessException)t;
1420 }
1421 if (t instanceof InvocationTargetException) {
1422 throw (InvocationTargetException)t;
1423 }
1424 throw new RuntimeException("Unexpected exception", t);
1425 } catch (InterruptedException e) {
1426 // Call get again
1427 } catch (CancellationException e) {
1428 throw new RuntimeException("Unexpected cancellation exception", e);
1429 }
1430 }
1431 }
1432
Jon Miranda4597e982014-07-29 07:25:49 -07001433 private static String formatIntToHexString(int value) {
1434 return "0x" + Integer.toHexString(value).toUpperCase();
1435 }
1436
The Android Open Source Project10592532009-03-18 17:39:46 -07001437 private static void exportMethods(Context context, Object view, BufferedWriter out,
1438 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001439
1440 final Method[] methods = getExportedPropertyMethods(klass);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001441 int count = methods.length;
1442 for (int i = 0; i < count; i++) {
1443 final Method method = methods[i];
1444 //noinspection EmptyCatchBlock
1445 try {
Kristian Monsen97e8f622013-04-26 12:51:19 -07001446 Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001447 final Class<?> returnType = method.getReturnType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001448 final ExportedProperty property = sAnnotations.get(method);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001449 String categoryPrefix =
1450 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451
1452 if (returnType == int.class) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001453 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001455 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001456 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001457 final FlagToString[] flagsMapping = property.flagMapping();
1458 if (flagsMapping.length > 0) {
1459 final int intValue = (Integer) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001460 final String valuePrefix =
1461 categoryPrefix + prefix + method.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001462 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1463 }
1464
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001465 final IntToString[] mapping = property.mapping();
1466 if (mapping.length > 0) {
1467 final int intValue = (Integer) methodValue;
1468 boolean mapped = false;
1469 int mappingCount = mapping.length;
1470 for (int j = 0; j < mappingCount; j++) {
1471 final IntToString mapper = mapping[j];
1472 if (mapper.from() == intValue) {
1473 methodValue = mapper.to();
1474 mapped = true;
1475 break;
1476 }
1477 }
1478
1479 if (!mapped) {
1480 methodValue = intValue;
1481 }
1482 }
1483 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001484 } else if (returnType == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001485 final int[] array = (int[]) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001486 final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001487 final String suffix = "()";
1488
The Android Open Source Project10592532009-03-18 17:39:46 -07001489 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001490
Jon Miranda48520212014-08-08 10:49:47 -07001491 continue;
Jon Miranda836c0a82014-08-11 12:32:26 -07001492 } else if (returnType == String[].class) {
1493 final String[] array = (String[]) methodValue;
1494 if (property.hasAdjacentMapping() && array != null) {
1495 for (int j = 0; j < array.length; j += 2) {
1496 if (array[j] != null) {
1497 writeEntry(out, categoryPrefix + prefix, array[j], "()",
1498 array[j + 1] == null ? "null" : array[j + 1]);
1499 }
1500
1501 }
1502 }
1503
1504 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 } else if (!returnType.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001506 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001507 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 continue;
1509 }
1510 }
1511
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001512 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001513 } catch (IllegalAccessException e) {
1514 } catch (InvocationTargetException e) {
Kristian Monsen97e8f622013-04-26 12:51:19 -07001515 } catch (TimeoutException e) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001516 }
1517 }
1518 }
1519
The Android Open Source Project10592532009-03-18 17:39:46 -07001520 private static void exportFields(Context context, Object view, BufferedWriter out,
1521 Class<?> klass, String prefix) throws IOException {
1522
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001523 final Field[] fields = getExportedPropertyFields(klass);
1524
1525 int count = fields.length;
1526 for (int i = 0; i < count; i++) {
1527 final Field field = fields[i];
1528
1529 //noinspection EmptyCatchBlock
1530 try {
1531 Object fieldValue = null;
1532 final Class<?> type = field.getType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001533 final ExportedProperty property = sAnnotations.get(field);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001534 String categoryPrefix =
1535 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001536
Fabrice Di Megliob365f912013-03-27 16:36:21 -07001537 if (type == int.class || type == byte.class) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001538 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001540 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001541 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001542 final FlagToString[] flagsMapping = property.flagMapping();
1543 if (flagsMapping.length > 0) {
1544 final int intValue = field.getInt(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001545 final String valuePrefix =
1546 categoryPrefix + prefix + field.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001547 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1548 }
1549
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001550 final IntToString[] mapping = property.mapping();
1551 if (mapping.length > 0) {
1552 final int intValue = field.getInt(view);
1553 int mappingCount = mapping.length;
1554 for (int j = 0; j < mappingCount; j++) {
1555 final IntToString mapped = mapping[j];
1556 if (mapped.from() == intValue) {
1557 fieldValue = mapped.to();
1558 break;
1559 }
1560 }
1561
1562 if (fieldValue == null) {
1563 fieldValue = intValue;
1564 }
1565 }
Jon Miranda4597e982014-07-29 07:25:49 -07001566
1567 if (property.formatToHexString()) {
1568 fieldValue = field.get(view);
1569 if (type == int.class) {
1570 fieldValue = formatIntToHexString((Integer) fieldValue);
1571 } else if (type == byte.class) {
Neil Fullerb5d1c152019-04-04 21:02:06 +01001572 fieldValue = "0x"
1573 + HexEncoding.encodeToString((Byte) fieldValue, true);
Jon Miranda4597e982014-07-29 07:25:49 -07001574 }
1575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001577 } else if (type == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001578 final int[] array = (int[]) field.get(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001579 final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001580 final String suffix = "";
1581
The Android Open Source Project10592532009-03-18 17:39:46 -07001582 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001583
Jon Miranda48520212014-08-08 10:49:47 -07001584 continue;
Jon Miranda836c0a82014-08-11 12:32:26 -07001585 } else if (type == String[].class) {
1586 final String[] array = (String[]) field.get(view);
1587 if (property.hasAdjacentMapping() && array != null) {
1588 for (int j = 0; j < array.length; j += 2) {
1589 if (array[j] != null) {
1590 writeEntry(out, categoryPrefix + prefix, array[j], "",
1591 array[j + 1] == null ? "null" : array[j + 1]);
1592 }
1593 }
1594 }
1595
1596 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001597 } else if (!type.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001598 if (property.deepExport()) {
Romain Guydfab3632012-10-03 14:53:25 -07001599 dumpViewProperties(context, field.get(view), out, prefix +
1600 property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001601 continue;
1602 }
1603 }
1604
1605 if (fieldValue == null) {
1606 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001607 }
1608
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001609 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001610 } catch (IllegalAccessException e) {
1611 }
1612 }
1613 }
1614
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001615 private static void writeEntry(BufferedWriter out, String prefix, String name,
1616 String suffix, Object value) throws IOException {
1617
1618 out.write(prefix);
1619 out.write(name);
1620 out.write(suffix);
1621 out.write("=");
1622 writeValue(out, value);
1623 out.write(' ');
1624 }
1625
Romain Guy809a7f62009-05-14 15:44:42 -07001626 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1627 int intValue, String prefix) throws IOException {
1628
1629 final int count = mapping.length;
1630 for (int j = 0; j < count; j++) {
1631 final FlagToString flagMapping = mapping[j];
1632 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001633 final int maskResult = intValue & flagMapping.mask();
1634 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001635 if ((test && ifTrue) || (!test && !ifTrue)) {
1636 final String name = flagMapping.name();
Jon Miranda4597e982014-07-29 07:25:49 -07001637 final String value = formatIntToHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001638 writeEntry(out, prefix, name, "", value);
1639 }
1640 }
1641 }
1642
Jorim Jaggi484851b2017-09-22 16:03:27 +02001643 /**
1644 * Converts an integer from a field that is mapped with {@link IntToString} to its string
1645 * representation.
1646 *
1647 * @param clazz The class the field is defined on.
1648 * @param field The field on which the {@link ExportedProperty} is defined on.
1649 * @param integer The value to convert.
1650 * @return The value converted into its string representation.
1651 * @hide
1652 */
1653 public static String intToString(Class<?> clazz, String field, int integer) {
1654 final IntToString[] mapping = getMapping(clazz, field);
1655 if (mapping == null) {
1656 return Integer.toString(integer);
1657 }
1658 final int count = mapping.length;
1659 for (int j = 0; j < count; j++) {
1660 final IntToString map = mapping[j];
1661 if (map.from() == integer) {
1662 return map.to();
1663 }
1664 }
1665 return Integer.toString(integer);
1666 }
1667
1668 /**
1669 * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
1670 * representation.
1671 *
1672 * @param clazz The class the field is defined on.
1673 * @param field The field on which the {@link ExportedProperty} is defined on.
1674 * @param flags The flags to convert.
1675 * @return The flags converted into their string representations.
1676 * @hide
1677 */
1678 public static String flagsToString(Class<?> clazz, String field, int flags) {
1679 final FlagToString[] mapping = getFlagMapping(clazz, field);
1680 if (mapping == null) {
1681 return Integer.toHexString(flags);
1682 }
1683 final StringBuilder result = new StringBuilder();
1684 final int count = mapping.length;
1685 for (int j = 0; j < count; j++) {
1686 final FlagToString flagMapping = mapping[j];
1687 final boolean ifTrue = flagMapping.outputIf();
1688 final int maskResult = flags & flagMapping.mask();
1689 final boolean test = maskResult == flagMapping.equals();
1690 if (test && ifTrue) {
1691 final String name = flagMapping.name();
1692 result.append(name).append(' ');
1693 }
1694 }
Jorim Jaggi2d3954f2017-09-28 16:56:46 +02001695 if (result.length() > 0) {
1696 result.deleteCharAt(result.length() - 1);
1697 }
Jorim Jaggi484851b2017-09-22 16:03:27 +02001698 return result.toString();
1699 }
1700
1701 private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
1702 try {
1703 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
1704 .flagMapping();
1705 } catch (NoSuchFieldException e) {
1706 return null;
1707 }
1708 }
1709
1710 private static IntToString[] getMapping(Class<?> clazz, String field) {
1711 try {
1712 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
1713 } catch (NoSuchFieldException e) {
1714 return null;
1715 }
1716 }
1717
The Android Open Source Project10592532009-03-18 17:39:46 -07001718 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001719 ExportedProperty property, int[] array, String prefix, String suffix)
1720 throws IOException {
1721
1722 final IntToString[] indexMapping = property.indexMapping();
1723 final boolean hasIndexMapping = indexMapping.length > 0;
1724
1725 final IntToString[] mapping = property.mapping();
1726 final boolean hasMapping = mapping.length > 0;
1727
The Android Open Source Project10592532009-03-18 17:39:46 -07001728 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001729 final int valuesCount = array.length;
1730
1731 for (int j = 0; j < valuesCount; j++) {
1732 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001733 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001734
1735 final int intValue = array[j];
1736
1737 name = String.valueOf(j);
1738 if (hasIndexMapping) {
1739 int mappingCount = indexMapping.length;
1740 for (int k = 0; k < mappingCount; k++) {
1741 final IntToString mapped = indexMapping[k];
1742 if (mapped.from() == j) {
1743 name = mapped.to();
1744 break;
1745 }
1746 }
1747 }
1748
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001749 if (hasMapping) {
1750 int mappingCount = mapping.length;
1751 for (int k = 0; k < mappingCount; k++) {
1752 final IntToString mapped = mapping[k];
1753 if (mapped.from() == intValue) {
1754 value = mapped.to();
1755 break;
1756 }
1757 }
1758 }
1759
1760 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001761 if (value == null) value = (String) resolveId(context, intValue);
1762 } else {
1763 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001764 }
1765
1766 writeEntry(out, prefix, name, suffix, value);
1767 }
1768 }
1769
Romain Guy237c1ce2009-12-08 11:30:25 -08001770 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001771 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001772 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001773 if (id >= 0) {
1774 try {
1775 fieldValue = resources.getResourceTypeName(id) + '/' +
1776 resources.getResourceEntryName(id);
1777 } catch (Resources.NotFoundException e) {
Jon Miranda4597e982014-07-29 07:25:49 -07001778 fieldValue = "id/" + formatIntToHexString(id);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001779 }
1780 } else {
1781 fieldValue = "NO_ID";
1782 }
1783 return fieldValue;
1784 }
1785
1786 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1787 if (value != null) {
Romain Guy97723b22012-09-27 23:36:34 -07001788 String output = "[EXCEPTION]";
1789 try {
1790 output = value.toString().replace("\n", "\\n");
1791 } finally {
1792 out.write(String.valueOf(output.length()));
1793 out.write(",");
1794 out.write(output);
1795 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001796 } else {
1797 out.write("4,null");
1798 }
1799 }
1800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001801 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1802 if (mCapturedViewFieldsForClasses == null) {
1803 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1804 }
1805 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1806
1807 Field[] fields = map.get(klass);
1808 if (fields != null) {
1809 return fields;
1810 }
1811
1812 final ArrayList<Field> foundFields = new ArrayList<Field>();
1813 fields = klass.getFields();
1814
1815 int count = fields.length;
1816 for (int i = 0; i < count; i++) {
1817 final Field field = fields[i];
1818 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1819 field.setAccessible(true);
1820 foundFields.add(field);
1821 }
1822 }
1823
1824 fields = foundFields.toArray(new Field[foundFields.size()]);
1825 map.put(klass, fields);
1826
1827 return fields;
1828 }
1829
1830 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1831 if (mCapturedViewMethodsForClasses == null) {
1832 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1833 }
1834 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1835
1836 Method[] methods = map.get(klass);
1837 if (methods != null) {
1838 return methods;
1839 }
1840
1841 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1842 methods = klass.getMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001843
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001844 int count = methods.length;
1845 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001846 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001847 if (method.getParameterTypes().length == 0 &&
1848 method.isAnnotationPresent(CapturedViewProperty.class) &&
1849 method.getReturnType() != Void.class) {
1850 method.setAccessible(true);
1851 foundMethods.add(method);
1852 }
1853 }
1854
1855 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1856 map.put(klass, methods);
1857
1858 return methods;
1859 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001860
1861 private static String capturedViewExportMethods(Object obj, Class<?> klass,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001862 String prefix) {
1863
1864 if (obj == null) {
1865 return "null";
1866 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001868 StringBuilder sb = new StringBuilder();
1869 final Method[] methods = capturedViewGetPropertyMethods(klass);
1870
1871 int count = methods.length;
1872 for (int i = 0; i < count; i++) {
1873 final Method method = methods[i];
1874 try {
1875 Object methodValue = method.invoke(obj, (Object[]) null);
1876 final Class<?> returnType = method.getReturnType();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001877
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001878 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1879 if (property.retrieveReturn()) {
1880 //we are interested in the second level data only
1881 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001882 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001883 sb.append(prefix);
1884 sb.append(method.getName());
1885 sb.append("()=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001886
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001887 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001888 final String value = methodValue.toString().replace("\n", "\\n");
1889 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001890 } else {
1891 sb.append("null");
1892 }
1893 sb.append("; ");
1894 }
Jon Miranda4597e982014-07-29 07:25:49 -07001895 } catch (IllegalAccessException e) {
1896 //Exception IllegalAccess, it is OK here
1897 //we simply ignore this method
1898 } catch (InvocationTargetException e) {
1899 //Exception InvocationTarget, it is OK here
1900 //we simply ignore this method
1901 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001902 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001903 return sb.toString();
1904 }
1905
1906 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001907 if (obj == null) {
1908 return "null";
1909 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001910
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001911 StringBuilder sb = new StringBuilder();
1912 final Field[] fields = capturedViewGetPropertyFields(klass);
1913
1914 int count = fields.length;
1915 for (int i = 0; i < count; i++) {
1916 final Field field = fields[i];
1917 try {
1918 Object fieldValue = field.get(obj);
1919
1920 sb.append(prefix);
1921 sb.append(field.getName());
1922 sb.append("=");
1923
1924 if (fieldValue != null) {
1925 final String value = fieldValue.toString().replace("\n", "\\n");
1926 sb.append(value);
1927 } else {
1928 sb.append("null");
1929 }
1930 sb.append(' ');
1931 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001932 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001933 //we simply ignore this field
1934 }
1935 }
1936 return sb.toString();
1937 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001938
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001939 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001940 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001941 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001942 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001943 * @param tag for log
1944 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001945 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001946 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001947 Class<?> klass = view.getClass();
1948 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1949 sb.append(capturedViewExportFields(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001950 sb.append(capturedViewExportMethods(view, klass, ""));
1951 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001952 }
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001953
1954 /**
1955 * Invoke a particular method on given view.
1956 * The given method is always invoked on the UI thread. The caller thread will stall until the
1957 * method invocation is complete. Returns an object equal to the result of the method
1958 * invocation, null if the method is declared to return void
1959 * @throws Exception if the method invocation caused any exception
1960 * @hide
1961 */
1962 public static Object invokeViewMethod(final View view, final Method method,
1963 final Object[] args) {
1964 final CountDownLatch latch = new CountDownLatch(1);
1965 final AtomicReference<Object> result = new AtomicReference<Object>();
1966 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1967
1968 view.post(new Runnable() {
1969 @Override
1970 public void run() {
1971 try {
1972 result.set(method.invoke(view, args));
1973 } catch (InvocationTargetException e) {
1974 exception.set(e.getCause());
1975 } catch (Exception e) {
1976 exception.set(e);
1977 }
1978
1979 latch.countDown();
1980 }
1981 });
1982
1983 try {
1984 latch.await();
1985 } catch (InterruptedException e) {
1986 throw new RuntimeException(e);
1987 }
1988
1989 if (exception.get() != null) {
1990 throw new RuntimeException(exception.get());
1991 }
1992
1993 return result.get();
1994 }
1995
1996 /**
1997 * @hide
1998 */
1999 public static void setLayoutParameter(final View view, final String param, final int value)
2000 throws NoSuchFieldException, IllegalAccessException {
2001 final ViewGroup.LayoutParams p = view.getLayoutParams();
2002 final Field f = p.getClass().getField(param);
2003 if (f.getType() != int.class) {
2004 throw new RuntimeException("Only integer layout parameters can be set. Field "
Jon Miranda4597e982014-07-29 07:25:49 -07002005 + param + " is of type " + f.getType().getSimpleName());
Siva Velusamyf9455fa2013-01-17 18:01:52 -08002006 }
2007
2008 f.set(p, Integer.valueOf(value));
2009
2010 view.post(new Runnable() {
2011 @Override
2012 public void run() {
2013 view.setLayoutParams(p);
2014 }
2015 });
2016 }
Sunny Goyald1b287e2018-01-04 09:37:22 -08002017
2018 /**
2019 * @hide
2020 */
2021 public static class SoftwareCanvasProvider implements CanvasProvider {
2022
2023 private Canvas mCanvas;
2024 private Bitmap mBitmap;
2025 private boolean mEnabledHwBitmapsInSwMode;
2026
2027 @Override
2028 public Canvas getCanvas(View view, int width, int height) {
2029 mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
2030 width, height, Bitmap.Config.ARGB_8888);
2031 if (mBitmap == null) {
2032 throw new OutOfMemoryError();
2033 }
2034 mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
2035
2036 if (view.mAttachInfo != null) {
2037 mCanvas = view.mAttachInfo.mCanvas;
2038 }
2039 if (mCanvas == null) {
2040 mCanvas = new Canvas();
2041 }
2042 mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
2043 mCanvas.setBitmap(mBitmap);
2044 return mCanvas;
2045 }
2046
2047 @Override
2048 public Bitmap createBitmap() {
2049 mCanvas.setBitmap(null);
2050 mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
2051 return mBitmap;
2052 }
2053 }
2054
2055 /**
2056 * @hide
2057 */
2058 public static class HardwareCanvasProvider implements CanvasProvider {
John Reck519ad482018-02-12 17:08:48 -08002059 private Picture mPicture;
Sunny Goyald1b287e2018-01-04 09:37:22 -08002060
2061 @Override
2062 public Canvas getCanvas(View view, int width, int height) {
John Reck519ad482018-02-12 17:08:48 -08002063 mPicture = new Picture();
2064 return mPicture.beginRecording(width, height);
Sunny Goyald1b287e2018-01-04 09:37:22 -08002065 }
2066
2067 @Override
2068 public Bitmap createBitmap() {
John Reck519ad482018-02-12 17:08:48 -08002069 mPicture.endRecording();
2070 return Bitmap.createBitmap(mPicture);
Sunny Goyald1b287e2018-01-04 09:37:22 -08002071 }
2072 }
2073
2074 /**
2075 * @hide
2076 */
2077 public interface CanvasProvider {
2078
2079 /**
2080 * Returns a canvas which can be used to draw {@param view}
2081 */
2082 Canvas getCanvas(View view, int width, int height);
2083
2084 /**
2085 * Creates a bitmap from previously returned canvas
2086 * @return
2087 */
2088 Bitmap createBitmap();
2089 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07002090}