blob: c19a107ec749d381abda8338cb6c08f5dc99fb48 [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
Romain Guye551dc72009-07-28 15:30:11 -070019import android.util.Config;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.util.Log;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070021import android.util.DisplayMetrics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.res.Resources;
The Android Open Source Project10592532009-03-18 17:39:46 -070023import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.graphics.Bitmap;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070025import android.graphics.Canvas;
Romain Guy223ff5c2010-03-02 17:07:47 -080026import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Environment;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070028import android.os.Debug;
Romain Guy223ff5c2010-03-02 17:07:47 -080029import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
Romain Guy223ff5c2010-03-02 17:07:47 -080031import java.io.ByteArrayOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import java.io.File;
33import java.io.BufferedWriter;
34import java.io.FileWriter;
35import java.io.IOException;
36import java.io.FileOutputStream;
37import java.io.DataOutputStream;
38import java.io.OutputStreamWriter;
39import java.io.BufferedOutputStream;
40import java.io.OutputStream;
41import java.util.List;
42import java.util.LinkedList;
43import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.concurrent.CountDownLatch;
46import java.util.concurrent.TimeUnit;
47import java.lang.annotation.Target;
48import java.lang.annotation.ElementType;
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51import java.lang.reflect.Field;
52import java.lang.reflect.Method;
53import java.lang.reflect.InvocationTargetException;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070054import java.lang.reflect.AccessibleObject;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56/**
57 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
58 */
59public class ViewDebug {
60 /**
Romain Guy13922e02009-05-12 17:56:14 -070061 * Log tag used to log errors related to the consistency of the view hierarchy.
62 *
63 * @hide
64 */
65 public static final String CONSISTENCY_LOG_TAG = "ViewConsistency";
66
67 /**
68 * Flag indicating the consistency check should check layout-related properties.
69 *
70 * @hide
71 */
72 public static final int CONSISTENCY_LAYOUT = 0x1;
73
74 /**
75 * Flag indicating the consistency check should check drawing-related properties.
76 *
77 * @hide
78 */
79 public static final int CONSISTENCY_DRAWING = 0x2;
80
81 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 * Enables or disables view hierarchy tracing. Any invoker of
83 * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
84 * check that this value is set to true as not to affect performance.
85 */
86 public static final boolean TRACE_HIERARCHY = false;
87
88 /**
89 * Enables or disables view recycler tracing. Any invoker of
90 * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
91 * check that this value is set to true as not to affect performance.
92 */
93 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 /**
Romain Guycf635ae2010-01-22 11:00:29 -080096 * Enables or disables motion events tracing. Any invoker of
97 * {@link #trace(View, MotionEvent, MotionEventTraceType)} should first check
98 * that this value is set to true as not to affect performance.
99 *
100 * @hide
101 */
102 public static final boolean TRACE_MOTION_EVENTS = false;
103
104 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 * The system property of dynamic switch for capturing view information
106 * when it is set, we dump interested fields and methods for the view on focus
Romain Guya1f3e4a2009-06-04 15:10:46 -0700107 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
Romain Guya1f3e4a2009-06-04 15:10:46 -0700109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 /**
111 * The system property of dynamic switch for capturing event information
112 * when it is set, we log key events, touch/motion and trackball events
Romain Guya1f3e4a2009-06-04 15:10:46 -0700113 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 /**
Romain Guy13922e02009-05-12 17:56:14 -0700117 * Profiles drawing times in the events log.
118 *
119 * @hide
120 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700121 public static final boolean DEBUG_PROFILE_DRAWING = false;
Romain Guy13922e02009-05-12 17:56:14 -0700122
123 /**
124 * Profiles layout times in the events log.
125 *
126 * @hide
127 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700128 public static final boolean DEBUG_PROFILE_LAYOUT = false;
Romain Guy13922e02009-05-12 17:56:14 -0700129
130 /**
131 * Profiles real fps (times between draws) and displays the result.
132 *
133 * @hide
134 */
Romain Guy5429e1d2010-09-07 12:38:00 -0700135 public static final boolean DEBUG_SHOW_FPS = false;
Romain Guy13922e02009-05-12 17:56:14 -0700136
137 /**
Christopher Tate2c095f32010-10-04 14:13:40 -0700138 * Enables detailed logging of drag/drop operations.
139 * @hide
140 */
Christopher Tate994ef922011-01-12 20:06:07 -0800141 public static final boolean DEBUG_DRAG = false;
Christopher Tate2c095f32010-10-04 14:13:40 -0700142
143 /**
Romain Guy13922e02009-05-12 17:56:14 -0700144 * <p>Enables or disables views consistency check. Even when this property is enabled,
145 * view consistency checks happen only if {@link android.util.Config#DEBUG} is set
146 * to true. The value of this property can be configured externally in one of the
147 * following files:</p>
148 * <ul>
149 * <li>/system/debug.prop</li>
150 * <li>/debug.prop</li>
151 * <li>/data/debug.prop</li>
152 * </ul>
153 * @hide
154 */
155 @Debug.DebugProperty
156 public static boolean consistencyCheckEnabled = false;
157
158 static {
Romain Guye551dc72009-07-28 15:30:11 -0700159 if (Config.DEBUG) {
160 Debug.setFieldsOn(ViewDebug.class, true);
161 }
Romain Guy13922e02009-05-12 17:56:14 -0700162 }
163
164 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 * This annotation can be used to mark fields and methods to be dumped by
166 * the view server. Only non-void methods with no arguments can be annotated
167 * by this annotation.
168 */
169 @Target({ ElementType.FIELD, ElementType.METHOD })
170 @Retention(RetentionPolicy.RUNTIME)
171 public @interface ExportedProperty {
172 /**
173 * When resolveId is true, and if the annotated field/method return value
174 * is an int, the value is converted to an Android's resource name.
175 *
176 * @return true if the property's value must be transformed into an Android
177 * resource name, false otherwise
178 */
179 boolean resolveId() default false;
180
181 /**
182 * A mapping can be defined to map int values to specific strings. For
183 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
184 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
185 * these human readable values:
186 *
187 * <pre>
188 * @ViewDebug.ExportedProperty(mapping = {
189 * @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
190 * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
191 * @ViewDebug.IntToString(from = 8, to = "GONE")
192 * })
193 * public int getVisibility() { ...
194 * <pre>
195 *
196 * @return An array of int to String mappings
197 *
198 * @see android.view.ViewDebug.IntToString
199 */
200 IntToString[] mapping() default { };
201
202 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700203 * A mapping can be defined to map array indices to specific strings.
204 * A mapping can be used to see human readable values for the indices
205 * of an array:
206 *
207 * <pre>
Romain Guy809a7f62009-05-14 15:44:42 -0700208 * @ViewDebug.ExportedProperty(indexMapping = {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700209 * @ViewDebug.IntToString(from = 0, to = "INVALID"),
210 * @ViewDebug.IntToString(from = 1, to = "FIRST"),
211 * @ViewDebug.IntToString(from = 2, to = "SECOND")
212 * })
213 * private int[] mElements;
214 * <pre>
215 *
216 * @return An array of int to String mappings
217 *
218 * @see android.view.ViewDebug.IntToString
219 * @see #mapping()
220 */
221 IntToString[] indexMapping() default { };
222
223 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700224 * A flags mapping can be defined to map flags encoded in an integer to
225 * specific strings. A mapping can be used to see human readable values
226 * for the flags of an integer:
227 *
228 * <pre>
229 * @ViewDebug.ExportedProperty(flagMapping = {
230 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"),
231 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"),
232 * })
233 * private int mFlags;
234 * <pre>
235 *
236 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700237 *
Romain Guy809a7f62009-05-14 15:44:42 -0700238 * @return An array of int to String mappings
239 */
240 FlagToString[] flagMapping() default { };
241
242 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 * When deep export is turned on, this property is not dumped. Instead, the
244 * properties contained in this property are dumped. Each child property
245 * is prefixed with the name of this property.
246 *
247 * @return true if the properties of this property should be dumped
248 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700249 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 */
251 boolean deepExport() default false;
252
253 /**
254 * The prefix to use on child properties when deep export is enabled
255 *
256 * @return a prefix as a String
257 *
258 * @see #deepExport()
259 */
260 String prefix() default "";
Konstantin Lopyrevbea95162010-08-10 17:02:18 -0700261
262 /**
263 * Specifies the category the property falls into, such as measurement,
264 * layout, drawing, etc.
265 *
266 * @return the category as String
267 */
268 String category() default "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 }
270
271 /**
272 * Defines a mapping from an int value to a String. Such a mapping can be used
273 * in a @ExportedProperty to provide more meaningful values to the end user.
274 *
275 * @see android.view.ViewDebug.ExportedProperty
276 */
277 @Target({ ElementType.TYPE })
278 @Retention(RetentionPolicy.RUNTIME)
279 public @interface IntToString {
280 /**
281 * The original int value to map to a String.
282 *
283 * @return An arbitrary int value.
284 */
285 int from();
286
287 /**
288 * The String to use in place of the original int value.
289 *
290 * @return An arbitrary non-null String.
291 */
292 String to();
293 }
Romain Guy809a7f62009-05-14 15:44:42 -0700294
295 /**
296 * Defines a mapping from an flag to a String. Such a mapping can be used
297 * in a @ExportedProperty to provide more meaningful values to the end user.
298 *
299 * @see android.view.ViewDebug.ExportedProperty
300 */
301 @Target({ ElementType.TYPE })
302 @Retention(RetentionPolicy.RUNTIME)
303 public @interface FlagToString {
304 /**
305 * The mask to apply to the original value.
306 *
307 * @return An arbitrary int value.
308 */
309 int mask();
310
311 /**
312 * The value to compare to the result of:
313 * <code>original value &amp; {@link #mask()}</code>.
314 *
315 * @return An arbitrary value.
316 */
317 int equals();
318
319 /**
320 * The String to use in place of the original int value.
321 *
322 * @return An arbitrary non-null String.
323 */
324 String name();
325
326 /**
327 * Indicates whether to output the flag when the test is true,
328 * or false. Defaults to true.
329 */
330 boolean outputIf() default true;
331 }
332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 /**
334 * This annotation can be used to mark fields and methods to be dumped when
335 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700336 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 */
338 @Target({ ElementType.FIELD, ElementType.METHOD })
339 @Retention(RetentionPolicy.RUNTIME)
340 public @interface CapturedViewProperty {
341 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700342 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700344 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700346 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700348 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700350
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800351 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
352 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
353
354 // Maximum delay in ms after which we stop trying to capture a View's drawing
355 private static final int CAPTURE_TIMEOUT = 4000;
356
357 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
358 private static final String REMOTE_COMMAND_DUMP = "DUMP";
359 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
360 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700361 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800362 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363
364 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
365 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700366 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 /**
369 * Defines the type of hierarhcy trace to output to the hierarchy traces file.
370 */
371 public enum HierarchyTraceType {
372 INVALIDATE,
373 INVALIDATE_CHILD,
374 INVALIDATE_CHILD_IN_PARENT,
375 REQUEST_LAYOUT,
376 ON_LAYOUT,
377 ON_MEASURE,
378 DRAW,
379 BUILD_CACHE
380 }
381
382 private static BufferedWriter sHierarchyTraces;
383 private static ViewRoot sHierarhcyRoot;
384 private static String sHierarchyTracePrefix;
385
386 /**
387 * Defines the type of recycler trace to output to the recycler traces file.
388 */
389 public enum RecyclerTraceType {
390 NEW_VIEW,
391 BIND_VIEW,
392 RECYCLE_FROM_ACTIVE_HEAP,
393 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 MOVE_TO_SCRAP_HEAP,
395 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
396 }
397
398 private static class RecyclerTrace {
399 public int view;
400 public RecyclerTraceType type;
401 public int position;
402 public int indexOnScreen;
403 }
404
405 private static View sRecyclerOwnerView;
406 private static List<View> sRecyclerViews;
407 private static List<RecyclerTrace> sRecyclerTraces;
408 private static String sRecyclerTracePrefix;
409
410 /**
Romain Guycf635ae2010-01-22 11:00:29 -0800411 * Defines the type of motion events trace to output to the motion events traces file.
412 *
413 * @hide
414 */
415 public enum MotionEventTraceType {
416 DISPATCH,
417 ON_INTERCEPT,
418 ON_TOUCH
419 }
420
421 private static BufferedWriter sMotionEventTraces;
422 private static ViewRoot sMotionEventRoot;
423 private static String sMotionEventTracePrefix;
424
425 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 * Returns the number of instanciated Views.
427 *
428 * @return The number of Views instanciated in the current process.
429 *
430 * @hide
431 */
432 public static long getViewInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700433 return Debug.countInstancesOfClass(View.class);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 }
435
436 /**
437 * Returns the number of instanciated ViewRoots.
438 *
439 * @return The number of ViewRoots instanciated in the current process.
440 *
441 * @hide
442 */
443 public static long getViewRootInstanceCount() {
Brian Carlstromc21550a2010-10-05 21:34:06 -0700444 return Debug.countInstancesOfClass(ViewRoot.class);
Romain Guya1f3e4a2009-06-04 15:10:46 -0700445 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446
447 /**
448 * Outputs a trace to the currently opened recycler traces. The trace records the type of
449 * recycler action performed on the supplied view as well as a number of parameters.
450 *
451 * @param view the view to trace
452 * @param type the type of the trace
453 * @param parameters parameters depending on the type of the trace
454 */
455 public static void trace(View view, RecyclerTraceType type, int... parameters) {
456 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
457 return;
458 }
459
460 if (!sRecyclerViews.contains(view)) {
461 sRecyclerViews.add(view);
462 }
463
464 final int index = sRecyclerViews.indexOf(view);
465
466 RecyclerTrace trace = new RecyclerTrace();
467 trace.view = index;
468 trace.type = type;
469 trace.position = parameters[0];
470 trace.indexOnScreen = parameters[1];
471
472 sRecyclerTraces.add(trace);
473 }
474
475 /**
476 * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
477 * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
478 * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
479 *
480 * Only one view recycler can be traced at the same time. After calling this method, any
481 * other invocation will result in a <code>IllegalStateException</code> unless
482 * {@link #stopRecyclerTracing()} is invoked before.
483 *
484 * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
485 *
486 * This method will return immediately if TRACE_RECYCLER is false.
487 *
488 * @param prefix the traces files name prefix
489 * @param view the view whose recycler must be traced
490 *
491 * @see #stopRecyclerTracing()
492 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
493 */
494 public static void startRecyclerTracing(String prefix, View view) {
495 //noinspection PointlessBooleanExpression,ConstantConditions
496 if (!TRACE_RECYCLER) {
497 return;
498 }
499
500 if (sRecyclerOwnerView != null) {
501 throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
502 " a new trace!");
503 }
504
505 sRecyclerTracePrefix = prefix;
506 sRecyclerOwnerView = view;
507 sRecyclerViews = new ArrayList<View>();
508 sRecyclerTraces = new LinkedList<RecyclerTrace>();
509 }
510
511 /**
512 * Stops the current view recycer tracing.
513 *
514 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
515 * containing all the traces (or method calls) relative to the specified view's recycler.
516 *
517 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
518 * containing all of the views used by the recycler of the view supplied to
519 * {@link #startRecyclerTracing(String, View)}.
520 *
521 * This method will return immediately if TRACE_RECYCLER is false.
522 *
523 * @see #startRecyclerTracing(String, View)
524 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
525 */
526 public static void stopRecyclerTracing() {
527 //noinspection PointlessBooleanExpression,ConstantConditions
528 if (!TRACE_RECYCLER) {
529 return;
530 }
531
532 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
533 throw new IllegalStateException("You must call startRecyclerTracing() before" +
534 " stopRecyclerTracing()!");
535 }
536
537 File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700538 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539 recyclerDump.mkdirs();
540
541 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
542 try {
543 final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
544
545 for (View view : sRecyclerViews) {
546 final String name = view.getClass().getName();
547 out.write(name);
548 out.newLine();
549 }
550
551 out.close();
552 } catch (IOException e) {
553 Log.e("View", "Could not dump recycler content");
554 return;
555 }
556
557 recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
558 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
559 try {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700560 if (recyclerDump.exists()) {
561 recyclerDump.delete();
562 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 final FileOutputStream file = new FileOutputStream(recyclerDump);
564 final DataOutputStream out = new DataOutputStream(file);
565
566 for (RecyclerTrace trace : sRecyclerTraces) {
567 out.writeInt(trace.view);
568 out.writeInt(trace.type.ordinal());
569 out.writeInt(trace.position);
570 out.writeInt(trace.indexOnScreen);
571 out.flush();
572 }
573
574 out.close();
575 } catch (IOException e) {
576 Log.e("View", "Could not dump recycler traces");
577 return;
578 }
579
580 sRecyclerViews.clear();
581 sRecyclerViews = null;
582
583 sRecyclerTraces.clear();
584 sRecyclerTraces = null;
585
586 sRecyclerOwnerView = null;
587 }
588
589 /**
590 * Outputs a trace to the currently opened traces file. The trace contains the class name
591 * and instance's hashcode of the specified view as well as the supplied trace type.
592 *
593 * @param view the view to trace
594 * @param type the type of the trace
595 */
596 public static void trace(View view, HierarchyTraceType type) {
597 if (sHierarchyTraces == null) {
598 return;
599 }
600
601 try {
602 sHierarchyTraces.write(type.name());
603 sHierarchyTraces.write(' ');
604 sHierarchyTraces.write(view.getClass().getName());
605 sHierarchyTraces.write('@');
606 sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
607 sHierarchyTraces.newLine();
608 } catch (IOException e) {
609 Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
610 }
611 }
612
613 /**
614 * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
615 * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
616 * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
617 *
618 * Only one view hierarchy can be traced at the same time. After calling this method, any
619 * other invocation will result in a <code>IllegalStateException</code> unless
620 * {@link #stopHierarchyTracing()} is invoked before.
621 *
622 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
623 * containing all the traces (or method calls) relative to the specified view's hierarchy.
624 *
625 * This method will return immediately if TRACE_HIERARCHY is false.
626 *
627 * @param prefix the traces files name prefix
628 * @param view the view whose hierarchy must be traced
629 *
630 * @see #stopHierarchyTracing()
631 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
632 */
633 public static void startHierarchyTracing(String prefix, View view) {
634 //noinspection PointlessBooleanExpression,ConstantConditions
635 if (!TRACE_HIERARCHY) {
636 return;
637 }
638
639 if (sHierarhcyRoot != null) {
640 throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
641 " a new trace!");
642 }
643
644 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700645 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 hierarchyDump.mkdirs();
647
648 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
649 sHierarchyTracePrefix = prefix;
650
651 try {
652 sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
653 } catch (IOException e) {
654 Log.e("View", "Could not dump view hierarchy");
655 return;
656 }
657
658 sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
659 }
660
661 /**
662 * Stops the current view hierarchy tracing. This method closes the file
663 * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
664 *
665 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
666 * containing the view hierarchy of the view supplied to
667 * {@link #startHierarchyTracing(String, View)}.
668 *
669 * This method will return immediately if TRACE_HIERARCHY is false.
670 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700671 * @see #startHierarchyTracing(String, View)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
673 */
674 public static void stopHierarchyTracing() {
675 //noinspection PointlessBooleanExpression,ConstantConditions
676 if (!TRACE_HIERARCHY) {
677 return;
678 }
679
680 if (sHierarhcyRoot == null || sHierarchyTraces == null) {
681 throw new IllegalStateException("You must call startHierarchyTracing() before" +
682 " stopHierarchyTracing()!");
683 }
684
685 try {
686 sHierarchyTraces.close();
687 } catch (IOException e) {
688 Log.e("View", "Could not write view traces");
689 }
690 sHierarchyTraces = null;
691
692 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700693 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 hierarchyDump.mkdirs();
695 hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
696
697 BufferedWriter out;
698 try {
699 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
700 } catch (IOException e) {
701 Log.e("View", "Could not dump view hierarchy");
702 return;
703 }
704
705 View view = sHierarhcyRoot.getView();
706 if (view instanceof ViewGroup) {
707 ViewGroup group = (ViewGroup) view;
708 dumpViewHierarchy(group, out, 0);
709 try {
710 out.close();
711 } catch (IOException e) {
712 Log.e("View", "Could not dump view hierarchy");
713 }
714 }
715
716 sHierarhcyRoot = null;
717 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700718
Romain Guycf635ae2010-01-22 11:00:29 -0800719 /**
720 * Outputs a trace to the currently opened traces file. The trace contains the class name
721 * and instance's hashcode of the specified view as well as the supplied trace type.
722 *
723 * @param view the view to trace
724 * @param event the event of the trace
725 * @param type the type of the trace
726 *
727 * @hide
728 */
729 public static void trace(View view, MotionEvent event, MotionEventTraceType type) {
730 if (sMotionEventTraces == null) {
731 return;
732 }
733
734 try {
735 sMotionEventTraces.write(type.name());
736 sMotionEventTraces.write(' ');
737 sMotionEventTraces.write(event.getAction());
738 sMotionEventTraces.write(' ');
739 sMotionEventTraces.write(view.getClass().getName());
740 sMotionEventTraces.write('@');
741 sMotionEventTraces.write(Integer.toHexString(view.hashCode()));
742 sHierarchyTraces.newLine();
743 } catch (IOException e) {
744 Log.w("View", "Error while dumping trace of event " + event + " for view " + view);
745 }
746 }
747
748 /**
749 * Starts tracing the motion events for the hierarchy of the specificy view.
750 * The trace is identified by a prefix, used to build the traces files names:
751 * <code>/EXTERNAL/motion-events/PREFIX.traces</code> and
752 * <code>/EXTERNAL/motion-events/PREFIX.tree</code>.
753 *
754 * Only one view hierarchy can be traced at the same time. After calling this method, any
755 * other invocation will result in a <code>IllegalStateException</code> unless
756 * {@link #stopMotionEventTracing()} is invoked before.
757 *
758 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.traces</code>
759 * containing all the traces (or method calls) relative to the specified view's hierarchy.
760 *
761 * This method will return immediately if TRACE_HIERARCHY is false.
762 *
763 * @param prefix the traces files name prefix
764 * @param view the view whose hierarchy must be traced
765 *
766 * @see #stopMotionEventTracing()
767 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
768 *
769 * @hide
770 */
771 public static void startMotionEventTracing(String prefix, View view) {
772 //noinspection PointlessBooleanExpression,ConstantConditions
773 if (!TRACE_MOTION_EVENTS) {
774 return;
775 }
776
777 if (sMotionEventRoot != null) {
778 throw new IllegalStateException("You must call stopMotionEventTracing() before running" +
779 " a new trace!");
780 }
781
782 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
783 //noinspection ResultOfMethodCallIgnored
784 hierarchyDump.mkdirs();
785
786 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
787 sMotionEventTracePrefix = prefix;
788
789 try {
790 sMotionEventTraces = new BufferedWriter(new FileWriter(hierarchyDump), 32 * 1024);
791 } catch (IOException e) {
792 Log.e("View", "Could not dump view hierarchy");
793 return;
794 }
795
796 sMotionEventRoot = (ViewRoot) view.getRootView().getParent();
797 }
798
799 /**
800 * Stops the current motion events tracing. This method closes the file
801 * <code>/EXTERNAL/motion-events/PREFIX.traces</code>.
802 *
803 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.tree</code>
804 * containing the view hierarchy of the view supplied to
805 * {@link #startMotionEventTracing(String, View)}.
806 *
807 * This method will return immediately if TRACE_HIERARCHY is false.
808 *
809 * @see #startMotionEventTracing(String, View)
810 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
811 *
812 * @hide
813 */
814 public static void stopMotionEventTracing() {
815 //noinspection PointlessBooleanExpression,ConstantConditions
816 if (!TRACE_MOTION_EVENTS) {
817 return;
818 }
819
820 if (sMotionEventRoot == null || sMotionEventTraces == null) {
821 throw new IllegalStateException("You must call startMotionEventTracing() before" +
822 " stopMotionEventTracing()!");
823 }
824
825 try {
826 sMotionEventTraces.close();
827 } catch (IOException e) {
828 Log.e("View", "Could not write view traces");
829 }
830 sMotionEventTraces = null;
831
832 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
833 //noinspection ResultOfMethodCallIgnored
834 hierarchyDump.mkdirs();
835 hierarchyDump = new File(hierarchyDump, sMotionEventTracePrefix + ".tree");
836
837 BufferedWriter out;
838 try {
839 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
840 } catch (IOException e) {
841 Log.e("View", "Could not dump view hierarchy");
842 return;
843 }
844
845 View view = sMotionEventRoot.getView();
846 if (view instanceof ViewGroup) {
847 ViewGroup group = (ViewGroup) view;
848 dumpViewHierarchy(group, out, 0);
849 try {
850 out.close();
851 } catch (IOException e) {
852 Log.e("View", "Could not dump view hierarchy");
853 }
854 }
855
856 sHierarhcyRoot = null;
857 }
858
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 static void dispatchCommand(View view, String command, String parameters,
860 OutputStream clientStream) throws IOException {
861
862 // Paranoid but safe...
863 view = view.getRootView();
864
865 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
866 dump(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800867 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
868 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 } else {
870 final String[] params = parameters.split(" ");
871 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
872 capture(view, clientStream, params[0]);
873 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
874 invalidate(view, params[0]);
875 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
876 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700877 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
878 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800879 }
880 }
881 }
882
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700883 private static View findView(View root, String parameter) {
884 // Look by type/hashcode
885 if (parameter.indexOf('@') != -1) {
886 final String[] ids = parameter.split("@");
887 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800888 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800889
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700890 View view = root.getRootView();
891 if (view instanceof ViewGroup) {
892 return findView((ViewGroup) view, className, hashCode);
893 }
894 } else {
895 // Look by id
896 final int id = root.getResources().getIdentifier(parameter, null, null);
897 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 }
899
900 return null;
901 }
902
903 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700904 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 if (view != null) {
906 view.postInvalidate();
907 }
908 }
909
910 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700911 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800912 if (view != null) {
913 root.post(new Runnable() {
914 public void run() {
915 view.requestLayout();
916 }
917 });
918 }
919 }
920
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700921 private static void profile(View root, OutputStream clientStream, String parameter)
922 throws IOException {
923
924 final View view = findView(root, parameter);
925 BufferedWriter out = null;
926 try {
927 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
928
929 if (view != null) {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700930 profileViewAndChildren(view, out);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700931 } else {
932 out.write("-1 -1 -1");
933 out.newLine();
934 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700935 out.write("DONE.");
936 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700937 } catch (Exception e) {
938 android.util.Log.w("View", "Problem profiling the view:", e);
939 } finally {
940 if (out != null) {
941 out.close();
942 }
943 }
944 }
945
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700946 private static void profileViewAndChildren(final View view, BufferedWriter out)
947 throws IOException {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700948 profileViewAndChildren(view, out, true);
949 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700950
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700951 private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
952 throws IOException {
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700953
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700954 long durationMeasure =
955 (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation(
956 view, new ViewOperation<Void>() {
957 public Void[] pre() {
958 forceLayout(view);
959 return null;
960 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700961
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700962 private void forceLayout(View view) {
963 view.forceLayout();
964 if (view instanceof ViewGroup) {
965 ViewGroup group = (ViewGroup) view;
966 final int count = group.getChildCount();
967 for (int i = 0; i < count; i++) {
968 forceLayout(group.getChildAt(i));
969 }
970 }
971 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700972
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700973 public void run(Void... data) {
974 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
975 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700976
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700977 public void post(Void... data) {
978 }
979 })
980 : 0;
981 long durationLayout =
982 (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation(
983 view, new ViewOperation<Void>() {
984 public Void[] pre() {
985 return null;
986 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700987
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700988 public void run(Void... data) {
989 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
990 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -0700991
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700992 public void post(Void... data) {
993 }
994 }) : 0;
995 long durationDraw =
Konstantin Lopyrevbef337f2010-08-18 11:31:27 -0700996 (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(
997 view,
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -0700998 new ViewOperation<Object>() {
999 public Object[] pre() {
1000 final DisplayMetrics metrics =
Chet Haasedaf98e92011-01-10 14:10:36 -08001001 (view != null && view.getResources() != null) ?
1002 view.getResources().getDisplayMetrics() : null;
1003 final Bitmap bitmap = metrics != null ?
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001004 Bitmap.createBitmap(metrics.widthPixels,
Chet Haasedaf98e92011-01-10 14:10:36 -08001005 metrics.heightPixels, Bitmap.Config.RGB_565) : null;
1006 final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001007 return new Object[] {
1008 bitmap, canvas
1009 };
1010 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001011
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001012 public void run(Object... data) {
Chet Haasedaf98e92011-01-10 14:10:36 -08001013 if (data[1] != null) {
1014 view.draw((Canvas) data[1]);
1015 }
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001016 }
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001017
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001018 public void post(Object... data) {
Chet Haasedaf98e92011-01-10 14:10:36 -08001019 if (data[0] != null) {
1020 ((Bitmap) data[0]).recycle();
1021 }
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001022 }
1023 }) : 0;
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001024 out.write(String.valueOf(durationMeasure));
1025 out.write(' ');
1026 out.write(String.valueOf(durationLayout));
1027 out.write(' ');
1028 out.write(String.valueOf(durationDraw));
1029 out.newLine();
1030 if (view instanceof ViewGroup) {
1031 ViewGroup group = (ViewGroup) view;
1032 final int count = group.getChildCount();
1033 for (int i = 0; i < count; i++) {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001034 profileViewAndChildren(group.getChildAt(i), out, false);
Konstantin Lopyrevf8e12192010-08-02 19:08:56 -07001035 }
1036 }
1037 }
1038
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001039 interface ViewOperation<T> {
1040 T[] pre();
1041 void run(T... data);
1042 void post(T... data);
1043 }
1044
1045 private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
1046 final CountDownLatch latch = new CountDownLatch(1);
1047 final long[] duration = new long[1];
1048
1049 view.post(new Runnable() {
1050 public void run() {
1051 try {
1052 T[] data = operation.pre();
1053 long start = Debug.threadCpuTimeNanos();
1054 operation.run(data);
1055 duration[0] = Debug.threadCpuTimeNanos() - start;
1056 operation.post(data);
1057 } finally {
1058 latch.countDown();
1059 }
1060 }
1061 });
1062
1063 try {
Konstantin Lopyrevc6dc4572010-08-06 15:01:52 -07001064 if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
1065 Log.w("View", "Could not complete the profiling of the view " + view);
1066 return -1;
1067 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001068 } catch (InterruptedException e) {
1069 Log.w("View", "Could not complete the profiling of the view " + view);
1070 Thread.currentThread().interrupt();
1071 return -1;
1072 }
1073
1074 return duration[0];
1075 }
1076
Romain Guy223ff5c2010-03-02 17:07:47 -08001077 private static void captureLayers(View root, final DataOutputStream clientStream)
1078 throws IOException {
1079
1080 try {
1081 Rect outRect = new Rect();
1082 try {
1083 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
1084 } catch (RemoteException e) {
1085 // Ignore
1086 }
1087
1088 clientStream.writeInt(outRect.width());
1089 clientStream.writeInt(outRect.height());
1090
Romain Guy65554f22010-03-22 18:58:21 -07001091 captureViewLayer(root, clientStream, true);
Romain Guy223ff5c2010-03-02 17:07:47 -08001092
1093 clientStream.write(2);
1094 } finally {
1095 clientStream.close();
1096 }
1097 }
1098
Romain Guy65554f22010-03-22 18:58:21 -07001099 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -08001100 throws IOException {
1101
Romain Guy65554f22010-03-22 18:58:21 -07001102 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
1103
Romain Guy223ff5c2010-03-02 17:07:47 -08001104 if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) {
1105 final int id = view.getId();
1106 String name = view.getClass().getSimpleName();
1107 if (id != View.NO_ID) {
1108 name = resolveId(view.getContext(), id).toString();
1109 }
1110
1111 clientStream.write(1);
1112 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -07001113 clientStream.writeByte(localVisible ? 1 : 0);
Romain Guy223ff5c2010-03-02 17:07:47 -08001114
1115 int[] position = new int[2];
1116 // XXX: Should happen on the UI thread
1117 view.getLocationInWindow(position);
1118
1119 clientStream.writeInt(position[0]);
1120 clientStream.writeInt(position[1]);
1121 clientStream.flush();
1122
1123 Bitmap b = performViewCapture(view, true);
1124 if (b != null) {
1125 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
1126 b.getHeight() * 2);
1127 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
1128 clientStream.writeInt(arrayOut.size());
1129 arrayOut.writeTo(clientStream);
1130 }
1131 clientStream.flush();
1132 }
1133
1134 if (view instanceof ViewGroup) {
1135 ViewGroup group = (ViewGroup) view;
1136 int count = group.getChildCount();
1137
1138 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -07001139 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -08001140 }
1141 }
1142 }
1143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 private static void capture(View root, final OutputStream clientStream, String parameter)
1145 throws IOException {
1146
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001147 final View captureView = findView(root, parameter);
Romain Guy223ff5c2010-03-02 17:07:47 -08001148 Bitmap b = performViewCapture(captureView, false);
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001149
1150 if (b == null) {
Romain Guy223ff5c2010-03-02 17:07:47 -08001151 Log.w("View", "Failed to create capture bitmap!");
Konstantin Lopyrev43b9b482010-08-24 22:00:12 -07001152 // Send an empty one so that it doesn't get stuck waiting for
1153 // something.
1154 b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
1155 }
1156
1157 BufferedOutputStream out = null;
1158 try {
1159 out = new BufferedOutputStream(clientStream, 32 * 1024);
1160 b.compress(Bitmap.CompressFormat.PNG, 100, out);
1161 out.flush();
1162 } finally {
1163 if (out != null) {
1164 out.close();
1165 }
1166 b.recycle();
Romain Guy223ff5c2010-03-02 17:07:47 -08001167 }
1168 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169
Romain Guy223ff5c2010-03-02 17:07:47 -08001170 private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001171 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001172 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 final Bitmap[] cache = new Bitmap[1];
1174
Romain Guy223ff5c2010-03-02 17:07:47 -08001175 captureView.post(new Runnable() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 public void run() {
1177 try {
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001178 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001179 Bitmap.Config.ARGB_8888, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001180 } catch (OutOfMemoryError e) {
1181 try {
1182 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001183 Bitmap.Config.ARGB_4444, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001184 } catch (OutOfMemoryError e2) {
1185 Log.w("View", "Out of memory for bitmap");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001187 } finally {
1188 latch.countDown();
1189 }
1190 }
1191 });
1192
1193 try {
1194 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -08001195 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001196 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001197 Log.w("View", "Could not complete the capture of the view " + captureView);
1198 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
1200 }
Romain Guy223ff5c2010-03-02 17:07:47 -08001201
1202 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 }
1204
1205 private static void dump(View root, OutputStream clientStream) throws IOException {
1206 BufferedWriter out = null;
1207 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001208 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001209 View view = root.getRootView();
1210 if (view instanceof ViewGroup) {
1211 ViewGroup group = (ViewGroup) view;
The Android Open Source Project10592532009-03-18 17:39:46 -07001212 dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
1214 out.write("DONE.");
1215 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001216 } catch (Exception e) {
1217 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001218 } finally {
1219 if (out != null) {
1220 out.close();
1221 }
1222 }
1223 }
1224
1225 private static View findView(ViewGroup group, String className, int hashCode) {
1226 if (isRequestedView(group, className, hashCode)) {
1227 return group;
1228 }
1229
1230 final int count = group.getChildCount();
1231 for (int i = 0; i < count; i++) {
1232 final View view = group.getChildAt(i);
1233 if (view instanceof ViewGroup) {
1234 final View found = findView((ViewGroup) view, className, hashCode);
1235 if (found != null) {
1236 return found;
1237 }
1238 } else if (isRequestedView(view, className, hashCode)) {
1239 return view;
1240 }
1241 }
1242
1243 return null;
1244 }
1245
1246 private static boolean isRequestedView(View view, String className, int hashCode) {
1247 return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
1248 }
1249
The Android Open Source Project10592532009-03-18 17:39:46 -07001250 private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 BufferedWriter out, int level) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001252 if (!dumpViewWithProperties(context, group, out, level)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 return;
1254 }
1255
1256 final int count = group.getChildCount();
1257 for (int i = 0; i < count; i++) {
1258 final View view = group.getChildAt(i);
1259 if (view instanceof ViewGroup) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001260 dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001261 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -07001262 dumpViewWithProperties(context, view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 }
1264 }
1265 }
1266
The Android Open Source Project10592532009-03-18 17:39:46 -07001267 private static boolean dumpViewWithProperties(Context context, View view,
1268 BufferedWriter out, int level) {
1269
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 try {
1271 for (int i = 0; i < level; i++) {
1272 out.write(' ');
1273 }
1274 out.write(view.getClass().getName());
1275 out.write('@');
1276 out.write(Integer.toHexString(view.hashCode()));
1277 out.write(' ');
The Android Open Source Project10592532009-03-18 17:39:46 -07001278 dumpViewProperties(context, view, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001279 out.newLine();
1280 } catch (IOException e) {
1281 Log.w("View", "Error while dumping hierarchy tree");
1282 return false;
1283 }
1284 return true;
1285 }
1286
1287 private static Field[] getExportedPropertyFields(Class<?> klass) {
1288 if (sFieldsForClasses == null) {
1289 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
1290 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001291 if (sAnnotations == null) {
1292 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1293 }
1294
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001295 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001296 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297
1298 Field[] fields = map.get(klass);
1299 if (fields != null) {
1300 return fields;
1301 }
1302
1303 final ArrayList<Field> foundFields = new ArrayList<Field>();
1304 fields = klass.getDeclaredFields();
1305
1306 int count = fields.length;
1307 for (int i = 0; i < count; i++) {
1308 final Field field = fields[i];
1309 if (field.isAnnotationPresent(ExportedProperty.class)) {
1310 field.setAccessible(true);
1311 foundFields.add(field);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001312 annotations.put(field, field.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 }
1314 }
1315
1316 fields = foundFields.toArray(new Field[foundFields.size()]);
1317 map.put(klass, fields);
1318
1319 return fields;
1320 }
1321
1322 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1323 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001324 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001326 if (sAnnotations == null) {
1327 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1328 }
1329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001331 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332
1333 Method[] methods = map.get(klass);
1334 if (methods != null) {
1335 return methods;
1336 }
1337
1338 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1339 methods = klass.getDeclaredMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001341 int count = methods.length;
1342 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001343 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001345 method.isAnnotationPresent(ExportedProperty.class) &&
1346 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347 method.setAccessible(true);
1348 foundMethods.add(method);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001349 annotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 }
1351 }
1352
1353 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1354 map.put(klass, methods);
1355
1356 return methods;
1357 }
1358
The Android Open Source Project10592532009-03-18 17:39:46 -07001359 private static void dumpViewProperties(Context context, Object view,
1360 BufferedWriter out) throws IOException {
1361
1362 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001363 }
1364
The Android Open Source Project10592532009-03-18 17:39:46 -07001365 private static void dumpViewProperties(Context context, Object view,
1366 BufferedWriter out, String prefix) throws IOException {
1367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 Class<?> klass = view.getClass();
1369
1370 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001371 exportFields(context, view, out, klass, prefix);
1372 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 klass = klass.getSuperclass();
1374 } while (klass != Object.class);
1375 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001376
The Android Open Source Project10592532009-03-18 17:39:46 -07001377 private static void exportMethods(Context context, Object view, BufferedWriter out,
1378 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379
1380 final Method[] methods = getExportedPropertyMethods(klass);
1381
1382 int count = methods.length;
1383 for (int i = 0; i < count; i++) {
1384 final Method method = methods[i];
1385 //noinspection EmptyCatchBlock
1386 try {
1387 // TODO: This should happen on the UI thread
1388 Object methodValue = method.invoke(view, (Object[]) null);
1389 final Class<?> returnType = method.getReturnType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001390 final ExportedProperty property = sAnnotations.get(method);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001391 String categoryPrefix =
1392 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001393
1394 if (returnType == int.class) {
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001395
The Android Open Source Project10592532009-03-18 17:39:46 -07001396 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001397 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001398 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001400 final FlagToString[] flagsMapping = property.flagMapping();
1401 if (flagsMapping.length > 0) {
1402 final int intValue = (Integer) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001403 final String valuePrefix =
1404 categoryPrefix + prefix + method.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001405 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1406 }
1407
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001408 final IntToString[] mapping = property.mapping();
1409 if (mapping.length > 0) {
1410 final int intValue = (Integer) methodValue;
1411 boolean mapped = false;
1412 int mappingCount = mapping.length;
1413 for (int j = 0; j < mappingCount; j++) {
1414 final IntToString mapper = mapping[j];
1415 if (mapper.from() == intValue) {
1416 methodValue = mapper.to();
1417 mapped = true;
1418 break;
1419 }
1420 }
1421
1422 if (!mapped) {
1423 methodValue = intValue;
1424 }
1425 }
1426 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001427 } else if (returnType == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001428 final int[] array = (int[]) methodValue;
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001429 final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001430 final String suffix = "()";
1431
The Android Open Source Project10592532009-03-18 17:39:46 -07001432 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001433
1434 // Probably want to return here, same as for fields.
1435 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 } else if (!returnType.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001437 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001438 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001439 continue;
1440 }
1441 }
1442
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001443 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 } catch (IllegalAccessException e) {
1445 } catch (InvocationTargetException e) {
1446 }
1447 }
1448 }
1449
The Android Open Source Project10592532009-03-18 17:39:46 -07001450 private static void exportFields(Context context, Object view, BufferedWriter out,
1451 Class<?> klass, String prefix) throws IOException {
1452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001453 final Field[] fields = getExportedPropertyFields(klass);
1454
1455 int count = fields.length;
1456 for (int i = 0; i < count; i++) {
1457 final Field field = fields[i];
1458
1459 //noinspection EmptyCatchBlock
1460 try {
1461 Object fieldValue = null;
1462 final Class<?> type = field.getType();
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001463 final ExportedProperty property = sAnnotations.get(field);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001464 String categoryPrefix =
1465 property.category().length() != 0 ? property.category() + ":" : "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001466
1467 if (type == int.class) {
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07001468
The Android Open Source Project10592532009-03-18 17:39:46 -07001469 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001471 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001472 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001473 final FlagToString[] flagsMapping = property.flagMapping();
1474 if (flagsMapping.length > 0) {
1475 final int intValue = field.getInt(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001476 final String valuePrefix =
1477 categoryPrefix + prefix + field.getName() + '_';
Romain Guy809a7f62009-05-14 15:44:42 -07001478 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1479 }
1480
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001481 final IntToString[] mapping = property.mapping();
1482 if (mapping.length > 0) {
1483 final int intValue = field.getInt(view);
1484 int mappingCount = mapping.length;
1485 for (int j = 0; j < mappingCount; j++) {
1486 final IntToString mapped = mapping[j];
1487 if (mapped.from() == intValue) {
1488 fieldValue = mapped.to();
1489 break;
1490 }
1491 }
1492
1493 if (fieldValue == null) {
1494 fieldValue = intValue;
1495 }
1496 }
1497 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001498 } else if (type == int[].class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001499 final int[] array = (int[]) field.get(view);
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001500 final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001501 final String suffix = "";
1502
The Android Open Source Project10592532009-03-18 17:39:46 -07001503 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001504
1505 // We exit here!
1506 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001507 } else if (!type.isPrimitive()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 if (property.deepExport()) {
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001509 dumpViewProperties(context, field.get(view), out, prefix
1510 + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 continue;
1512 }
1513 }
1514
1515 if (fieldValue == null) {
1516 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 }
1518
Konstantin Lopyrev91a7f5f2010-08-10 18:54:54 -07001519 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001520 } catch (IllegalAccessException e) {
1521 }
1522 }
1523 }
1524
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001525 private static void writeEntry(BufferedWriter out, String prefix, String name,
1526 String suffix, Object value) throws IOException {
1527
1528 out.write(prefix);
1529 out.write(name);
1530 out.write(suffix);
1531 out.write("=");
1532 writeValue(out, value);
1533 out.write(' ');
1534 }
1535
Romain Guy809a7f62009-05-14 15:44:42 -07001536 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1537 int intValue, String prefix) throws IOException {
1538
1539 final int count = mapping.length;
1540 for (int j = 0; j < count; j++) {
1541 final FlagToString flagMapping = mapping[j];
1542 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001543 final int maskResult = intValue & flagMapping.mask();
1544 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001545 if ((test && ifTrue) || (!test && !ifTrue)) {
1546 final String name = flagMapping.name();
Romain Guy5bcdff42009-05-14 21:27:18 -07001547 final String value = "0x" + Integer.toHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001548 writeEntry(out, prefix, name, "", value);
1549 }
1550 }
1551 }
1552
The Android Open Source Project10592532009-03-18 17:39:46 -07001553 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001554 ExportedProperty property, int[] array, String prefix, String suffix)
1555 throws IOException {
1556
1557 final IntToString[] indexMapping = property.indexMapping();
1558 final boolean hasIndexMapping = indexMapping.length > 0;
1559
1560 final IntToString[] mapping = property.mapping();
1561 final boolean hasMapping = mapping.length > 0;
1562
The Android Open Source Project10592532009-03-18 17:39:46 -07001563 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001564 final int valuesCount = array.length;
1565
1566 for (int j = 0; j < valuesCount; j++) {
1567 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001568 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001569
1570 final int intValue = array[j];
1571
1572 name = String.valueOf(j);
1573 if (hasIndexMapping) {
1574 int mappingCount = indexMapping.length;
1575 for (int k = 0; k < mappingCount; k++) {
1576 final IntToString mapped = indexMapping[k];
1577 if (mapped.from() == j) {
1578 name = mapped.to();
1579 break;
1580 }
1581 }
1582 }
1583
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001584 if (hasMapping) {
1585 int mappingCount = mapping.length;
1586 for (int k = 0; k < mappingCount; k++) {
1587 final IntToString mapped = mapping[k];
1588 if (mapped.from() == intValue) {
1589 value = mapped.to();
1590 break;
1591 }
1592 }
1593 }
1594
1595 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001596 if (value == null) value = (String) resolveId(context, intValue);
1597 } else {
1598 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001599 }
1600
1601 writeEntry(out, prefix, name, suffix, value);
1602 }
1603 }
1604
Romain Guy237c1ce2009-12-08 11:30:25 -08001605 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001606 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001607 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001608 if (id >= 0) {
1609 try {
1610 fieldValue = resources.getResourceTypeName(id) + '/' +
1611 resources.getResourceEntryName(id);
1612 } catch (Resources.NotFoundException e) {
1613 fieldValue = "id/0x" + Integer.toHexString(id);
1614 }
1615 } else {
1616 fieldValue = "NO_ID";
1617 }
1618 return fieldValue;
1619 }
1620
1621 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1622 if (value != null) {
1623 String output = value.toString().replace("\n", "\\n");
1624 out.write(String.valueOf(output.length()));
1625 out.write(",");
1626 out.write(output);
1627 } else {
1628 out.write("4,null");
1629 }
1630 }
1631
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001632 private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
1633 if (!dumpView(group, out, level)) {
1634 return;
1635 }
1636
1637 final int count = group.getChildCount();
1638 for (int i = 0; i < count; i++) {
1639 final View view = group.getChildAt(i);
1640 if (view instanceof ViewGroup) {
1641 dumpViewHierarchy((ViewGroup) view, out, level + 1);
1642 } else {
1643 dumpView(view, out, level + 1);
1644 }
1645 }
1646 }
1647
1648 private static boolean dumpView(Object view, BufferedWriter out, int level) {
1649 try {
1650 for (int i = 0; i < level; i++) {
1651 out.write(' ');
1652 }
1653 out.write(view.getClass().getName());
1654 out.write('@');
1655 out.write(Integer.toHexString(view.hashCode()));
1656 out.newLine();
1657 } catch (IOException e) {
1658 Log.w("View", "Error while dumping hierarchy tree");
1659 return false;
1660 }
1661 return true;
1662 }
1663
1664 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1665 if (mCapturedViewFieldsForClasses == null) {
1666 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1667 }
1668 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1669
1670 Field[] fields = map.get(klass);
1671 if (fields != null) {
1672 return fields;
1673 }
1674
1675 final ArrayList<Field> foundFields = new ArrayList<Field>();
1676 fields = klass.getFields();
1677
1678 int count = fields.length;
1679 for (int i = 0; i < count; i++) {
1680 final Field field = fields[i];
1681 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1682 field.setAccessible(true);
1683 foundFields.add(field);
1684 }
1685 }
1686
1687 fields = foundFields.toArray(new Field[foundFields.size()]);
1688 map.put(klass, fields);
1689
1690 return fields;
1691 }
1692
1693 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1694 if (mCapturedViewMethodsForClasses == null) {
1695 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1696 }
1697 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1698
1699 Method[] methods = map.get(klass);
1700 if (methods != null) {
1701 return methods;
1702 }
1703
1704 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1705 methods = klass.getMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001707 int count = methods.length;
1708 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001709 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710 if (method.getParameterTypes().length == 0 &&
1711 method.isAnnotationPresent(CapturedViewProperty.class) &&
1712 method.getReturnType() != Void.class) {
1713 method.setAccessible(true);
1714 foundMethods.add(method);
1715 }
1716 }
1717
1718 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1719 map.put(klass, methods);
1720
1721 return methods;
1722 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001723
1724 private static String capturedViewExportMethods(Object obj, Class<?> klass,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001725 String prefix) {
1726
1727 if (obj == null) {
1728 return "null";
1729 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001730
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001731 StringBuilder sb = new StringBuilder();
1732 final Method[] methods = capturedViewGetPropertyMethods(klass);
1733
1734 int count = methods.length;
1735 for (int i = 0; i < count; i++) {
1736 final Method method = methods[i];
1737 try {
1738 Object methodValue = method.invoke(obj, (Object[]) null);
1739 final Class<?> returnType = method.getReturnType();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001740
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001741 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1742 if (property.retrieveReturn()) {
1743 //we are interested in the second level data only
1744 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001745 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001746 sb.append(prefix);
1747 sb.append(method.getName());
1748 sb.append("()=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001749
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001750 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001751 final String value = methodValue.toString().replace("\n", "\\n");
1752 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001753 } else {
1754 sb.append("null");
1755 }
1756 sb.append("; ");
1757 }
1758 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001759 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001760 //we simply ignore this method
1761 } catch (InvocationTargetException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001762 //Exception InvocationTarget, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001763 //we simply ignore this method
Romain Guya1f3e4a2009-06-04 15:10:46 -07001764 }
1765 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001766 return sb.toString();
1767 }
1768
1769 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001770
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001771 if (obj == null) {
1772 return "null";
1773 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001774
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001775 StringBuilder sb = new StringBuilder();
1776 final Field[] fields = capturedViewGetPropertyFields(klass);
1777
1778 int count = fields.length;
1779 for (int i = 0; i < count; i++) {
1780 final Field field = fields[i];
1781 try {
1782 Object fieldValue = field.get(obj);
1783
1784 sb.append(prefix);
1785 sb.append(field.getName());
1786 sb.append("=");
1787
1788 if (fieldValue != null) {
1789 final String value = fieldValue.toString().replace("\n", "\\n");
1790 sb.append(value);
1791 } else {
1792 sb.append("null");
1793 }
1794 sb.append(' ');
1795 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001796 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001797 //we simply ignore this field
1798 }
1799 }
1800 return sb.toString();
1801 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001802
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001803 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001804 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001805 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001806 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 * @param tag for log
1808 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001809 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001810 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001811 Class<?> klass = view.getClass();
1812 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1813 sb.append(capturedViewExportFields(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001814 sb.append(capturedViewExportMethods(view, klass, ""));
1815 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001816 }
1817}