blob: 1534099615509accf6225ca93780ee9c1d9725ad [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
19import android.util.Log;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070020import android.util.DisplayMetrics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.res.Resources;
The Android Open Source Project10592532009-03-18 17:39:46 -070022import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.graphics.Bitmap;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070024import android.graphics.Canvas;
Romain Guy223ff5c2010-03-02 17:07:47 -080025import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.os.Environment;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070027import android.os.Debug;
Romain Guy223ff5c2010-03-02 17:07:47 -080028import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
Romain Guy223ff5c2010-03-02 17:07:47 -080030import java.io.ByteArrayOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import java.io.File;
32import java.io.BufferedWriter;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.io.FileOutputStream;
36import java.io.DataOutputStream;
37import java.io.OutputStreamWriter;
38import java.io.BufferedOutputStream;
39import java.io.OutputStream;
40import java.util.List;
41import java.util.LinkedList;
42import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.concurrent.CountDownLatch;
45import java.util.concurrent.TimeUnit;
46import java.lang.annotation.Target;
47import java.lang.annotation.ElementType;
48import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
50import java.lang.reflect.Field;
51import java.lang.reflect.Method;
52import java.lang.reflect.InvocationTargetException;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070053import java.lang.reflect.AccessibleObject;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054
55/**
56 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
57 */
58public class ViewDebug {
59 /**
Romain Guy13922e02009-05-12 17:56:14 -070060 * Log tag used to log errors related to the consistency of the view hierarchy.
61 *
62 * @hide
63 */
64 public static final String CONSISTENCY_LOG_TAG = "ViewConsistency";
65
66 /**
67 * Flag indicating the consistency check should check layout-related properties.
68 *
69 * @hide
70 */
71 public static final int CONSISTENCY_LAYOUT = 0x1;
72
73 /**
74 * Flag indicating the consistency check should check drawing-related properties.
75 *
76 * @hide
77 */
78 public static final int CONSISTENCY_DRAWING = 0x2;
79
80 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 * Enables or disables view hierarchy tracing. Any invoker of
82 * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
83 * check that this value is set to true as not to affect performance.
84 */
85 public static final boolean TRACE_HIERARCHY = false;
86
87 /**
88 * Enables or disables view recycler tracing. Any invoker of
89 * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
90 * check that this value is set to true as not to affect performance.
91 */
92 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070093
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094 /**
Romain Guycf635ae2010-01-22 11:00:29 -080095 * Enables or disables motion events tracing. Any invoker of
96 * {@link #trace(View, MotionEvent, MotionEventTraceType)} should first check
97 * that this value is set to true as not to affect performance.
98 *
99 * @hide
100 */
101 public static final boolean TRACE_MOTION_EVENTS = false;
102
103 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 * The system property of dynamic switch for capturing view information
105 * when it is set, we dump interested fields and methods for the view on focus
Romain Guya1f3e4a2009-06-04 15:10:46 -0700106 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
Romain Guya1f3e4a2009-06-04 15:10:46 -0700108
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 /**
110 * The system property of dynamic switch for capturing event information
111 * when it is set, we log key events, touch/motion and trackball events
Romain Guya1f3e4a2009-06-04 15:10:46 -0700112 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700114
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 /**
Romain Guy13922e02009-05-12 17:56:14 -0700116 * Profiles drawing times in the events log.
117 *
118 * @hide
119 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700120 public static final boolean DEBUG_PROFILE_DRAWING = false;
Romain Guy13922e02009-05-12 17:56:14 -0700121
122 /**
123 * Profiles layout times in the events log.
124 *
125 * @hide
126 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700127 public static final boolean DEBUG_PROFILE_LAYOUT = false;
Romain Guy13922e02009-05-12 17:56:14 -0700128
129 /**
130 * Profiles real fps (times between draws) and displays the result.
131 *
132 * @hide
133 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700134 public static final boolean DEBUG_SHOW_FPS = false;
Romain Guy13922e02009-05-12 17:56:14 -0700135
136 /**
Christopher Tate2c095f32010-10-04 14:13:40 -0700137 * Enables detailed logging of drag/drop operations.
138 * @hide
139 */
Christopher Tate994ef922011-01-12 20:06:07 -0800140 public static final boolean DEBUG_DRAG = false;
Christopher Tate2c095f32010-10-04 14:13:40 -0700141
142 /**
Jeff Brown4e91a182011-04-07 11:38:09 -0700143 * Enables logging of factors that affect the latency and responsiveness of an application.
144 *
145 * Logs the relative difference between the time an event was created and the time it
146 * was delivered.
147 *
148 * Logs the time spent waiting for Surface.lockCanvas() or eglSwapBuffers().
149 * This is time that the event loop spends blocked and unresponsive. Ideally, drawing
150 * and animations should be perfectly synchronized with VSYNC so that swap buffers
151 * is instantaneous.
152 *
153 * Logs the time spent in ViewRoot.performTraversals() or ViewRoot.draw().
154 * @hide
155 */
156 public static final boolean DEBUG_LATENCY = false;
157
158 /**
Romain Guy13922e02009-05-12 17:56:14 -0700159 * <p>Enables or disables views consistency check. Even when this property is enabled,
Joe Onorato43a17652011-04-06 19:22:23 -0700160 * view consistency checks happen only if {@link false} is set
Romain Guy13922e02009-05-12 17:56:14 -0700161 * to true. The value of this property can be configured externally in one of the
162 * following files:</p>
163 * <ul>
164 * <li>/system/debug.prop</li>
165 * <li>/debug.prop</li>
166 * <li>/data/debug.prop</li>
167 * </ul>
168 * @hide
169 */
170 @Debug.DebugProperty
171 public static boolean consistencyCheckEnabled = false;
172
173 static {
Joe Onorato43a17652011-04-06 19:22:23 -0700174 if (false) {
Romain Guye551dc72009-07-28 15:30:11 -0700175 Debug.setFieldsOn(ViewDebug.class, true);
176 }
Romain Guy13922e02009-05-12 17:56:14 -0700177 }
178
179 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 * This annotation can be used to mark fields and methods to be dumped by
181 * the view server. Only non-void methods with no arguments can be annotated
182 * by this annotation.
183 */
184 @Target({ ElementType.FIELD, ElementType.METHOD })
185 @Retention(RetentionPolicy.RUNTIME)
186 public @interface ExportedProperty {
187 /**
188 * When resolveId is true, and if the annotated field/method return value
189 * is an int, the value is converted to an Android's resource name.
190 *
191 * @return true if the property's value must be transformed into an Android
192 * resource name, false otherwise
193 */
194 boolean resolveId() default false;
195
196 /**
197 * A mapping can be defined to map int values to specific strings. For
198 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
199 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
200 * these human readable values:
201 *
202 * <pre>
203 * @ViewDebug.ExportedProperty(mapping = {
204 * @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
205 * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
206 * @ViewDebug.IntToString(from = 8, to = "GONE")
207 * })
208 * public int getVisibility() { ...
209 * <pre>
210 *
211 * @return An array of int to String mappings
212 *
213 * @see android.view.ViewDebug.IntToString
214 */
215 IntToString[] mapping() default { };
216
217 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700218 * A mapping can be defined to map array indices to specific strings.
219 * A mapping can be used to see human readable values for the indices
220 * of an array:
221 *
222 * <pre>
Romain Guy809a7f62009-05-14 15:44:42 -0700223 * @ViewDebug.ExportedProperty(indexMapping = {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700224 * @ViewDebug.IntToString(from = 0, to = "INVALID"),
225 * @ViewDebug.IntToString(from = 1, to = "FIRST"),
226 * @ViewDebug.IntToString(from = 2, to = "SECOND")
227 * })
228 * private int[] mElements;
229 * <pre>
230 *
231 * @return An array of int to String mappings
232 *
233 * @see android.view.ViewDebug.IntToString
234 * @see #mapping()
235 */
236 IntToString[] indexMapping() default { };
237
238 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700239 * A flags mapping can be defined to map flags encoded in an integer to
240 * specific strings. A mapping can be used to see human readable values
241 * for the flags of an integer:
242 *
243 * <pre>
244 * @ViewDebug.ExportedProperty(flagMapping = {
245 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"),
246 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"),
247 * })
248 * private int mFlags;
249 * <pre>
250 *
251 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700252 *
Romain Guy809a7f62009-05-14 15:44:42 -0700253 * @return An array of int to String mappings
254 */
255 FlagToString[] flagMapping() default { };
256
257 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 * When deep export is turned on, this property is not dumped. Instead, the
259 * properties contained in this property are dumped. Each child property
260 * is prefixed with the name of this property.
261 *
262 * @return true if the properties of this property should be dumped
263 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700264 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 */
266 boolean deepExport() default false;
267
268 /**
269 * The prefix to use on child properties when deep export is enabled
270 *
271 * @return a prefix as a String
272 *
273 * @see #deepExport()
274 */
275 String prefix() default "";
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700276
277 /**
278 * Specifies the category the property falls into, such as measurement,
279 * layout, drawing, etc.
280 *
281 * @return the category as String
282 */
283 String category() default "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 }
285
286 /**
287 * Defines a mapping from an int value to a String. Such a mapping can be used
288 * in a @ExportedProperty to provide more meaningful values to the end user.
289 *
290 * @see android.view.ViewDebug.ExportedProperty
291 */
292 @Target({ ElementType.TYPE })
293 @Retention(RetentionPolicy.RUNTIME)
294 public @interface IntToString {
295 /**
296 * The original int value to map to a String.
297 *
298 * @return An arbitrary int value.
299 */
300 int from();
301
302 /**
303 * The String to use in place of the original int value.
304 *
305 * @return An arbitrary non-null String.
306 */
307 String to();
308 }
Romain Guy809a7f62009-05-14 15:44:42 -0700309
310 /**
311 * Defines a mapping from an flag to a String. Such a mapping can be used
312 * in a @ExportedProperty to provide more meaningful values to the end user.
313 *
314 * @see android.view.ViewDebug.ExportedProperty
315 */
316 @Target({ ElementType.TYPE })
317 @Retention(RetentionPolicy.RUNTIME)
318 public @interface FlagToString {
319 /**
320 * The mask to apply to the original value.
321 *
322 * @return An arbitrary int value.
323 */
324 int mask();
325
326 /**
327 * The value to compare to the result of:
328 * <code>original value &amp; {@link #mask()}</code>.
329 *
330 * @return An arbitrary value.
331 */
332 int equals();
333
334 /**
335 * The String to use in place of the original int value.
336 *
337 * @return An arbitrary non-null String.
338 */
339 String name();
340
341 /**
342 * Indicates whether to output the flag when the test is true,
343 * or false. Defaults to true.
344 */
345 boolean outputIf() default true;
346 }
347
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 /**
349 * This annotation can be used to mark fields and methods to be dumped when
350 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700351 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 */
353 @Target({ ElementType.FIELD, ElementType.METHOD })
354 @Retention(RetentionPolicy.RUNTIME)
355 public @interface CapturedViewProperty {
356 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700357 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700359 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800360 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700361 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700363 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
367 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
368
369 // Maximum delay in ms after which we stop trying to capture a View's drawing
370 private static final int CAPTURE_TIMEOUT = 4000;
371
372 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
373 private static final String REMOTE_COMMAND_DUMP = "DUMP";
374 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
375 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700376 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800377 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
Chet Haaseed30fd82011-04-22 16:18:45 -0700378 private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379
380 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
381 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700382 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 /**
385 * Defines the type of hierarhcy trace to output to the hierarchy traces file.
386 */
387 public enum HierarchyTraceType {
388 INVALIDATE,
389 INVALIDATE_CHILD,
390 INVALIDATE_CHILD_IN_PARENT,
391 REQUEST_LAYOUT,
392 ON_LAYOUT,
393 ON_MEASURE,
394 DRAW,
395 BUILD_CACHE
396 }
397
398 private static BufferedWriter sHierarchyTraces;
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700399 private static ViewAncestor sHierarhcyRoot;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 private static String sHierarchyTracePrefix;
401
402 /**
403 * Defines the type of recycler trace to output to the recycler traces file.
404 */
405 public enum RecyclerTraceType {
406 NEW_VIEW,
407 BIND_VIEW,
408 RECYCLE_FROM_ACTIVE_HEAP,
409 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 MOVE_TO_SCRAP_HEAP,
411 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
412 }
413
414 private static class RecyclerTrace {
415 public int view;
416 public RecyclerTraceType type;
417 public int position;
418 public int indexOnScreen;
419 }
420
421 private static View sRecyclerOwnerView;
422 private static List<View> sRecyclerViews;
423 private static List<RecyclerTrace> sRecyclerTraces;
424 private static String sRecyclerTracePrefix;
425
426 /**
Romain Guycf635ae2010-01-22 11:00:29 -0800427 * Defines the type of motion events trace to output to the motion events traces file.
428 *
429 * @hide
430 */
431 public enum MotionEventTraceType {
432 DISPATCH,
433 ON_INTERCEPT,
434 ON_TOUCH
435 }
436
437 private static BufferedWriter sMotionEventTraces;
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700438 private static ViewAncestor sMotionEventRoot;
Romain Guycf635ae2010-01-22 11:00:29 -0800439 private static String sMotionEventTracePrefix;
440
441 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 * Returns the number of instanciated Views.
443 *
444 * @return The number of Views instanciated in the current process.
445 *
446 * @hide
447 */
448 public static long getViewInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700449 return Debug.countInstancesOfClass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800450 }
451
452 /**
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700453 * Returns the number of instanciated ViewAncestors.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800454 *
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700455 * @return The number of ViewAncestors instanciated in the current process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 *
457 * @hide
458 */
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700459 public static long getViewAncestorInstanceCount() {
460 return Debug.countInstancesOfClass(ViewAncestor.class);
Romain Guya1f3e4a2009-06-04 15:10:46 -0700461 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462
463 /**
464 * Outputs a trace to the currently opened recycler traces. The trace records the type of
465 * recycler action performed on the supplied view as well as a number of parameters.
466 *
467 * @param view the view to trace
468 * @param type the type of the trace
469 * @param parameters parameters depending on the type of the trace
470 */
471 public static void trace(View view, RecyclerTraceType type, int... parameters) {
472 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
473 return;
474 }
475
476 if (!sRecyclerViews.contains(view)) {
477 sRecyclerViews.add(view);
478 }
479
480 final int index = sRecyclerViews.indexOf(view);
481
482 RecyclerTrace trace = new RecyclerTrace();
483 trace.view = index;
484 trace.type = type;
485 trace.position = parameters[0];
486 trace.indexOnScreen = parameters[1];
487
488 sRecyclerTraces.add(trace);
489 }
490
491 /**
492 * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
493 * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
494 * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
495 *
496 * Only one view recycler can be traced at the same time. After calling this method, any
497 * other invocation will result in a <code>IllegalStateException</code> unless
498 * {@link #stopRecyclerTracing()} is invoked before.
499 *
500 * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
501 *
502 * This method will return immediately if TRACE_RECYCLER is false.
503 *
504 * @param prefix the traces files name prefix
505 * @param view the view whose recycler must be traced
506 *
507 * @see #stopRecyclerTracing()
508 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
509 */
510 public static void startRecyclerTracing(String prefix, View view) {
511 //noinspection PointlessBooleanExpression,ConstantConditions
512 if (!TRACE_RECYCLER) {
513 return;
514 }
515
516 if (sRecyclerOwnerView != null) {
517 throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
518 " a new trace!");
519 }
520
521 sRecyclerTracePrefix = prefix;
522 sRecyclerOwnerView = view;
523 sRecyclerViews = new ArrayList<View>();
524 sRecyclerTraces = new LinkedList<RecyclerTrace>();
525 }
526
527 /**
528 * Stops the current view recycer tracing.
529 *
530 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
531 * containing all the traces (or method calls) relative to the specified view's recycler.
532 *
533 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
534 * containing all of the views used by the recycler of the view supplied to
535 * {@link #startRecyclerTracing(String, View)}.
536 *
537 * This method will return immediately if TRACE_RECYCLER is false.
538 *
539 * @see #startRecyclerTracing(String, View)
540 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
541 */
542 public static void stopRecyclerTracing() {
543 //noinspection PointlessBooleanExpression,ConstantConditions
544 if (!TRACE_RECYCLER) {
545 return;
546 }
547
548 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
549 throw new IllegalStateException("You must call startRecyclerTracing() before" +
550 " stopRecyclerTracing()!");
551 }
552
553 File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700554 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 recyclerDump.mkdirs();
556
557 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
558 try {
559 final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
560
561 for (View view : sRecyclerViews) {
562 final String name = view.getClass().getName();
563 out.write(name);
564 out.newLine();
565 }
566
567 out.close();
568 } catch (IOException e) {
569 Log.e("View", "Could not dump recycler content");
570 return;
571 }
572
573 recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
574 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
575 try {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700576 if (recyclerDump.exists()) {
577 recyclerDump.delete();
578 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 final FileOutputStream file = new FileOutputStream(recyclerDump);
580 final DataOutputStream out = new DataOutputStream(file);
581
582 for (RecyclerTrace trace : sRecyclerTraces) {
583 out.writeInt(trace.view);
584 out.writeInt(trace.type.ordinal());
585 out.writeInt(trace.position);
586 out.writeInt(trace.indexOnScreen);
587 out.flush();
588 }
589
590 out.close();
591 } catch (IOException e) {
592 Log.e("View", "Could not dump recycler traces");
593 return;
594 }
595
596 sRecyclerViews.clear();
597 sRecyclerViews = null;
598
599 sRecyclerTraces.clear();
600 sRecyclerTraces = null;
601
602 sRecyclerOwnerView = null;
603 }
604
605 /**
606 * Outputs a trace to the currently opened traces file. The trace contains the class name
607 * and instance's hashcode of the specified view as well as the supplied trace type.
608 *
609 * @param view the view to trace
610 * @param type the type of the trace
611 */
612 public static void trace(View view, HierarchyTraceType type) {
613 if (sHierarchyTraces == null) {
614 return;
615 }
616
617 try {
618 sHierarchyTraces.write(type.name());
619 sHierarchyTraces.write(' ');
620 sHierarchyTraces.write(view.getClass().getName());
621 sHierarchyTraces.write('@');
622 sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
623 sHierarchyTraces.newLine();
624 } catch (IOException e) {
625 Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
626 }
627 }
628
629 /**
630 * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
631 * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
632 * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
633 *
634 * Only one view hierarchy can be traced at the same time. After calling this method, any
635 * other invocation will result in a <code>IllegalStateException</code> unless
636 * {@link #stopHierarchyTracing()} is invoked before.
637 *
638 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
639 * containing all the traces (or method calls) relative to the specified view's hierarchy.
640 *
641 * This method will return immediately if TRACE_HIERARCHY is false.
642 *
643 * @param prefix the traces files name prefix
644 * @param view the view whose hierarchy must be traced
645 *
646 * @see #stopHierarchyTracing()
647 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
648 */
649 public static void startHierarchyTracing(String prefix, View view) {
650 //noinspection PointlessBooleanExpression,ConstantConditions
651 if (!TRACE_HIERARCHY) {
652 return;
653 }
654
655 if (sHierarhcyRoot != null) {
656 throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
657 " a new trace!");
658 }
659
660 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700661 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 hierarchyDump.mkdirs();
663
664 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
665 sHierarchyTracePrefix = prefix;
666
667 try {
668 sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
669 } catch (IOException e) {
670 Log.e("View", "Could not dump view hierarchy");
671 return;
672 }
673
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700674 sHierarhcyRoot = (ViewAncestor) view.getRootView().getParent();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 }
676
677 /**
678 * Stops the current view hierarchy tracing. This method closes the file
679 * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
680 *
681 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
682 * containing the view hierarchy of the view supplied to
683 * {@link #startHierarchyTracing(String, View)}.
684 *
685 * This method will return immediately if TRACE_HIERARCHY is false.
686 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700687 * @see #startHierarchyTracing(String, View)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
689 */
690 public static void stopHierarchyTracing() {
691 //noinspection PointlessBooleanExpression,ConstantConditions
692 if (!TRACE_HIERARCHY) {
693 return;
694 }
695
696 if (sHierarhcyRoot == null || sHierarchyTraces == null) {
697 throw new IllegalStateException("You must call startHierarchyTracing() before" +
698 " stopHierarchyTracing()!");
699 }
700
701 try {
702 sHierarchyTraces.close();
703 } catch (IOException e) {
704 Log.e("View", "Could not write view traces");
705 }
706 sHierarchyTraces = null;
707
708 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700709 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 hierarchyDump.mkdirs();
711 hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
712
713 BufferedWriter out;
714 try {
715 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
716 } catch (IOException e) {
717 Log.e("View", "Could not dump view hierarchy");
718 return;
719 }
720
721 View view = sHierarhcyRoot.getView();
722 if (view instanceof ViewGroup) {
723 ViewGroup group = (ViewGroup) view;
724 dumpViewHierarchy(group, out, 0);
725 try {
726 out.close();
727 } catch (IOException e) {
728 Log.e("View", "Could not dump view hierarchy");
729 }
730 }
731
732 sHierarhcyRoot = null;
733 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700734
Romain Guycf635ae2010-01-22 11:00:29 -0800735 /**
736 * Outputs a trace to the currently opened traces file. The trace contains the class name
737 * and instance's hashcode of the specified view as well as the supplied trace type.
738 *
739 * @param view the view to trace
740 * @param event the event of the trace
741 * @param type the type of the trace
742 *
743 * @hide
744 */
745 public static void trace(View view, MotionEvent event, MotionEventTraceType type) {
746 if (sMotionEventTraces == null) {
747 return;
748 }
749
750 try {
751 sMotionEventTraces.write(type.name());
752 sMotionEventTraces.write(' ');
753 sMotionEventTraces.write(event.getAction());
754 sMotionEventTraces.write(' ');
755 sMotionEventTraces.write(view.getClass().getName());
756 sMotionEventTraces.write('@');
757 sMotionEventTraces.write(Integer.toHexString(view.hashCode()));
758 sHierarchyTraces.newLine();
759 } catch (IOException e) {
760 Log.w("View", "Error while dumping trace of event " + event + " for view " + view);
761 }
762 }
763
764 /**
765 * Starts tracing the motion events for the hierarchy of the specificy view.
766 * The trace is identified by a prefix, used to build the traces files names:
767 * <code>/EXTERNAL/motion-events/PREFIX.traces</code> and
768 * <code>/EXTERNAL/motion-events/PREFIX.tree</code>.
769 *
770 * Only one view hierarchy can be traced at the same time. After calling this method, any
771 * other invocation will result in a <code>IllegalStateException</code> unless
772 * {@link #stopMotionEventTracing()} is invoked before.
773 *
774 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.traces</code>
775 * containing all the traces (or method calls) relative to the specified view's hierarchy.
776 *
777 * This method will return immediately if TRACE_HIERARCHY is false.
778 *
779 * @param prefix the traces files name prefix
780 * @param view the view whose hierarchy must be traced
781 *
782 * @see #stopMotionEventTracing()
783 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
784 *
785 * @hide
786 */
787 public static void startMotionEventTracing(String prefix, View view) {
788 //noinspection PointlessBooleanExpression,ConstantConditions
789 if (!TRACE_MOTION_EVENTS) {
790 return;
791 }
792
793 if (sMotionEventRoot != null) {
794 throw new IllegalStateException("You must call stopMotionEventTracing() before running" +
795 " a new trace!");
796 }
797
798 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
799 //noinspection ResultOfMethodCallIgnored
800 hierarchyDump.mkdirs();
801
802 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
803 sMotionEventTracePrefix = prefix;
804
805 try {
806 sMotionEventTraces = new BufferedWriter(new FileWriter(hierarchyDump), 32 * 1024);
807 } catch (IOException e) {
808 Log.e("View", "Could not dump view hierarchy");
809 return;
810 }
811
Joe Onoratoc6cc0f82011-04-12 11:53:13 -0700812 sMotionEventRoot = (ViewAncestor) view.getRootView().getParent();
Romain Guycf635ae2010-01-22 11:00:29 -0800813 }
814
815 /**
816 * Stops the current motion events tracing. This method closes the file
817 * <code>/EXTERNAL/motion-events/PREFIX.traces</code>.
818 *
819 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.tree</code>
820 * containing the view hierarchy of the view supplied to
821 * {@link #startMotionEventTracing(String, View)}.
822 *
823 * This method will return immediately if TRACE_HIERARCHY is false.
824 *
825 * @see #startMotionEventTracing(String, View)
826 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
827 *
828 * @hide
829 */
830 public static void stopMotionEventTracing() {
831 //noinspection PointlessBooleanExpression,ConstantConditions
832 if (!TRACE_MOTION_EVENTS) {
833 return;
834 }
835
836 if (sMotionEventRoot == null || sMotionEventTraces == null) {
837 throw new IllegalStateException("You must call startMotionEventTracing() before" +
838 " stopMotionEventTracing()!");
839 }
840
841 try {
842 sMotionEventTraces.close();
843 } catch (IOException e) {
844 Log.e("View", "Could not write view traces");
845 }
846 sMotionEventTraces = null;
847
848 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
849 //noinspection ResultOfMethodCallIgnored
850 hierarchyDump.mkdirs();
851 hierarchyDump = new File(hierarchyDump, sMotionEventTracePrefix + ".tree");
852
853 BufferedWriter out;
854 try {
855 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
856 } catch (IOException e) {
857 Log.e("View", "Could not dump view hierarchy");
858 return;
859 }
860
861 View view = sMotionEventRoot.getView();
862 if (view instanceof ViewGroup) {
863 ViewGroup group = (ViewGroup) view;
864 dumpViewHierarchy(group, out, 0);
865 try {
866 out.close();
867 } catch (IOException e) {
868 Log.e("View", "Could not dump view hierarchy");
869 }
870 }
871
872 sHierarhcyRoot = null;
873 }
874
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 static void dispatchCommand(View view, String command, String parameters,
876 OutputStream clientStream) throws IOException {
877
878 // Paranoid but safe...
879 view = view.getRootView();
880
881 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
882 dump(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800883 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
884 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 } else {
886 final String[] params = parameters.split(" ");
887 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
888 capture(view, clientStream, params[0]);
Chet Haaseed30fd82011-04-22 16:18:45 -0700889 } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
890 outputDisplayList(view, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
892 invalidate(view, params[0]);
893 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
894 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700895 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
896 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800897 }
898 }
899 }
900
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700901 private static View findView(View root, String parameter) {
902 // Look by type/hashcode
903 if (parameter.indexOf('@') != -1) {
904 final String[] ids = parameter.split("@");
905 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800906 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700908 View view = root.getRootView();
909 if (view instanceof ViewGroup) {
910 return findView((ViewGroup) view, className, hashCode);
911 }
912 } else {
913 // Look by id
914 final int id = root.getResources().getIdentifier(parameter, null, null);
915 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 }
917
918 return null;
919 }
920
921 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700922 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 if (view != null) {
924 view.postInvalidate();
925 }
926 }
927
928 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700929 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 if (view != null) {
931 root.post(new Runnable() {
932 public void run() {
933 view.requestLayout();
934 }
935 });
936 }
937 }
938
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700939 private static void profile(View root, OutputStream clientStream, String parameter)
940 throws IOException {
941
942 final View view = findView(root, parameter);
943 BufferedWriter out = null;
944 try {
945 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
946
947 if (view != null) {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700948 profileViewAndChildren(view, out);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700949 } else {
950 out.write("-1 -1 -1");
951 out.newLine();
952 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700953 out.write("DONE.");
954 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700955 } catch (Exception e) {
956 android.util.Log.w("View", "Problem profiling the view:", e);
957 } finally {
958 if (out != null) {
959 out.close();
960 }
961 }
962 }
963
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700964 private static void profileViewAndChildren(final View view, BufferedWriter out)
965 throws IOException {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700966 profileViewAndChildren(view, out, true);
967 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700968
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700969 private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
970 throws IOException {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700971
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700972 long durationMeasure =
973 (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation(
974 view, new ViewOperation<Void>() {
975 public Void[] pre() {
976 forceLayout(view);
977 return null;
978 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700979
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700980 private void forceLayout(View view) {
981 view.forceLayout();
982 if (view instanceof ViewGroup) {
983 ViewGroup group = (ViewGroup) view;
984 final int count = group.getChildCount();
985 for (int i = 0; i < count; i++) {
986 forceLayout(group.getChildAt(i));
987 }
988 }
989 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700990
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700991 public void run(Void... data) {
992 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
993 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700994
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700995 public void post(Void... data) {
996 }
997 })
998 : 0;
999 long durationLayout =
1000 (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation(
1001 view, new ViewOperation<Void>() {
1002 public Void[] pre() {
1003 return null;
1004 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001005
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001006 public void run(Void... data) {
1007 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
1008 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001009
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001010 public void post(Void... data) {
1011 }
1012 }) : 0;
1013 long durationDraw =
Konstantin Lopyrevbef337f2010-08-18 11:31:27 -07001014 (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(
1015 view,
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001016 new ViewOperation<Object>() {
1017 public Object[] pre() {
1018 final DisplayMetrics metrics =
Chet Haasedaf98e92011-01-10 14:10:36 -08001019 (view != null && view.getResources() != null) ?
1020 view.getResources().getDisplayMetrics() : null;
1021 final Bitmap bitmap = metrics != null ?
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001022 Bitmap.createBitmap(metrics.widthPixels,
Chet Haasedaf98e92011-01-10 14:10:36 -08001023 metrics.heightPixels, Bitmap.Config.RGB_565) : null;
1024 final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001025 return new Object[] {
1026 bitmap, canvas
1027 };
1028 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001029
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001030 public void run(Object... data) {
Chet Haasedaf98e92011-01-10 14:10:36 -08001031 if (data[1] != null) {
1032 view.draw((Canvas) data[1]);
1033 }
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001034 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001035
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001036 public void post(Object... data) {
Chet Haasedaf98e92011-01-10 14:10:36 -08001037 if (data[0] != null) {
1038 ((Bitmap) data[0]).recycle();
1039 }
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001040 }
1041 }) : 0;
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001042 out.write(String.valueOf(durationMeasure));
1043 out.write(' ');
1044 out.write(String.valueOf(durationLayout));
1045 out.write(' ');
1046 out.write(String.valueOf(durationDraw));
1047 out.newLine();
1048 if (view instanceof ViewGroup) {
1049 ViewGroup group = (ViewGroup) view;
1050 final int count = group.getChildCount();
1051 for (int i = 0; i < count; i++) {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001052 profileViewAndChildren(group.getChildAt(i), out, false);
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001053 }
1054 }
1055 }
1056
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001057 interface ViewOperation<T> {
1058 T[] pre();
1059 void run(T... data);
1060 void post(T... data);
1061 }
1062
1063 private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
1064 final CountDownLatch latch = new CountDownLatch(1);
1065 final long[] duration = new long[1];
1066
1067 view.post(new Runnable() {
1068 public void run() {
1069 try {
1070 T[] data = operation.pre();
1071 long start = Debug.threadCpuTimeNanos();
1072 operation.run(data);
1073 duration[0] = Debug.threadCpuTimeNanos() - start;
1074 operation.post(data);
1075 } finally {
1076 latch.countDown();
1077 }
1078 }
1079 });
1080
1081 try {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001082 if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
1083 Log.w("View", "Could not complete the profiling of the view " + view);
1084 return -1;
1085 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001086 } catch (InterruptedException e) {
1087 Log.w("View", "Could not complete the profiling of the view " + view);
1088 Thread.currentThread().interrupt();
1089 return -1;
1090 }
1091
1092 return duration[0];
1093 }
1094
Romain Guy223ff5c2010-03-02 17:07:47 -08001095 private static void captureLayers(View root, final DataOutputStream clientStream)
1096 throws IOException {
1097
1098 try {
1099 Rect outRect = new Rect();
1100 try {
1101 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
1102 } catch (RemoteException e) {
1103 // Ignore
1104 }
1105
1106 clientStream.writeInt(outRect.width());
1107 clientStream.writeInt(outRect.height());
1108
Romain Guy65554f22010-03-22 18:58:21 -07001109 captureViewLayer(root, clientStream, true);
Romain Guy223ff5c2010-03-02 17:07:47 -08001110
1111 clientStream.write(2);
1112 } finally {
1113 clientStream.close();
1114 }
1115 }
1116
Romain Guy65554f22010-03-22 18:58:21 -07001117 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -08001118 throws IOException {
1119
Romain Guy65554f22010-03-22 18:58:21 -07001120 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
1121
Romain Guy223ff5c2010-03-02 17:07:47 -08001122 if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) {
1123 final int id = view.getId();
1124 String name = view.getClass().getSimpleName();
1125 if (id != View.NO_ID) {
1126 name = resolveId(view.getContext(), id).toString();
1127 }
1128
1129 clientStream.write(1);
1130 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -07001131 clientStream.writeByte(localVisible ? 1 : 0);
Romain Guy223ff5c2010-03-02 17:07:47 -08001132
1133 int[] position = new int[2];
1134 // XXX: Should happen on the UI thread
1135 view.getLocationInWindow(position);
1136
1137 clientStream.writeInt(position[0]);
1138 clientStream.writeInt(position[1]);
1139 clientStream.flush();
1140
1141 Bitmap b = performViewCapture(view, true);
1142 if (b != null) {
1143 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
1144 b.getHeight() * 2);
1145 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
1146 clientStream.writeInt(arrayOut.size());
1147 arrayOut.writeTo(clientStream);
1148 }
1149 clientStream.flush();
1150 }
1151
1152 if (view instanceof ViewGroup) {
1153 ViewGroup group = (ViewGroup) view;
1154 int count = group.getChildCount();
1155
1156 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -07001157 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -08001158 }
1159 }
1160 }
1161
Chet Haaseed30fd82011-04-22 16:18:45 -07001162 private static void outputDisplayList(View root, String parameter) throws IOException {
1163 final View view = findView(root, parameter);
1164 view.getViewAncestor().outputDisplayList(view);
1165 }
1166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001167 private static void capture(View root, final OutputStream clientStream, String parameter)
1168 throws IOException {
1169
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001170 final View captureView = findView(root, parameter);
Romain Guy223ff5c2010-03-02 17:07:47 -08001171 Bitmap b = performViewCapture(captureView, false);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001172
1173 if (b == null) {
Romain Guy223ff5c2010-03-02 17:07:47 -08001174 Log.w("View", "Failed to create capture bitmap!");
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001175 // Send an empty one so that it doesn't get stuck waiting for
1176 // something.
1177 b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
1178 }
1179
1180 BufferedOutputStream out = null;
1181 try {
1182 out = new BufferedOutputStream(clientStream, 32 * 1024);
1183 b.compress(Bitmap.CompressFormat.PNG, 100, out);
1184 out.flush();
1185 } finally {
1186 if (out != null) {
1187 out.close();
1188 }
1189 b.recycle();
Romain Guy223ff5c2010-03-02 17:07:47 -08001190 }
1191 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192
Romain Guy223ff5c2010-03-02 17:07:47 -08001193 private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001195 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 final Bitmap[] cache = new Bitmap[1];
1197
Romain Guy223ff5c2010-03-02 17:07:47 -08001198 captureView.post(new Runnable() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 public void run() {
1200 try {
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001201 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001202 Bitmap.Config.ARGB_8888, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001203 } catch (OutOfMemoryError e) {
1204 try {
1205 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001206 Bitmap.Config.ARGB_4444, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001207 } catch (OutOfMemoryError e2) {
1208 Log.w("View", "Out of memory for bitmap");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001210 } finally {
1211 latch.countDown();
1212 }
1213 }
1214 });
1215
1216 try {
1217 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -08001218 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001219 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001220 Log.w("View", "Could not complete the capture of the view " + captureView);
1221 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 }
1223 }
Romain Guy223ff5c2010-03-02 17:07:47 -08001224
1225 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226 }
1227
1228 private static void dump(View root, OutputStream clientStream) throws IOException {
1229 BufferedWriter out = null;
1230 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001231 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 View view = root.getRootView();
1233 if (view instanceof ViewGroup) {
1234 ViewGroup group = (ViewGroup) view;
The Android Open Source Project10592532009-03-18 17:39:46 -07001235 dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 }
1237 out.write("DONE.");
1238 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001239 } catch (Exception e) {
1240 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001241 } finally {
1242 if (out != null) {
1243 out.close();
1244 }
1245 }
1246 }
1247
1248 private static View findView(ViewGroup group, String className, int hashCode) {
1249 if (isRequestedView(group, className, hashCode)) {
1250 return group;
1251 }
1252
1253 final int count = group.getChildCount();
1254 for (int i = 0; i < count; i++) {
1255 final View view = group.getChildAt(i);
1256 if (view instanceof ViewGroup) {
1257 final View found = findView((ViewGroup) view, className, hashCode);
1258 if (found != null) {
1259 return found;
1260 }
1261 } else if (isRequestedView(view, className, hashCode)) {
1262 return view;
1263 }
1264 }
1265
1266 return null;
1267 }
1268
1269 private static boolean isRequestedView(View view, String className, int hashCode) {
1270 return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
1271 }
1272
The Android Open Source Project10592532009-03-18 17:39:46 -07001273 private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 BufferedWriter out, int level) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001275 if (!dumpViewWithProperties(context, group, out, level)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001276 return;
1277 }
1278
1279 final int count = group.getChildCount();
1280 for (int i = 0; i < count; i++) {
1281 final View view = group.getChildAt(i);
1282 if (view instanceof ViewGroup) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001283 dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001284 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -07001285 dumpViewWithProperties(context, view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286 }
1287 }
1288 }
1289
The Android Open Source Project10592532009-03-18 17:39:46 -07001290 private static boolean dumpViewWithProperties(Context context, View view,
1291 BufferedWriter out, int level) {
1292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001293 try {
1294 for (int i = 0; i < level; i++) {
1295 out.write(' ');
1296 }
1297 out.write(view.getClass().getName());
1298 out.write('@');
1299 out.write(Integer.toHexString(view.hashCode()));
1300 out.write(' ');
The Android Open Source Project10592532009-03-18 17:39:46 -07001301 dumpViewProperties(context, view, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001302 out.newLine();
1303 } catch (IOException e) {
1304 Log.w("View", "Error while dumping hierarchy tree");
1305 return false;
1306 }
1307 return true;
1308 }
1309
1310 private static Field[] getExportedPropertyFields(Class<?> klass) {
1311 if (sFieldsForClasses == null) {
1312 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
1313 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001314 if (sAnnotations == null) {
1315 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1316 }
1317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001319 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001320
1321 Field[] fields = map.get(klass);
1322 if (fields != null) {
1323 return fields;
1324 }
1325
1326 final ArrayList<Field> foundFields = new ArrayList<Field>();
1327 fields = klass.getDeclaredFields();
1328
1329 int count = fields.length;
1330 for (int i = 0; i < count; i++) {
1331 final Field field = fields[i];
1332 if (field.isAnnotationPresent(ExportedProperty.class)) {
1333 field.setAccessible(true);
1334 foundFields.add(field);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001335 annotations.put(field, field.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001336 }
1337 }
1338
1339 fields = foundFields.toArray(new Field[foundFields.size()]);
1340 map.put(klass, fields);
1341
1342 return fields;
1343 }
1344
1345 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1346 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001347 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001348 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001349 if (sAnnotations == null) {
1350 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1351 }
1352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001353 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001354 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355
1356 Method[] methods = map.get(klass);
1357 if (methods != null) {
1358 return methods;
1359 }
1360
1361 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1362 methods = klass.getDeclaredMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001363
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364 int count = methods.length;
1365 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001366 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001367 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001368 method.isAnnotationPresent(ExportedProperty.class) &&
1369 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 method.setAccessible(true);
1371 foundMethods.add(method);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001372 annotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 }
1374 }
1375
1376 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1377 map.put(klass, methods);
1378
1379 return methods;
1380 }
1381
The Android Open Source Project10592532009-03-18 17:39:46 -07001382 private static void dumpViewProperties(Context context, Object view,
1383 BufferedWriter out) throws IOException {
1384
1385 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001386 }
1387
The Android Open Source Project10592532009-03-18 17:39:46 -07001388 private static void dumpViewProperties(Context context, Object view,
1389 BufferedWriter out, String prefix) throws IOException {
1390
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 Class<?> klass = view.getClass();
1392
1393 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001394 exportFields(context, view, out, klass, prefix);
1395 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001396 klass = klass.getSuperclass();
1397 } while (klass != Object.class);
1398 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001399
The Android Open Source Project10592532009-03-18 17:39:46 -07001400 private static void exportMethods(Context context, Object view, BufferedWriter out,
1401 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001402
1403 final Method[] methods = getExportedPropertyMethods(klass);
1404
1405 int count = methods.length;
1406 for (int i = 0; i < count; i++) {
1407 final Method method = methods[i];
1408 //noinspection EmptyCatchBlock
1409 try {
1410 // TODO: This should happen on the UI thread
1411 Object methodValue = method.invoke(view, (Object[]) null);
1412 final Class<?> returnType = method.getReturnType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001413 final ExportedProperty property = sAnnotations.get(method);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001414 String categoryPrefix =
1415 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416
1417 if (returnType == int.class) {
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001418
The Android Open Source Project10592532009-03-18 17:39:46 -07001419 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001421 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001422 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001423 final FlagToString[] flagsMapping = property.flagMapping();
1424 if (flagsMapping.length > 0) {
1425 final int intValue = (Integer) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001426 final String valuePrefix =
1427 categoryPrefix + prefix + method.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001428 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1429 }
1430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001431 final IntToString[] mapping = property.mapping();
1432 if (mapping.length > 0) {
1433 final int intValue = (Integer) methodValue;
1434 boolean mapped = false;
1435 int mappingCount = mapping.length;
1436 for (int j = 0; j < mappingCount; j++) {
1437 final IntToString mapper = mapping[j];
1438 if (mapper.from() == intValue) {
1439 methodValue = mapper.to();
1440 mapped = true;
1441 break;
1442 }
1443 }
1444
1445 if (!mapped) {
1446 methodValue = intValue;
1447 }
1448 }
1449 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001450 } else if (returnType == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001451 final int[] array = (int[]) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001452 final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001453 final String suffix = "()";
1454
The Android Open Source Project10592532009-03-18 17:39:46 -07001455 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001456
1457 // Probably want to return here, same as for fields.
1458 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001459 } else if (!returnType.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001461 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 continue;
1463 }
1464 }
1465
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001466 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001467 } catch (IllegalAccessException e) {
1468 } catch (InvocationTargetException e) {
1469 }
1470 }
1471 }
1472
The Android Open Source Project10592532009-03-18 17:39:46 -07001473 private static void exportFields(Context context, Object view, BufferedWriter out,
1474 Class<?> klass, String prefix) throws IOException {
1475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001476 final Field[] fields = getExportedPropertyFields(klass);
1477
1478 int count = fields.length;
1479 for (int i = 0; i < count; i++) {
1480 final Field field = fields[i];
1481
1482 //noinspection EmptyCatchBlock
1483 try {
1484 Object fieldValue = null;
1485 final Class<?> type = field.getType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001486 final ExportedProperty property = sAnnotations.get(field);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001487 String categoryPrefix =
1488 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489
1490 if (type == int.class) {
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001491
The Android Open Source Project10592532009-03-18 17:39:46 -07001492 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001493 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001494 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001496 final FlagToString[] flagsMapping = property.flagMapping();
1497 if (flagsMapping.length > 0) {
1498 final int intValue = field.getInt(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001499 final String valuePrefix =
1500 categoryPrefix + prefix + field.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001501 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1502 }
1503
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001504 final IntToString[] mapping = property.mapping();
1505 if (mapping.length > 0) {
1506 final int intValue = field.getInt(view);
1507 int mappingCount = mapping.length;
1508 for (int j = 0; j < mappingCount; j++) {
1509 final IntToString mapped = mapping[j];
1510 if (mapped.from() == intValue) {
1511 fieldValue = mapped.to();
1512 break;
1513 }
1514 }
1515
1516 if (fieldValue == null) {
1517 fieldValue = intValue;
1518 }
1519 }
1520 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001521 } else if (type == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001522 final int[] array = (int[]) field.get(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001523 final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001524 final String suffix = "";
1525
The Android Open Source Project10592532009-03-18 17:39:46 -07001526 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001527
1528 // We exit here!
1529 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 } else if (!type.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001531 if (property.deepExport()) {
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001532 dumpViewProperties(context, field.get(view), out, prefix
1533 + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001534 continue;
1535 }
1536 }
1537
1538 if (fieldValue == null) {
1539 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001540 }
1541
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001542 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001543 } catch (IllegalAccessException e) {
1544 }
1545 }
1546 }
1547
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001548 private static void writeEntry(BufferedWriter out, String prefix, String name,
1549 String suffix, Object value) throws IOException {
1550
1551 out.write(prefix);
1552 out.write(name);
1553 out.write(suffix);
1554 out.write("=");
1555 writeValue(out, value);
1556 out.write(' ');
1557 }
1558
Romain Guy809a7f62009-05-14 15:44:42 -07001559 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1560 int intValue, String prefix) throws IOException {
1561
1562 final int count = mapping.length;
1563 for (int j = 0; j < count; j++) {
1564 final FlagToString flagMapping = mapping[j];
1565 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001566 final int maskResult = intValue & flagMapping.mask();
1567 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001568 if ((test && ifTrue) || (!test && !ifTrue)) {
1569 final String name = flagMapping.name();
Romain Guy5bcdff42009-05-14 21:27:18 -07001570 final String value = "0x" + Integer.toHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001571 writeEntry(out, prefix, name, "", value);
1572 }
1573 }
1574 }
1575
The Android Open Source Project10592532009-03-18 17:39:46 -07001576 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001577 ExportedProperty property, int[] array, String prefix, String suffix)
1578 throws IOException {
1579
1580 final IntToString[] indexMapping = property.indexMapping();
1581 final boolean hasIndexMapping = indexMapping.length > 0;
1582
1583 final IntToString[] mapping = property.mapping();
1584 final boolean hasMapping = mapping.length > 0;
1585
The Android Open Source Project10592532009-03-18 17:39:46 -07001586 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001587 final int valuesCount = array.length;
1588
1589 for (int j = 0; j < valuesCount; j++) {
1590 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001591 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001592
1593 final int intValue = array[j];
1594
1595 name = String.valueOf(j);
1596 if (hasIndexMapping) {
1597 int mappingCount = indexMapping.length;
1598 for (int k = 0; k < mappingCount; k++) {
1599 final IntToString mapped = indexMapping[k];
1600 if (mapped.from() == j) {
1601 name = mapped.to();
1602 break;
1603 }
1604 }
1605 }
1606
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001607 if (hasMapping) {
1608 int mappingCount = mapping.length;
1609 for (int k = 0; k < mappingCount; k++) {
1610 final IntToString mapped = mapping[k];
1611 if (mapped.from() == intValue) {
1612 value = mapped.to();
1613 break;
1614 }
1615 }
1616 }
1617
1618 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001619 if (value == null) value = (String) resolveId(context, intValue);
1620 } else {
1621 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001622 }
1623
1624 writeEntry(out, prefix, name, suffix, value);
1625 }
1626 }
1627
Romain Guy237c1ce2009-12-08 11:30:25 -08001628 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001629 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001630 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001631 if (id >= 0) {
1632 try {
1633 fieldValue = resources.getResourceTypeName(id) + '/' +
1634 resources.getResourceEntryName(id);
1635 } catch (Resources.NotFoundException e) {
1636 fieldValue = "id/0x" + Integer.toHexString(id);
1637 }
1638 } else {
1639 fieldValue = "NO_ID";
1640 }
1641 return fieldValue;
1642 }
1643
1644 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1645 if (value != null) {
1646 String output = value.toString().replace("\n", "\\n");
1647 out.write(String.valueOf(output.length()));
1648 out.write(",");
1649 out.write(output);
1650 } else {
1651 out.write("4,null");
1652 }
1653 }
1654
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
1656 if (!dumpView(group, out, level)) {
1657 return;
1658 }
1659
1660 final int count = group.getChildCount();
1661 for (int i = 0; i < count; i++) {
1662 final View view = group.getChildAt(i);
1663 if (view instanceof ViewGroup) {
1664 dumpViewHierarchy((ViewGroup) view, out, level + 1);
1665 } else {
1666 dumpView(view, out, level + 1);
1667 }
1668 }
1669 }
1670
1671 private static boolean dumpView(Object view, BufferedWriter out, int level) {
1672 try {
1673 for (int i = 0; i < level; i++) {
1674 out.write(' ');
1675 }
1676 out.write(view.getClass().getName());
1677 out.write('@');
1678 out.write(Integer.toHexString(view.hashCode()));
1679 out.newLine();
1680 } catch (IOException e) {
1681 Log.w("View", "Error while dumping hierarchy tree");
1682 return false;
1683 }
1684 return true;
1685 }
1686
1687 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1688 if (mCapturedViewFieldsForClasses == null) {
1689 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1690 }
1691 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1692
1693 Field[] fields = map.get(klass);
1694 if (fields != null) {
1695 return fields;
1696 }
1697
1698 final ArrayList<Field> foundFields = new ArrayList<Field>();
1699 fields = klass.getFields();
1700
1701 int count = fields.length;
1702 for (int i = 0; i < count; i++) {
1703 final Field field = fields[i];
1704 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1705 field.setAccessible(true);
1706 foundFields.add(field);
1707 }
1708 }
1709
1710 fields = foundFields.toArray(new Field[foundFields.size()]);
1711 map.put(klass, fields);
1712
1713 return fields;
1714 }
1715
1716 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1717 if (mCapturedViewMethodsForClasses == null) {
1718 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1719 }
1720 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1721
1722 Method[] methods = map.get(klass);
1723 if (methods != null) {
1724 return methods;
1725 }
1726
1727 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1728 methods = klass.getMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001729
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001730 int count = methods.length;
1731 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001732 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001733 if (method.getParameterTypes().length == 0 &&
1734 method.isAnnotationPresent(CapturedViewProperty.class) &&
1735 method.getReturnType() != Void.class) {
1736 method.setAccessible(true);
1737 foundMethods.add(method);
1738 }
1739 }
1740
1741 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1742 map.put(klass, methods);
1743
1744 return methods;
1745 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001746
1747 private static String capturedViewExportMethods(Object obj, Class<?> klass,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001748 String prefix) {
1749
1750 if (obj == null) {
1751 return "null";
1752 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001753
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001754 StringBuilder sb = new StringBuilder();
1755 final Method[] methods = capturedViewGetPropertyMethods(klass);
1756
1757 int count = methods.length;
1758 for (int i = 0; i < count; i++) {
1759 final Method method = methods[i];
1760 try {
1761 Object methodValue = method.invoke(obj, (Object[]) null);
1762 final Class<?> returnType = method.getReturnType();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001763
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001764 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1765 if (property.retrieveReturn()) {
1766 //we are interested in the second level data only
1767 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001768 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001769 sb.append(prefix);
1770 sb.append(method.getName());
1771 sb.append("()=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001772
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001773 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001774 final String value = methodValue.toString().replace("\n", "\\n");
1775 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776 } else {
1777 sb.append("null");
1778 }
1779 sb.append("; ");
1780 }
1781 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001782 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001783 //we simply ignore this method
1784 } catch (InvocationTargetException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001785 //Exception InvocationTarget, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001786 //we simply ignore this method
Romain Guya1f3e4a2009-06-04 15:10:46 -07001787 }
1788 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001789 return sb.toString();
1790 }
1791
1792 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001794 if (obj == null) {
1795 return "null";
1796 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001797
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001798 StringBuilder sb = new StringBuilder();
1799 final Field[] fields = capturedViewGetPropertyFields(klass);
1800
1801 int count = fields.length;
1802 for (int i = 0; i < count; i++) {
1803 final Field field = fields[i];
1804 try {
1805 Object fieldValue = field.get(obj);
1806
1807 sb.append(prefix);
1808 sb.append(field.getName());
1809 sb.append("=");
1810
1811 if (fieldValue != null) {
1812 final String value = fieldValue.toString().replace("\n", "\\n");
1813 sb.append(value);
1814 } else {
1815 sb.append("null");
1816 }
1817 sb.append(' ');
1818 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001819 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001820 //we simply ignore this field
1821 }
1822 }
1823 return sb.toString();
1824 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001825
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001826 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001827 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001828 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001829 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830 * @param tag for log
1831 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001832 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001833 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 Class<?> klass = view.getClass();
1835 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1836 sb.append(capturedViewExportFields(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001837 sb.append(capturedViewExportMethods(view, klass, ""));
1838 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001839 }
1840}