blob: a390db2e9644c8394d058f29aaecba02c595d650 [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
Romain Guyf9284692011-07-13 18:46:21 -070040import java.io.BufferedOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import java.io.BufferedWriter;
Romain Guyf9284692011-07-13 18:46:21 -070042import java.io.ByteArrayOutputStream;
43import java.io.DataOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import java.io.OutputStream;
Romain Guyf9284692011-07-13 18:46:21 -070046import java.io.OutputStreamWriter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import java.lang.annotation.ElementType;
48import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
Romain Guyf9284692011-07-13 18:46:21 -070050import java.lang.annotation.Target;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070051import java.lang.reflect.AccessibleObject;
Romain Guyf9284692011-07-13 18:46:21 -070052import java.lang.reflect.Field;
53import java.lang.reflect.InvocationTargetException;
54import java.lang.reflect.Method;
John Reck5cca8f22018-12-10 17:06:22 -080055import java.util.ArrayDeque;
Romain Guyf9284692011-07-13 18:46:21 -070056import java.util.ArrayList;
57import java.util.HashMap;
Kristian Monsen97e8f622013-04-26 12:51:19 -070058import java.util.concurrent.Callable;
59import java.util.concurrent.CancellationException;
Romain Guyf9284692011-07-13 18:46:21 -070060import java.util.concurrent.CountDownLatch;
Kristian Monsen97e8f622013-04-26 12:51:19 -070061import java.util.concurrent.ExecutionException;
John Reck5cca8f22018-12-10 17:06:22 -080062import java.util.concurrent.Executor;
Kristian Monsen97e8f622013-04-26 12:51:19 -070063import java.util.concurrent.FutureTask;
Romain Guyf9284692011-07-13 18:46:21 -070064import java.util.concurrent.TimeUnit;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070065import java.util.concurrent.TimeoutException;
Siva Velusamyf9455fa2013-01-17 18:01:52 -080066import java.util.concurrent.atomic.AtomicReference;
John Reck5cca8f22018-12-10 17:06:22 -080067import java.util.concurrent.locks.ReentrantLock;
68import java.util.function.Function;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
70/**
71 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
72 */
73public class ViewDebug {
74 /**
Romain Guy13b90732012-05-21 12:13:31 -070075 * @deprecated This flag is now unused
Romain Guy13922e02009-05-12 17:56:14 -070076 */
Romain Guy13b90732012-05-21 12:13:31 -070077 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 public static final boolean TRACE_HIERARCHY = false;
79
80 /**
Romain Guy13b90732012-05-21 12:13:31 -070081 * @deprecated This flag is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 */
Romain Guy13b90732012-05-21 12:13:31 -070083 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070085
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 /**
Christopher Tate2c095f32010-10-04 14:13:40 -070087 * Enables detailed logging of drag/drop operations.
88 * @hide
89 */
Christopher Tate994ef922011-01-12 20:06:07 -080090 public static final boolean DEBUG_DRAG = false;
Christopher Tate2c095f32010-10-04 14:13:40 -070091
92 /**
Chong Zhang8e89b312015-09-09 15:09:30 -070093 * Enables detailed logging of task positioning operations.
94 * @hide
95 */
96 public static final boolean DEBUG_POSITIONING = false;
97
98 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 * This annotation can be used to mark fields and methods to be dumped by
100 * the view server. Only non-void methods with no arguments can be annotated
101 * by this annotation.
102 */
103 @Target({ ElementType.FIELD, ElementType.METHOD })
104 @Retention(RetentionPolicy.RUNTIME)
105 public @interface ExportedProperty {
106 /**
107 * When resolveId is true, and if the annotated field/method return value
108 * is an int, the value is converted to an Android's resource name.
109 *
110 * @return true if the property's value must be transformed into an Android
111 * resource name, false otherwise
112 */
113 boolean resolveId() default false;
114
115 /**
116 * A mapping can be defined to map int values to specific strings. For
117 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
118 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
119 * these human readable values:
120 *
121 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800122 * {@literal @}ViewDebug.ExportedProperty(mapping = {
123 * {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"),
124 * {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
125 * {@literal @}ViewDebug.IntToString(from = 8, to = "GONE")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 * })
127 * public int getVisibility() { ...
128 * <pre>
129 *
130 * @return An array of int to String mappings
131 *
132 * @see android.view.ViewDebug.IntToString
133 */
134 IntToString[] mapping() default { };
135
136 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700137 * A mapping can be defined to map array indices to specific strings.
138 * A mapping can be used to see human readable values for the indices
139 * of an array:
140 *
141 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800142 * {@literal @}ViewDebug.ExportedProperty(indexMapping = {
143 * {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"),
144 * {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"),
145 * {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND")
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700146 * })
147 * private int[] mElements;
148 * <pre>
149 *
150 * @return An array of int to String mappings
151 *
152 * @see android.view.ViewDebug.IntToString
153 * @see #mapping()
154 */
155 IntToString[] indexMapping() default { };
156
157 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700158 * A flags mapping can be defined to map flags encoded in an integer to
159 * specific strings. A mapping can be used to see human readable values
160 * for the flags of an integer:
161 *
162 * <pre>
Scott Mainfc4373c2017-02-16 18:40:04 -0800163 * {@literal @}ViewDebug.ExportedProperty(flagMapping = {
164 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED,
165 * name = "ENABLED"),
166 * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED,
167 * name = "DISABLED"),
Romain Guy809a7f62009-05-14 15:44:42 -0700168 * })
169 * private int mFlags;
170 * <pre>
171 *
172 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700173 *
Romain Guy809a7f62009-05-14 15:44:42 -0700174 * @return An array of int to String mappings
175 */
176 FlagToString[] flagMapping() default { };
177
178 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 * When deep export is turned on, this property is not dumped. Instead, the
180 * properties contained in this property are dumped. Each child property
181 * is prefixed with the name of this property.
182 *
183 * @return true if the properties of this property should be dumped
184 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700185 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 */
187 boolean deepExport() default false;
188
189 /**
190 * The prefix to use on child properties when deep export is enabled
191 *
192 * @return a prefix as a String
193 *
194 * @see #deepExport()
195 */
196 String prefix() default "";
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700197
198 /**
199 * Specifies the category the property falls into, such as measurement,
200 * layout, drawing, etc.
201 *
202 * @return the category as String
203 */
204 String category() default "";
Jon Miranda4597e982014-07-29 07:25:49 -0700205
206 /**
207 * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string.
208 *
209 * @return true if the supported values should be formatted as a hex string.
210 */
211 boolean formatToHexString() default false;
Jon Miranda836c0a82014-08-11 12:32:26 -0700212
213 /**
214 * Indicates whether or not the key to value mappings are held in adjacent indices.
215 *
216 * Note: Applies only to fields and methods that return String[].
217 *
218 * @return true if the key to value mappings are held in adjacent indices.
219 */
220 boolean hasAdjacentMapping() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 }
222
223 /**
224 * Defines a mapping from an int value to a String. Such a mapping can be used
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900225 * in an @ExportedProperty to provide more meaningful values to the end user.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 *
227 * @see android.view.ViewDebug.ExportedProperty
228 */
229 @Target({ ElementType.TYPE })
230 @Retention(RetentionPolicy.RUNTIME)
231 public @interface IntToString {
232 /**
233 * The original int value to map to a String.
234 *
235 * @return An arbitrary int value.
236 */
237 int from();
238
239 /**
240 * The String to use in place of the original int value.
241 *
242 * @return An arbitrary non-null String.
243 */
244 String to();
245 }
Romain Guy809a7f62009-05-14 15:44:42 -0700246
247 /**
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900248 * Defines a mapping from a flag to a String. Such a mapping can be used
249 * in an @ExportedProperty to provide more meaningful values to the end user.
Romain Guy809a7f62009-05-14 15:44:42 -0700250 *
251 * @see android.view.ViewDebug.ExportedProperty
252 */
253 @Target({ ElementType.TYPE })
254 @Retention(RetentionPolicy.RUNTIME)
255 public @interface FlagToString {
256 /**
257 * The mask to apply to the original value.
258 *
259 * @return An arbitrary int value.
260 */
261 int mask();
262
263 /**
264 * The value to compare to the result of:
265 * <code>original value &amp; {@link #mask()}</code>.
266 *
267 * @return An arbitrary value.
268 */
269 int equals();
270
271 /**
272 * The String to use in place of the original int value.
273 *
274 * @return An arbitrary non-null String.
275 */
276 String name();
277
278 /**
279 * Indicates whether to output the flag when the test is true,
280 * or false. Defaults to true.
281 */
282 boolean outputIf() default true;
283 }
284
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 /**
286 * This annotation can be used to mark fields and methods to be dumped when
287 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700288 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 */
290 @Target({ ElementType.FIELD, ElementType.METHOD })
291 @Retention(RetentionPolicy.RUNTIME)
292 public @interface CapturedViewProperty {
293 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700294 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700296 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700298 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700300 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700302
John Reck926cf562012-06-14 10:00:31 -0700303 /**
304 * Allows a View to inject custom children into HierarchyViewer. For example,
305 * WebView uses this to add its internal layer tree as a child to itself
306 * @hide
307 */
308 public interface HierarchyHandler {
309 /**
310 * Dumps custom children to hierarchy viewer.
John Reckf2361152012-06-14 15:23:22 -0700311 * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
John Reck926cf562012-06-14 10:00:31 -0700312 * for the format
313 *
314 * An empty implementation should simply do nothing
315 *
316 * @param out The output writer
317 * @param level The indentation level
318 */
319 public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
320
321 /**
322 * Returns a View to enable grabbing screenshots from custom children
323 * returned in dumpViewHierarchyWithProperties.
324 *
325 * @param className The className of the view to find
326 * @param hashCode The hashCode of the view to find
327 * @return the View to capture from, or null if not found
328 */
329 public View findHierarchyView(String className, int hashCode);
330 }
331
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
333 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
334
335 // Maximum delay in ms after which we stop trying to capture a View's drawing
336 private static final int CAPTURE_TIMEOUT = 4000;
337
338 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
339 private static final String REMOTE_COMMAND_DUMP = "DUMP";
Jon Miranda042ad632014-09-03 17:57:35 -0700340 private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
342 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700343 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800344 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
Chet Haaseed30fd82011-04-22 16:18:45 -0700345 private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346
347 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
348 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700349 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
350
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 /**
Romain Guy13b90732012-05-21 12:13:31 -0700352 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 */
Romain Guy13b90732012-05-21 12:13:31 -0700354 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 public enum HierarchyTraceType {
356 INVALIDATE,
357 INVALIDATE_CHILD,
358 INVALIDATE_CHILD_IN_PARENT,
359 REQUEST_LAYOUT,
360 ON_LAYOUT,
361 ON_MEASURE,
362 DRAW,
363 BUILD_CACHE
364 }
365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 /**
Romain Guy13b90732012-05-21 12:13:31 -0700367 * @deprecated This enum is now unused
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 */
Romain Guy13b90732012-05-21 12:13:31 -0700369 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 public enum RecyclerTraceType {
371 NEW_VIEW,
372 BIND_VIEW,
373 RECYCLE_FROM_ACTIVE_HEAP,
374 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 MOVE_TO_SCRAP_HEAP,
376 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
377 }
378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 /**
380 * Returns the number of instanciated Views.
381 *
382 * @return The number of Views instanciated in the current process.
383 *
384 * @hide
385 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100386 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 public static long getViewInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700388 return Debug.countInstancesOfClass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 }
390
391 /**
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700392 * Returns the number of instanciated ViewAncestors.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 *
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700394 * @return The number of ViewAncestors instanciated in the current process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395 *
396 * @hide
397 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100398 @UnsupportedAppUsage
Romain Guy65b345f2011-07-27 18:51:50 -0700399 public static long getViewRootImplCount() {
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700400 return Debug.countInstancesOfClass(ViewRootImpl.class);
Romain Guya1f3e4a2009-06-04 15:10:46 -0700401 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402
403 /**
Romain Guy13b90732012-05-21 12:13:31 -0700404 * @deprecated This method is now unused and invoking it is a no-op
Romain Guyf9284692011-07-13 18:46:21 -0700405 */
Romain Guy13b90732012-05-21 12:13:31 -0700406 @Deprecated
407 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 public static void trace(View view, RecyclerTraceType type, int... parameters) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
410
411 /**
Romain Guy13b90732012-05-21 12:13:31 -0700412 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 */
Romain Guy13b90732012-05-21 12:13:31 -0700414 @Deprecated
415 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 public static void startRecyclerTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 }
418
419 /**
Romain Guy13b90732012-05-21 12:13:31 -0700420 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 */
Romain Guy13b90732012-05-21 12:13:31 -0700422 @Deprecated
423 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 public static void stopRecyclerTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 }
426
427 /**
Romain Guy13b90732012-05-21 12:13:31 -0700428 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 */
Romain Guy13b90732012-05-21 12:13:31 -0700430 @Deprecated
431 @SuppressWarnings({ "UnusedParameters", "deprecation" })
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 public static void trace(View view, HierarchyTraceType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800433 }
434
435 /**
Romain Guy13b90732012-05-21 12:13:31 -0700436 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 */
Romain Guy13b90732012-05-21 12:13:31 -0700438 @Deprecated
439 @SuppressWarnings("UnusedParameters")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 public static void startHierarchyTracing(String prefix, View view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 }
442
443 /**
Romain Guy13b90732012-05-21 12:13:31 -0700444 * @deprecated This method is now unused and invoking it is a no-op
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 */
Romain Guy13b90732012-05-21 12:13:31 -0700446 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 public static void stopHierarchyTracing() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700449
Mathew Inwooda570dee2018-08-17 14:56:00 +0100450 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 static void dispatchCommand(View view, String command, String parameters,
452 OutputStream clientStream) throws IOException {
453
454 // Paranoid but safe...
455 view = view.getRootView();
456
457 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
Siva Velusamy945bfb62013-01-06 16:03:12 -0800458 dump(view, false, true, clientStream);
Jon Miranda042ad632014-09-03 17:57:35 -0700459 } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) {
460 dumpTheme(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800461 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
462 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 } else {
464 final String[] params = parameters.split(" ");
465 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
466 capture(view, clientStream, params[0]);
Chet Haaseed30fd82011-04-22 16:18:45 -0700467 } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
468 outputDisplayList(view, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
470 invalidate(view, params[0]);
471 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
472 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700473 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
474 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 }
476 }
477 }
478
Siva Velusamy945bfb62013-01-06 16:03:12 -0800479 /** @hide */
480 public static View findView(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700481 // Look by type/hashcode
482 if (parameter.indexOf('@') != -1) {
483 final String[] ids = parameter.split("@");
484 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800485 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700487 View view = root.getRootView();
488 if (view instanceof ViewGroup) {
489 return findView((ViewGroup) view, className, hashCode);
490 }
491 } else {
492 // Look by id
493 final int id = root.getResources().getIdentifier(parameter, null, null);
494 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 }
496
497 return null;
498 }
499
500 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700501 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 if (view != null) {
503 view.postInvalidate();
504 }
505 }
506
507 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700508 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800509 if (view != null) {
510 root.post(new Runnable() {
511 public void run() {
512 view.requestLayout();
513 }
514 });
515 }
516 }
517
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700518 private static void profile(View root, OutputStream clientStream, String parameter)
519 throws IOException {
520
521 final View view = findView(root, parameter);
522 BufferedWriter out = null;
523 try {
524 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
525
526 if (view != null) {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700527 profileViewAndChildren(view, out);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700528 } else {
529 out.write("-1 -1 -1");
530 out.newLine();
531 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700532 out.write("DONE.");
533 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700534 } catch (Exception e) {
535 android.util.Log.w("View", "Problem profiling the view:", e);
536 } finally {
537 if (out != null) {
538 out.close();
539 }
540 }
541 }
542
Siva Velusamy945bfb62013-01-06 16:03:12 -0800543 /** @hide */
544 public static void profileViewAndChildren(final View view, BufferedWriter out)
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700545 throws IOException {
Sunny Goyalc1043202017-10-16 14:00:44 -0700546 RenderNode node = RenderNode.create("ViewDebug", null);
547 profileViewAndChildren(view, node, out, true);
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700548 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700549
Sunny Goyalc1043202017-10-16 14:00:44 -0700550 private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
551 boolean root) throws IOException {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700552 long durationMeasure =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700553 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700554 ? profileViewMeasure(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700555 long durationLayout =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700556 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700557 ? profileViewLayout(view) : 0;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700558 long durationDraw =
Dianne Hackborn4702a852012-08-17 15:18:29 -0700559 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
Sunny Goyalc1043202017-10-16 14:00:44 -0700560 ? profileViewDraw(view, node) : 0;
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700561
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700562 out.write(String.valueOf(durationMeasure));
563 out.write(' ');
564 out.write(String.valueOf(durationLayout));
565 out.write(' ');
566 out.write(String.valueOf(durationDraw));
567 out.newLine();
568 if (view instanceof ViewGroup) {
569 ViewGroup group = (ViewGroup) view;
570 final int count = group.getChildCount();
571 for (int i = 0; i < count; i++) {
Sunny Goyalc1043202017-10-16 14:00:44 -0700572 profileViewAndChildren(group.getChildAt(i), node, out, false);
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700573 }
574 }
575 }
576
Sunny Goyalc1043202017-10-16 14:00:44 -0700577 private static long profileViewMeasure(final View view) {
578 return profileViewOperation(view, new ViewOperation() {
579 @Override
580 public void pre() {
581 forceLayout(view);
582 }
583
584 private void forceLayout(View view) {
585 view.forceLayout();
586 if (view instanceof ViewGroup) {
587 ViewGroup group = (ViewGroup) view;
588 final int count = group.getChildCount();
589 for (int i = 0; i < count; i++) {
590 forceLayout(group.getChildAt(i));
591 }
592 }
593 }
594
595 @Override
596 public void run() {
597 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
598 }
599 });
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700600 }
601
Sunny Goyalc1043202017-10-16 14:00:44 -0700602 private static long profileViewLayout(View view) {
603 return profileViewOperation(view,
604 () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
605 }
606
607 private static long profileViewDraw(View view, RenderNode node) {
608 DisplayMetrics dm = view.getResources().getDisplayMetrics();
609 if (dm == null) {
610 return 0;
611 }
612
613 if (view.isHardwareAccelerated()) {
John Recke57475e2019-02-20 17:39:52 -0800614 RecordingCanvas canvas = node.beginRecording(dm.widthPixels, dm.heightPixels);
Sunny Goyalc1043202017-10-16 14:00:44 -0700615 try {
616 return profileViewOperation(view, () -> view.draw(canvas));
617 } finally {
John Recke57475e2019-02-20 17:39:52 -0800618 node.endRecording();
Sunny Goyalc1043202017-10-16 14:00:44 -0700619 }
620 } else {
621 Bitmap bitmap = Bitmap.createBitmap(
622 dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
623 Canvas canvas = new Canvas(bitmap);
624 try {
625 return profileViewOperation(view, () -> view.draw(canvas));
626 } finally {
627 canvas.setBitmap(null);
628 bitmap.recycle();
629 }
630 }
631 }
632
633 interface ViewOperation {
634 default void pre() {}
635
636 void run();
637 }
638
639 private static long profileViewOperation(View view, final ViewOperation operation) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700640 final CountDownLatch latch = new CountDownLatch(1);
641 final long[] duration = new long[1];
642
Sunny Goyalc1043202017-10-16 14:00:44 -0700643 view.post(() -> {
644 try {
645 operation.pre();
646 long start = Debug.threadCpuTimeNanos();
647 //noinspection unchecked
648 operation.run();
649 duration[0] = Debug.threadCpuTimeNanos() - start;
650 } finally {
651 latch.countDown();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700652 }
653 });
654
655 try {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700656 if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
657 Log.w("View", "Could not complete the profiling of the view " + view);
658 return -1;
659 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700660 } catch (InterruptedException e) {
661 Log.w("View", "Could not complete the profiling of the view " + view);
662 Thread.currentThread().interrupt();
663 return -1;
664 }
665
666 return duration[0];
667 }
668
Siva Velusamy945bfb62013-01-06 16:03:12 -0800669 /** @hide */
670 public static void captureLayers(View root, final DataOutputStream clientStream)
Romain Guy223ff5c2010-03-02 17:07:47 -0800671 throws IOException {
672
673 try {
674 Rect outRect = new Rect();
675 try {
676 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
677 } catch (RemoteException e) {
678 // Ignore
679 }
Jon Miranda4597e982014-07-29 07:25:49 -0700680
Romain Guy223ff5c2010-03-02 17:07:47 -0800681 clientStream.writeInt(outRect.width());
682 clientStream.writeInt(outRect.height());
Jon Miranda4597e982014-07-29 07:25:49 -0700683
Romain Guy65554f22010-03-22 18:58:21 -0700684 captureViewLayer(root, clientStream, true);
Jon Miranda4597e982014-07-29 07:25:49 -0700685
Romain Guy223ff5c2010-03-02 17:07:47 -0800686 clientStream.write(2);
687 } finally {
688 clientStream.close();
689 }
690 }
691
Romain Guy65554f22010-03-22 18:58:21 -0700692 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -0800693 throws IOException {
694
Romain Guy65554f22010-03-22 18:58:21 -0700695 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
696
Dianne Hackborn4702a852012-08-17 15:18:29 -0700697 if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
Romain Guy223ff5c2010-03-02 17:07:47 -0800698 final int id = view.getId();
699 String name = view.getClass().getSimpleName();
700 if (id != View.NO_ID) {
701 name = resolveId(view.getContext(), id).toString();
702 }
Jon Miranda4597e982014-07-29 07:25:49 -0700703
Romain Guy223ff5c2010-03-02 17:07:47 -0800704 clientStream.write(1);
705 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -0700706 clientStream.writeByte(localVisible ? 1 : 0);
Jon Miranda4597e982014-07-29 07:25:49 -0700707
Romain Guy223ff5c2010-03-02 17:07:47 -0800708 int[] position = new int[2];
709 // XXX: Should happen on the UI thread
710 view.getLocationInWindow(position);
Jon Miranda4597e982014-07-29 07:25:49 -0700711
Romain Guy223ff5c2010-03-02 17:07:47 -0800712 clientStream.writeInt(position[0]);
713 clientStream.writeInt(position[1]);
714 clientStream.flush();
Jon Miranda4597e982014-07-29 07:25:49 -0700715
Romain Guy223ff5c2010-03-02 17:07:47 -0800716 Bitmap b = performViewCapture(view, true);
717 if (b != null) {
718 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
719 b.getHeight() * 2);
720 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
721 clientStream.writeInt(arrayOut.size());
722 arrayOut.writeTo(clientStream);
723 }
724 clientStream.flush();
725 }
726
727 if (view instanceof ViewGroup) {
728 ViewGroup group = (ViewGroup) view;
729 int count = group.getChildCount();
730
731 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -0700732 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -0800733 }
734 }
Chet Haase68bf5bd2013-09-05 16:27:28 -0700735
736 if (view.mOverlay != null) {
737 ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
738 captureViewLayer(overlayContainer, clientStream, localVisible);
739 }
Romain Guy223ff5c2010-03-02 17:07:47 -0800740 }
741
Chet Haaseed30fd82011-04-22 16:18:45 -0700742 private static void outputDisplayList(View root, String parameter) throws IOException {
743 final View view = findView(root, parameter);
Dianne Hackborn6dd005b2011-07-18 13:22:50 -0700744 view.getViewRootImpl().outputDisplayList(view);
Chet Haaseed30fd82011-04-22 16:18:45 -0700745 }
746
Siva Velusamy945bfb62013-01-06 16:03:12 -0800747 /** @hide */
748 public static void outputDisplayList(View root, View target) {
749 root.getViewRootImpl().outputDisplayList(target);
750 }
751
John Reck5cca8f22018-12-10 17:06:22 -0800752 private static class PictureCallbackHandler implements AutoCloseable,
753 HardwareRenderer.PictureCapturedCallback, Runnable {
754 private final HardwareRenderer mRenderer;
755 private final Function<Picture, Boolean> mCallback;
756 private final Executor mExecutor;
757 private final ReentrantLock mLock = new ReentrantLock(false);
758 private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
759 private boolean mStopListening;
760 private Thread mRenderThread;
761
762 private PictureCallbackHandler(HardwareRenderer renderer,
763 Function<Picture, Boolean> callback, Executor executor) {
764 mRenderer = renderer;
765 mCallback = callback;
766 mExecutor = executor;
767 mRenderer.setPictureCaptureCallback(this);
768 }
769
770 @Override
771 public void close() {
772 mLock.lock();
773 mStopListening = true;
774 mLock.unlock();
775 mRenderer.setPictureCaptureCallback(null);
776 }
777
778 @Override
779 public void onPictureCaptured(Picture picture) {
780 mLock.lock();
781 if (mStopListening) {
782 mLock.unlock();
783 mRenderer.setPictureCaptureCallback(null);
784 return;
785 }
786 if (mRenderThread == null) {
787 mRenderThread = Thread.currentThread();
788 }
789 Picture toDestroy = null;
790 if (mQueue.size() == 3) {
791 toDestroy = mQueue.removeLast();
792 }
793 mQueue.add(picture);
794 mLock.unlock();
795 if (toDestroy == null) {
796 mExecutor.execute(this);
797 } else {
798 toDestroy.close();
799 }
800 }
801
802 @Override
803 public void run() {
804 mLock.lock();
805 final Picture picture = mQueue.poll();
806 final boolean isStopped = mStopListening;
807 mLock.unlock();
808 if (Thread.currentThread() == mRenderThread) {
809 close();
810 throw new IllegalStateException(
811 "ViewDebug#startRenderingCommandsCapture must be given an executor that "
812 + "invokes asynchronously");
813 }
814 if (isStopped) {
815 picture.close();
816 return;
817 }
818 final boolean keepReceiving = mCallback.apply(picture);
819 if (!keepReceiving) {
820 close();
821 }
822 }
823 }
824
825 /**
826 * Begins capturing the entire rendering commands for the view tree referenced by the given
827 * view. The view passed may be any View in the tree as long as it is attached. That is,
828 * {@link View#isAttachedToWindow()} must be true.
829 *
830 * Every time a frame is rendered a Picture will be passed to the given callback via the given
831 * executor. As long as the callback returns 'true' it will continue to receive new frames.
832 * The system will only invoke the callback at a rate that the callback is able to keep up with.
833 * That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
834 * then the callback will only receive 33% of the frames produced.
835 *
836 * This method must be called on the same thread as the View tree.
837 *
838 * @param tree The View tree to capture the rendering commands.
839 * @param callback The callback to invoke on every frame produced. Should return true to
840 * continue receiving new frames, false to stop capturing.
841 * @param executor The executor to invoke the callback on. Recommend using a background thread
842 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
843 * exception will be thrown.
844 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
845 * that the callback may continue to receive another frame or two depending on thread timings.
846 * Returns null if the capture stream cannot be started, such as if there's no
847 * HardwareRenderer for the given view tree.
848 * @hide
John Reckd9425652019-02-15 12:32:31 -0800849 * @deprecated use {@link #startRenderingCommandsCapture(View, Executor, Callable)} instead.
John Reck5cca8f22018-12-10 17:06:22 -0800850 */
851 @TestApi
852 @Nullable
John Reckd9425652019-02-15 12:32:31 -0800853 @Deprecated
John Reck5cca8f22018-12-10 17:06:22 -0800854 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
855 Function<Picture, Boolean> callback) {
856 final View.AttachInfo attachInfo = tree.mAttachInfo;
857 if (attachInfo == null) {
858 throw new IllegalArgumentException("Given view isn't attached");
859 }
860 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
861 throw new IllegalStateException("Called on the wrong thread."
862 + " Must be called on the thread that owns the given View");
863 }
864 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
865 if (renderer != null) {
866 return new PictureCallbackHandler(renderer, callback, executor);
867 }
868 return null;
869 }
870
John Reckd9425652019-02-15 12:32:31 -0800871 /**
872 * Begins capturing the entire rendering commands for the view tree referenced by the given
873 * view. The view passed may be any View in the tree as long as it is attached. That is,
874 * {@link View#isAttachedToWindow()} must be true.
875 *
876 * Every time a frame is rendered the callback will be invoked on the given executor to
877 * provide an OutputStream to serialize to. As long as the callback returns a valid
878 * OutputStream the capturing will continue. The system will only invoke the callback at a rate
879 * that the callback & OutputStream is able to keep up with. That is, if it takes 48ms for the
880 * callback & serialization to complete and there is a 60fps animation running
881 * then the callback will only receive 33% of the frames produced.
882 *
883 * This method must be called on the same thread as the View tree.
884 *
885 * @param tree The View tree to capture the rendering commands.
886 * @param callback The callback to invoke on every frame produced. Should return an
887 * OutputStream to write the data to. Return null to cancel capture. The
888 * same stream may be returned each time as the serialized data contains
889 * start & end markers. The callback will not be invoked while a previous
890 * serialization is being performed, so if a single continuous stream is being
891 * used it is valid for the callback to write its own metadata to that stream
892 * in response to callback invocation.
893 * @param executor The executor to invoke the callback on. Recommend using a background thread
894 * to avoid stalling the UI thread. Must be an asynchronous invoke or an
895 * exception will be thrown.
896 * @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
897 * that the callback may continue to receive another frame or two depending on thread timings.
898 * Returns null if the capture stream cannot be started, such as if there's no
899 * HardwareRenderer for the given view tree.
900 * @hide
901 */
902 @TestApi
903 @Nullable
904 public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
905 Callable<OutputStream> callback) {
906 final View.AttachInfo attachInfo = tree.mAttachInfo;
907 if (attachInfo == null) {
908 throw new IllegalArgumentException("Given view isn't attached");
909 }
910 if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
911 throw new IllegalStateException("Called on the wrong thread."
912 + " Must be called on the thread that owns the given View");
913 }
914 final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
915 if (renderer != null) {
916 return new PictureCallbackHandler(renderer, (picture -> {
917 try {
918 OutputStream stream = callback.call();
919 if (stream != null) {
920 picture.writeToStream(stream);
921 return true;
922 }
923 } catch (Exception ex) {
924 // fall through
925 }
926 return false;
927 }), executor);
928 }
929 return null;
930 }
931
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800932 private static void capture(View root, final OutputStream clientStream, String parameter)
933 throws IOException {
934
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700935 final View captureView = findView(root, parameter);
Siva Velusamy945bfb62013-01-06 16:03:12 -0800936 capture(root, clientStream, captureView);
937 }
938
939 /** @hide */
940 public static void capture(View root, final OutputStream clientStream, View captureView)
941 throws IOException {
Romain Guy223ff5c2010-03-02 17:07:47 -0800942 Bitmap b = performViewCapture(captureView, false);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -0700943
944 if (b == null) {
Romain Guy223ff5c2010-03-02 17:07:47 -0800945 Log.w("View", "Failed to create capture bitmap!");
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -0700946 // Send an empty one so that it doesn't get stuck waiting for
947 // something.
Dianne Hackborndde331c2012-08-03 14:01:57 -0700948 b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
949 1, 1, Bitmap.Config.ARGB_8888);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -0700950 }
951
952 BufferedOutputStream out = null;
953 try {
954 out = new BufferedOutputStream(clientStream, 32 * 1024);
955 b.compress(Bitmap.CompressFormat.PNG, 100, out);
956 out.flush();
957 } finally {
958 if (out != null) {
959 out.close();
960 }
961 b.recycle();
Romain Guy223ff5c2010-03-02 17:07:47 -0800962 }
963 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800964
Chet Haase68bf5bd2013-09-05 16:27:28 -0700965 private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800966 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700967 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800968 final Bitmap[] cache = new Bitmap[1];
969
Sunny Goyald1b287e2018-01-04 09:37:22 -0800970 captureView.post(() -> {
971 try {
972 CanvasProvider provider = captureView.isHardwareAccelerated()
973 ? new HardwareCanvasProvider() : new SoftwareCanvasProvider();
974 cache[0] = captureView.createSnapshot(provider, skipChildren);
975 } catch (OutOfMemoryError e) {
976 Log.w("View", "Out of memory for bitmap");
977 } finally {
978 latch.countDown();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800979 }
980 });
981
982 try {
983 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -0800984 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800985 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700986 Log.w("View", "Could not complete the capture of the view " + captureView);
987 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 }
989 }
Jon Miranda4597e982014-07-29 07:25:49 -0700990
Romain Guy223ff5c2010-03-02 17:07:47 -0800991 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800992 }
993
Siva Velusamy945bfb62013-01-06 16:03:12 -0800994 /**
995 * Dumps the view hierarchy starting from the given view.
Siva Velusamy0d857b92015-04-22 10:23:56 -0700996 * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below.
Siva Velusamy945bfb62013-01-06 16:03:12 -0800997 * @hide
998 */
Aurimas Liutikas514c5ef2016-05-24 15:22:55 -0700999 @Deprecated
Mathew Inwooda570dee2018-08-17 14:56:00 +01001000 @UnsupportedAppUsage
Siva Velusamy945bfb62013-01-06 16:03:12 -08001001 public static void dump(View root, boolean skipChildren, boolean includeProperties,
1002 OutputStream clientStream) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001003 BufferedWriter out = null;
1004 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001005 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 View view = root.getRootView();
1007 if (view instanceof ViewGroup) {
1008 ViewGroup group = (ViewGroup) view;
Siva Velusamy945bfb62013-01-06 16:03:12 -08001009 dumpViewHierarchy(group.getContext(), group, out, 0,
1010 skipChildren, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 }
1012 out.write("DONE.");
1013 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001014 } catch (Exception e) {
1015 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001016 } finally {
1017 if (out != null) {
1018 out.close();
1019 }
1020 }
1021 }
1022
Jon Miranda042ad632014-09-03 17:57:35 -07001023 /**
Siva Velusamy0d857b92015-04-22 10:23:56 -07001024 * Dumps the view hierarchy starting from the given view.
1025 * Rather than using reflection, it uses View's encode method to obtain all the properties.
1026 * @hide
1027 */
1028 public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out)
1029 throws InterruptedException {
1030 final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out);
1031 final CountDownLatch latch = new CountDownLatch(1);
1032
1033 view.post(new Runnable() {
1034 @Override
1035 public void run() {
Manu Cornet4d052b32016-09-15 13:03:28 -07001036 encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft);
1037 encoder.addProperty("window:top", view.mAttachInfo.mWindowTop);
Siva Velusamy0d857b92015-04-22 10:23:56 -07001038 view.encode(encoder);
1039 latch.countDown();
1040 }
1041 });
1042
1043 latch.await(2, TimeUnit.SECONDS);
1044 encoder.endStream();
1045 }
1046
1047 /**
Jon Miranda042ad632014-09-03 17:57:35 -07001048 * Dumps the theme attributes from the given View.
1049 * @hide
1050 */
1051 public static void dumpTheme(View view, OutputStream clientStream) throws IOException {
1052 BufferedWriter out = null;
1053 try {
1054 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
1055 String[] attributes = getStyleAttributesDump(view.getContext().getResources(),
1056 view.getContext().getTheme());
1057 if (attributes != null) {
1058 for (int i = 0; i < attributes.length; i += 2) {
1059 if (attributes[i] != null) {
1060 out.write(attributes[i] + "\n");
1061 out.write(attributes[i + 1] + "\n");
1062 }
1063 }
1064 }
1065 out.write("DONE.");
1066 out.newLine();
1067 } catch (Exception e) {
1068 android.util.Log.w("View", "Problem dumping View Theme:", e);
1069 } finally {
1070 if (out != null) {
1071 out.close();
1072 }
1073 }
1074 }
1075
1076 /**
1077 * Gets the style attributes from the {@link Resources.Theme}. For debugging only.
1078 *
1079 * @param resources Resources to resolve attributes from.
1080 * @param theme Theme to dump.
1081 * @return a String array containing pairs of adjacent Theme attribute data: name followed by
1082 * its value.
1083 *
1084 * @hide
1085 */
1086 private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) {
1087 TypedValue outValue = new TypedValue();
1088 String nullString = "null";
1089 int i = 0;
1090 int[] attributes = theme.getAllAttributes();
1091 String[] data = new String[attributes.length * 2];
1092 for (int attributeId : attributes) {
1093 try {
1094 data[i] = resources.getResourceName(attributeId);
1095 data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ?
1096 outValue.coerceToString().toString() : nullString;
1097 i += 2;
Jon Miranda7c744bd2014-09-09 17:06:31 -07001098
1099 // attempt to replace reference data with its name
1100 if (outValue.type == TypedValue.TYPE_REFERENCE) {
1101 data[i - 1] = resources.getResourceName(outValue.resourceId);
1102 }
Jon Miranda042ad632014-09-03 17:57:35 -07001103 } catch (Resources.NotFoundException e) {
1104 // ignore resources we can't resolve
1105 }
1106 }
1107 return data;
1108 }
1109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 private static View findView(ViewGroup group, String className, int hashCode) {
1111 if (isRequestedView(group, className, hashCode)) {
1112 return group;
1113 }
1114
1115 final int count = group.getChildCount();
1116 for (int i = 0; i < count; i++) {
1117 final View view = group.getChildAt(i);
1118 if (view instanceof ViewGroup) {
1119 final View found = findView((ViewGroup) view, className, hashCode);
1120 if (found != null) {
1121 return found;
1122 }
1123 } else if (isRequestedView(view, className, hashCode)) {
1124 return view;
1125 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001126 if (view.mOverlay != null) {
1127 final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
1128 className, hashCode);
1129 if (found != null) {
1130 return found;
1131 }
1132 }
John Reck926cf562012-06-14 10:00:31 -07001133 if (view instanceof HierarchyHandler) {
1134 final View found = ((HierarchyHandler)view)
1135 .findHierarchyView(className, hashCode);
1136 if (found != null) {
1137 return found;
1138 }
1139 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 return null;
1142 }
1143
1144 private static boolean isRequestedView(View view, String className, int hashCode) {
Chet Haase68bf5bd2013-09-05 16:27:28 -07001145 if (view.hashCode() == hashCode) {
1146 String viewClassName = view.getClass().getName();
1147 if (className.equals("ViewOverlay")) {
1148 return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
1149 } else {
1150 return className.equals(viewClassName);
1151 }
1152 }
1153 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
1155
Siva Velusamy945bfb62013-01-06 16:03:12 -08001156 private static void dumpViewHierarchy(Context context, ViewGroup group,
1157 BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
1158 if (!dumpView(context, group, out, level, includeProperties)) {
1159 return;
1160 }
1161
1162 if (skipChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 return;
1164 }
1165
1166 final int count = group.getChildCount();
1167 for (int i = 0; i < count; i++) {
1168 final View view = group.getChildAt(i);
1169 if (view instanceof ViewGroup) {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001170 dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
1171 includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001172 } else {
Siva Velusamy945bfb62013-01-06 16:03:12 -08001173 dumpView(context, view, out, level + 1, includeProperties);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001175 if (view.mOverlay != null) {
1176 ViewOverlay overlay = view.getOverlay();
1177 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
1178 dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
1179 includeProperties);
1180 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001181 }
John Reck926cf562012-06-14 10:00:31 -07001182 if (group instanceof HierarchyHandler) {
1183 ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
1184 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001185 }
1186
Siva Velusamy945bfb62013-01-06 16:03:12 -08001187 private static boolean dumpView(Context context, View view,
1188 BufferedWriter out, int level, boolean includeProperties) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001190 try {
1191 for (int i = 0; i < level; i++) {
1192 out.write(' ');
1193 }
Chet Haase68bf5bd2013-09-05 16:27:28 -07001194 String className = view.getClass().getName();
1195 if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
1196 className = "ViewOverlay";
1197 }
1198 out.write(className);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 out.write('@');
1200 out.write(Integer.toHexString(view.hashCode()));
1201 out.write(' ');
Siva Velusamy945bfb62013-01-06 16:03:12 -08001202 if (includeProperties) {
1203 dumpViewProperties(context, view, out);
1204 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001205 out.newLine();
1206 } catch (IOException e) {
1207 Log.w("View", "Error while dumping hierarchy tree");
1208 return false;
1209 }
1210 return true;
1211 }
1212
1213 private static Field[] getExportedPropertyFields(Class<?> klass) {
1214 if (sFieldsForClasses == null) {
1215 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
1216 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001217 if (sAnnotations == null) {
1218 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1219 }
1220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001221 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
1222
1223 Field[] fields = map.get(klass);
1224 if (fields != null) {
1225 return fields;
1226 }
1227
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001228 try {
1229 final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
1230 final ArrayList<Field> foundFields = new ArrayList<Field>();
1231 for (final Field field : declaredFields) {
1232 // Fields which can't be resolved have a null type.
1233 if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
1234 field.setAccessible(true);
1235 foundFields.add(field);
1236 sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
1237 }
Alan Viverette99562fb2014-10-14 14:48:52 -07001238 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07001239 fields = foundFields.toArray(new Field[foundFields.size()]);
1240 map.put(klass, fields);
1241 } catch (NoClassDefFoundError e) {
1242 throw new AssertionError(e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001243 }
1244
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 return fields;
1246 }
1247
1248 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1249 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001250 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001252 if (sAnnotations == null) {
1253 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1254 }
1255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001256 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
1257
1258 Method[] methods = map.get(klass);
1259 if (methods != null) {
1260 return methods;
1261 }
1262
Mathieu Chartiera8a65162015-04-17 12:49:57 -07001263 methods = klass.getDeclaredMethodsUnchecked(false);
Romain Guya1f3e4a2009-06-04 15:10:46 -07001264
Alan Viverette99562fb2014-10-14 14:48:52 -07001265 final ArrayList<Method> foundMethods = new ArrayList<Method>();
Mathieu Chartiera8a65162015-04-17 12:49:57 -07001266 for (final Method method : methods) {
Alan Viverette99562fb2014-10-14 14:48:52 -07001267 // Ensure the method return and parameter types can be resolved.
1268 try {
1269 method.getReturnType();
1270 method.getParameterTypes();
1271 } catch (NoClassDefFoundError e) {
1272 continue;
1273 }
1274
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001276 method.isAnnotationPresent(ExportedProperty.class) &&
1277 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001278 method.setAccessible(true);
1279 foundMethods.add(method);
Romain Guy88b4f152011-05-19 16:15:46 -07001280 sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001281 }
1282 }
1283
1284 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1285 map.put(klass, methods);
1286
1287 return methods;
1288 }
1289
The Android Open Source Project10592532009-03-18 17:39:46 -07001290 private static void dumpViewProperties(Context context, Object view,
1291 BufferedWriter out) throws IOException {
1292
1293 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294 }
1295
The Android Open Source Project10592532009-03-18 17:39:46 -07001296 private static void dumpViewProperties(Context context, Object view,
1297 BufferedWriter out, String prefix) throws IOException {
1298
Romain Guydfab3632012-10-03 14:53:25 -07001299 if (view == null) {
1300 out.write(prefix + "=4,null ");
1301 return;
1302 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303
Romain Guydfab3632012-10-03 14:53:25 -07001304 Class<?> klass = view.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001306 exportFields(context, view, out, klass, prefix);
1307 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 klass = klass.getSuperclass();
1309 } while (klass != Object.class);
1310 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001311
Kristian Monsen97e8f622013-04-26 12:51:19 -07001312 private static Object callMethodOnAppropriateTheadBlocking(final Method method,
1313 final Object object) throws IllegalAccessException, InvocationTargetException,
1314 TimeoutException {
1315 if (!(object instanceof View)) {
1316 return method.invoke(object, (Object[]) null);
1317 }
1318
1319 final View view = (View) object;
1320 Callable<Object> callable = new Callable<Object>() {
Jon Miranda4597e982014-07-29 07:25:49 -07001321 @Override
1322 public Object call() throws IllegalAccessException, InvocationTargetException {
1323 return method.invoke(view, (Object[]) null);
1324 }
Kristian Monsen97e8f622013-04-26 12:51:19 -07001325 };
1326 FutureTask<Object> future = new FutureTask<Object>(callable);
1327 // Try to use the handler provided by the view
1328 Handler handler = view.getHandler();
1329 // Fall back on using the main thread
1330 if (handler == null) {
1331 handler = new Handler(android.os.Looper.getMainLooper());
1332 }
1333 handler.post(future);
1334 while (true) {
1335 try {
1336 return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
1337 } catch (ExecutionException e) {
1338 Throwable t = e.getCause();
1339 if (t instanceof IllegalAccessException) {
1340 throw (IllegalAccessException)t;
1341 }
1342 if (t instanceof InvocationTargetException) {
1343 throw (InvocationTargetException)t;
1344 }
1345 throw new RuntimeException("Unexpected exception", t);
1346 } catch (InterruptedException e) {
1347 // Call get again
1348 } catch (CancellationException e) {
1349 throw new RuntimeException("Unexpected cancellation exception", e);
1350 }
1351 }
1352 }
1353
Jon Miranda4597e982014-07-29 07:25:49 -07001354 private static String formatIntToHexString(int value) {
1355 return "0x" + Integer.toHexString(value).toUpperCase();
1356 }
1357
The Android Open Source Project10592532009-03-18 17:39:46 -07001358 private static void exportMethods(Context context, Object view, BufferedWriter out,
1359 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001360
1361 final Method[] methods = getExportedPropertyMethods(klass);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001362 int count = methods.length;
1363 for (int i = 0; i < count; i++) {
1364 final Method method = methods[i];
1365 //noinspection EmptyCatchBlock
1366 try {
Kristian Monsen97e8f622013-04-26 12:51:19 -07001367 Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 final Class<?> returnType = method.getReturnType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001369 final ExportedProperty property = sAnnotations.get(method);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001370 String categoryPrefix =
1371 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372
1373 if (returnType == int.class) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001374 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001375 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001376 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001377 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001378 final FlagToString[] flagsMapping = property.flagMapping();
1379 if (flagsMapping.length > 0) {
1380 final int intValue = (Integer) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001381 final String valuePrefix =
1382 categoryPrefix + prefix + method.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001383 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1384 }
1385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001386 final IntToString[] mapping = property.mapping();
1387 if (mapping.length > 0) {
1388 final int intValue = (Integer) methodValue;
1389 boolean mapped = false;
1390 int mappingCount = mapping.length;
1391 for (int j = 0; j < mappingCount; j++) {
1392 final IntToString mapper = mapping[j];
1393 if (mapper.from() == intValue) {
1394 methodValue = mapper.to();
1395 mapped = true;
1396 break;
1397 }
1398 }
1399
1400 if (!mapped) {
1401 methodValue = intValue;
1402 }
1403 }
1404 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001405 } else if (returnType == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001406 final int[] array = (int[]) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001407 final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001408 final String suffix = "()";
1409
The Android Open Source Project10592532009-03-18 17:39:46 -07001410 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001411
Jon Miranda48520212014-08-08 10:49:47 -07001412 continue;
Jon Miranda836c0a82014-08-11 12:32:26 -07001413 } else if (returnType == String[].class) {
1414 final String[] array = (String[]) methodValue;
1415 if (property.hasAdjacentMapping() && array != null) {
1416 for (int j = 0; j < array.length; j += 2) {
1417 if (array[j] != null) {
1418 writeEntry(out, categoryPrefix + prefix, array[j], "()",
1419 array[j + 1] == null ? "null" : array[j + 1]);
1420 }
1421
1422 }
1423 }
1424
1425 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426 } else if (!returnType.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001428 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001429 continue;
1430 }
1431 }
1432
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001433 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001434 } catch (IllegalAccessException e) {
1435 } catch (InvocationTargetException e) {
Kristian Monsen97e8f622013-04-26 12:51:19 -07001436 } catch (TimeoutException e) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 }
1438 }
1439 }
1440
The Android Open Source Project10592532009-03-18 17:39:46 -07001441 private static void exportFields(Context context, Object view, BufferedWriter out,
1442 Class<?> klass, String prefix) throws IOException {
1443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 final Field[] fields = getExportedPropertyFields(klass);
1445
1446 int count = fields.length;
1447 for (int i = 0; i < count; i++) {
1448 final Field field = fields[i];
1449
1450 //noinspection EmptyCatchBlock
1451 try {
1452 Object fieldValue = null;
1453 final Class<?> type = field.getType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001454 final ExportedProperty property = sAnnotations.get(field);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001455 String categoryPrefix =
1456 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457
Fabrice Di Megliob365f912013-03-27 16:36:21 -07001458 if (type == int.class || type == byte.class) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001459 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001461 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001463 final FlagToString[] flagsMapping = property.flagMapping();
1464 if (flagsMapping.length > 0) {
1465 final int intValue = field.getInt(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001466 final String valuePrefix =
1467 categoryPrefix + prefix + field.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001468 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1469 }
1470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 final IntToString[] mapping = property.mapping();
1472 if (mapping.length > 0) {
1473 final int intValue = field.getInt(view);
1474 int mappingCount = mapping.length;
1475 for (int j = 0; j < mappingCount; j++) {
1476 final IntToString mapped = mapping[j];
1477 if (mapped.from() == intValue) {
1478 fieldValue = mapped.to();
1479 break;
1480 }
1481 }
1482
1483 if (fieldValue == null) {
1484 fieldValue = intValue;
1485 }
1486 }
Jon Miranda4597e982014-07-29 07:25:49 -07001487
1488 if (property.formatToHexString()) {
1489 fieldValue = field.get(view);
1490 if (type == int.class) {
1491 fieldValue = formatIntToHexString((Integer) fieldValue);
1492 } else if (type == byte.class) {
1493 fieldValue = "0x" + Byte.toHexString((Byte) fieldValue, true);
1494 }
1495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001497 } else if (type == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001498 final int[] array = (int[]) field.get(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001499 final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001500 final String suffix = "";
1501
The Android Open Source Project10592532009-03-18 17:39:46 -07001502 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001503
Jon Miranda48520212014-08-08 10:49:47 -07001504 continue;
Jon Miranda836c0a82014-08-11 12:32:26 -07001505 } else if (type == String[].class) {
1506 final String[] array = (String[]) field.get(view);
1507 if (property.hasAdjacentMapping() && array != null) {
1508 for (int j = 0; j < array.length; j += 2) {
1509 if (array[j] != null) {
1510 writeEntry(out, categoryPrefix + prefix, array[j], "",
1511 array[j + 1] == null ? "null" : array[j + 1]);
1512 }
1513 }
1514 }
1515
1516 continue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 } else if (!type.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001518 if (property.deepExport()) {
Romain Guydfab3632012-10-03 14:53:25 -07001519 dumpViewProperties(context, field.get(view), out, prefix +
1520 property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001521 continue;
1522 }
1523 }
1524
1525 if (fieldValue == null) {
1526 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001527 }
1528
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001529 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 } catch (IllegalAccessException e) {
1531 }
1532 }
1533 }
1534
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001535 private static void writeEntry(BufferedWriter out, String prefix, String name,
1536 String suffix, Object value) throws IOException {
1537
1538 out.write(prefix);
1539 out.write(name);
1540 out.write(suffix);
1541 out.write("=");
1542 writeValue(out, value);
1543 out.write(' ');
1544 }
1545
Romain Guy809a7f62009-05-14 15:44:42 -07001546 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1547 int intValue, String prefix) throws IOException {
1548
1549 final int count = mapping.length;
1550 for (int j = 0; j < count; j++) {
1551 final FlagToString flagMapping = mapping[j];
1552 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001553 final int maskResult = intValue & flagMapping.mask();
1554 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001555 if ((test && ifTrue) || (!test && !ifTrue)) {
1556 final String name = flagMapping.name();
Jon Miranda4597e982014-07-29 07:25:49 -07001557 final String value = formatIntToHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001558 writeEntry(out, prefix, name, "", value);
1559 }
1560 }
1561 }
1562
Jorim Jaggi484851b2017-09-22 16:03:27 +02001563 /**
1564 * Converts an integer from a field that is mapped with {@link IntToString} to its string
1565 * representation.
1566 *
1567 * @param clazz The class the field is defined on.
1568 * @param field The field on which the {@link ExportedProperty} is defined on.
1569 * @param integer The value to convert.
1570 * @return The value converted into its string representation.
1571 * @hide
1572 */
1573 public static String intToString(Class<?> clazz, String field, int integer) {
1574 final IntToString[] mapping = getMapping(clazz, field);
1575 if (mapping == null) {
1576 return Integer.toString(integer);
1577 }
1578 final int count = mapping.length;
1579 for (int j = 0; j < count; j++) {
1580 final IntToString map = mapping[j];
1581 if (map.from() == integer) {
1582 return map.to();
1583 }
1584 }
1585 return Integer.toString(integer);
1586 }
1587
1588 /**
1589 * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
1590 * representation.
1591 *
1592 * @param clazz The class the field is defined on.
1593 * @param field The field on which the {@link ExportedProperty} is defined on.
1594 * @param flags The flags to convert.
1595 * @return The flags converted into their string representations.
1596 * @hide
1597 */
1598 public static String flagsToString(Class<?> clazz, String field, int flags) {
1599 final FlagToString[] mapping = getFlagMapping(clazz, field);
1600 if (mapping == null) {
1601 return Integer.toHexString(flags);
1602 }
1603 final StringBuilder result = new StringBuilder();
1604 final int count = mapping.length;
1605 for (int j = 0; j < count; j++) {
1606 final FlagToString flagMapping = mapping[j];
1607 final boolean ifTrue = flagMapping.outputIf();
1608 final int maskResult = flags & flagMapping.mask();
1609 final boolean test = maskResult == flagMapping.equals();
1610 if (test && ifTrue) {
1611 final String name = flagMapping.name();
1612 result.append(name).append(' ');
1613 }
1614 }
Jorim Jaggi2d3954f2017-09-28 16:56:46 +02001615 if (result.length() > 0) {
1616 result.deleteCharAt(result.length() - 1);
1617 }
Jorim Jaggi484851b2017-09-22 16:03:27 +02001618 return result.toString();
1619 }
1620
1621 private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
1622 try {
1623 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
1624 .flagMapping();
1625 } catch (NoSuchFieldException e) {
1626 return null;
1627 }
1628 }
1629
1630 private static IntToString[] getMapping(Class<?> clazz, String field) {
1631 try {
1632 return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
1633 } catch (NoSuchFieldException e) {
1634 return null;
1635 }
1636 }
1637
The Android Open Source Project10592532009-03-18 17:39:46 -07001638 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001639 ExportedProperty property, int[] array, String prefix, String suffix)
1640 throws IOException {
1641
1642 final IntToString[] indexMapping = property.indexMapping();
1643 final boolean hasIndexMapping = indexMapping.length > 0;
1644
1645 final IntToString[] mapping = property.mapping();
1646 final boolean hasMapping = mapping.length > 0;
1647
The Android Open Source Project10592532009-03-18 17:39:46 -07001648 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001649 final int valuesCount = array.length;
1650
1651 for (int j = 0; j < valuesCount; j++) {
1652 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001653 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001654
1655 final int intValue = array[j];
1656
1657 name = String.valueOf(j);
1658 if (hasIndexMapping) {
1659 int mappingCount = indexMapping.length;
1660 for (int k = 0; k < mappingCount; k++) {
1661 final IntToString mapped = indexMapping[k];
1662 if (mapped.from() == j) {
1663 name = mapped.to();
1664 break;
1665 }
1666 }
1667 }
1668
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001669 if (hasMapping) {
1670 int mappingCount = mapping.length;
1671 for (int k = 0; k < mappingCount; k++) {
1672 final IntToString mapped = mapping[k];
1673 if (mapped.from() == intValue) {
1674 value = mapped.to();
1675 break;
1676 }
1677 }
1678 }
1679
1680 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001681 if (value == null) value = (String) resolveId(context, intValue);
1682 } else {
1683 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001684 }
1685
1686 writeEntry(out, prefix, name, suffix, value);
1687 }
1688 }
1689
Romain Guy237c1ce2009-12-08 11:30:25 -08001690 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001691 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001692 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001693 if (id >= 0) {
1694 try {
1695 fieldValue = resources.getResourceTypeName(id) + '/' +
1696 resources.getResourceEntryName(id);
1697 } catch (Resources.NotFoundException e) {
Jon Miranda4597e982014-07-29 07:25:49 -07001698 fieldValue = "id/" + formatIntToHexString(id);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001699 }
1700 } else {
1701 fieldValue = "NO_ID";
1702 }
1703 return fieldValue;
1704 }
1705
1706 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1707 if (value != null) {
Romain Guy97723b22012-09-27 23:36:34 -07001708 String output = "[EXCEPTION]";
1709 try {
1710 output = value.toString().replace("\n", "\\n");
1711 } finally {
1712 out.write(String.valueOf(output.length()));
1713 out.write(",");
1714 out.write(output);
1715 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001716 } else {
1717 out.write("4,null");
1718 }
1719 }
1720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001721 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1722 if (mCapturedViewFieldsForClasses == null) {
1723 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1724 }
1725 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1726
1727 Field[] fields = map.get(klass);
1728 if (fields != null) {
1729 return fields;
1730 }
1731
1732 final ArrayList<Field> foundFields = new ArrayList<Field>();
1733 fields = klass.getFields();
1734
1735 int count = fields.length;
1736 for (int i = 0; i < count; i++) {
1737 final Field field = fields[i];
1738 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1739 field.setAccessible(true);
1740 foundFields.add(field);
1741 }
1742 }
1743
1744 fields = foundFields.toArray(new Field[foundFields.size()]);
1745 map.put(klass, fields);
1746
1747 return fields;
1748 }
1749
1750 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1751 if (mCapturedViewMethodsForClasses == null) {
1752 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1753 }
1754 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1755
1756 Method[] methods = map.get(klass);
1757 if (methods != null) {
1758 return methods;
1759 }
1760
1761 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1762 methods = klass.getMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001763
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001764 int count = methods.length;
1765 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001766 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001767 if (method.getParameterTypes().length == 0 &&
1768 method.isAnnotationPresent(CapturedViewProperty.class) &&
1769 method.getReturnType() != Void.class) {
1770 method.setAccessible(true);
1771 foundMethods.add(method);
1772 }
1773 }
1774
1775 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1776 map.put(klass, methods);
1777
1778 return methods;
1779 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001780
1781 private static String capturedViewExportMethods(Object obj, Class<?> klass,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001782 String prefix) {
1783
1784 if (obj == null) {
1785 return "null";
1786 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001788 StringBuilder sb = new StringBuilder();
1789 final Method[] methods = capturedViewGetPropertyMethods(klass);
1790
1791 int count = methods.length;
1792 for (int i = 0; i < count; i++) {
1793 final Method method = methods[i];
1794 try {
1795 Object methodValue = method.invoke(obj, (Object[]) null);
1796 final Class<?> returnType = method.getReturnType();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001798 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1799 if (property.retrieveReturn()) {
1800 //we are interested in the second level data only
1801 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001802 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001803 sb.append(prefix);
1804 sb.append(method.getName());
1805 sb.append("()=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001808 final String value = methodValue.toString().replace("\n", "\\n");
1809 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001810 } else {
1811 sb.append("null");
1812 }
1813 sb.append("; ");
1814 }
Jon Miranda4597e982014-07-29 07:25:49 -07001815 } catch (IllegalAccessException e) {
1816 //Exception IllegalAccess, it is OK here
1817 //we simply ignore this method
1818 } catch (InvocationTargetException e) {
1819 //Exception InvocationTarget, it is OK here
1820 //we simply ignore this method
1821 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001822 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001823 return sb.toString();
1824 }
1825
1826 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001827 if (obj == null) {
1828 return "null";
1829 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001830
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001831 StringBuilder sb = new StringBuilder();
1832 final Field[] fields = capturedViewGetPropertyFields(klass);
1833
1834 int count = fields.length;
1835 for (int i = 0; i < count; i++) {
1836 final Field field = fields[i];
1837 try {
1838 Object fieldValue = field.get(obj);
1839
1840 sb.append(prefix);
1841 sb.append(field.getName());
1842 sb.append("=");
1843
1844 if (fieldValue != null) {
1845 final String value = fieldValue.toString().replace("\n", "\\n");
1846 sb.append(value);
1847 } else {
1848 sb.append("null");
1849 }
1850 sb.append(' ');
1851 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001852 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001853 //we simply ignore this field
1854 }
1855 }
1856 return sb.toString();
1857 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001858
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001859 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001860 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001861 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001862 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001863 * @param tag for log
1864 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001865 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001866 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001867 Class<?> klass = view.getClass();
1868 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1869 sb.append(capturedViewExportFields(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001870 sb.append(capturedViewExportMethods(view, klass, ""));
1871 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001872 }
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001873
1874 /**
1875 * Invoke a particular method on given view.
1876 * The given method is always invoked on the UI thread. The caller thread will stall until the
1877 * method invocation is complete. Returns an object equal to the result of the method
1878 * invocation, null if the method is declared to return void
1879 * @throws Exception if the method invocation caused any exception
1880 * @hide
1881 */
1882 public static Object invokeViewMethod(final View view, final Method method,
1883 final Object[] args) {
1884 final CountDownLatch latch = new CountDownLatch(1);
1885 final AtomicReference<Object> result = new AtomicReference<Object>();
1886 final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1887
1888 view.post(new Runnable() {
1889 @Override
1890 public void run() {
1891 try {
1892 result.set(method.invoke(view, args));
1893 } catch (InvocationTargetException e) {
1894 exception.set(e.getCause());
1895 } catch (Exception e) {
1896 exception.set(e);
1897 }
1898
1899 latch.countDown();
1900 }
1901 });
1902
1903 try {
1904 latch.await();
1905 } catch (InterruptedException e) {
1906 throw new RuntimeException(e);
1907 }
1908
1909 if (exception.get() != null) {
1910 throw new RuntimeException(exception.get());
1911 }
1912
1913 return result.get();
1914 }
1915
1916 /**
1917 * @hide
1918 */
1919 public static void setLayoutParameter(final View view, final String param, final int value)
1920 throws NoSuchFieldException, IllegalAccessException {
1921 final ViewGroup.LayoutParams p = view.getLayoutParams();
1922 final Field f = p.getClass().getField(param);
1923 if (f.getType() != int.class) {
1924 throw new RuntimeException("Only integer layout parameters can be set. Field "
Jon Miranda4597e982014-07-29 07:25:49 -07001925 + param + " is of type " + f.getType().getSimpleName());
Siva Velusamyf9455fa2013-01-17 18:01:52 -08001926 }
1927
1928 f.set(p, Integer.valueOf(value));
1929
1930 view.post(new Runnable() {
1931 @Override
1932 public void run() {
1933 view.setLayoutParams(p);
1934 }
1935 });
1936 }
Sunny Goyald1b287e2018-01-04 09:37:22 -08001937
1938 /**
1939 * @hide
1940 */
1941 public static class SoftwareCanvasProvider implements CanvasProvider {
1942
1943 private Canvas mCanvas;
1944 private Bitmap mBitmap;
1945 private boolean mEnabledHwBitmapsInSwMode;
1946
1947 @Override
1948 public Canvas getCanvas(View view, int width, int height) {
1949 mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(),
1950 width, height, Bitmap.Config.ARGB_8888);
1951 if (mBitmap == null) {
1952 throw new OutOfMemoryError();
1953 }
1954 mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi);
1955
1956 if (view.mAttachInfo != null) {
1957 mCanvas = view.mAttachInfo.mCanvas;
1958 }
1959 if (mCanvas == null) {
1960 mCanvas = new Canvas();
1961 }
1962 mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
1963 mCanvas.setBitmap(mBitmap);
1964 return mCanvas;
1965 }
1966
1967 @Override
1968 public Bitmap createBitmap() {
1969 mCanvas.setBitmap(null);
1970 mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
1971 return mBitmap;
1972 }
1973 }
1974
1975 /**
1976 * @hide
1977 */
1978 public static class HardwareCanvasProvider implements CanvasProvider {
John Reck519ad482018-02-12 17:08:48 -08001979 private Picture mPicture;
Sunny Goyald1b287e2018-01-04 09:37:22 -08001980
1981 @Override
1982 public Canvas getCanvas(View view, int width, int height) {
John Reck519ad482018-02-12 17:08:48 -08001983 mPicture = new Picture();
1984 return mPicture.beginRecording(width, height);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001985 }
1986
1987 @Override
1988 public Bitmap createBitmap() {
John Reck519ad482018-02-12 17:08:48 -08001989 mPicture.endRecording();
1990 return Bitmap.createBitmap(mPicture);
Sunny Goyald1b287e2018-01-04 09:37:22 -08001991 }
1992 }
1993
1994 /**
1995 * @hide
1996 */
1997 public interface CanvasProvider {
1998
1999 /**
2000 * Returns a canvas which can be used to draw {@param view}
2001 */
2002 Canvas getCanvas(View view, int width, int height);
2003
2004 /**
2005 * Creates a bitmap from previously returned canvas
2006 * @return
2007 */
2008 Bitmap createBitmap();
2009 }
Mathieu Chartier3d529c52015-03-25 13:35:38 -07002010}