blob: aaaadefa15af052cfd9211d64cf35581eec69212 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.os.Environment;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070026import android.os.Debug;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
28import java.io.File;
29import java.io.BufferedWriter;
30import java.io.FileWriter;
31import java.io.IOException;
32import java.io.FileOutputStream;
33import java.io.DataOutputStream;
34import java.io.OutputStreamWriter;
35import java.io.BufferedOutputStream;
36import java.io.OutputStream;
37import java.util.List;
38import java.util.LinkedList;
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.concurrent.CountDownLatch;
42import java.util.concurrent.TimeUnit;
43import java.lang.annotation.Target;
44import java.lang.annotation.ElementType;
45import java.lang.annotation.Retention;
46import java.lang.annotation.RetentionPolicy;
47import java.lang.reflect.Field;
48import java.lang.reflect.Method;
49import java.lang.reflect.InvocationTargetException;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070050import java.lang.reflect.AccessibleObject;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
52/**
53 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
54 */
55public class ViewDebug {
56 /**
Romain Guy13922e02009-05-12 17:56:14 -070057 * Log tag used to log errors related to the consistency of the view hierarchy.
58 *
59 * @hide
60 */
61 public static final String CONSISTENCY_LOG_TAG = "ViewConsistency";
62
63 /**
64 * Flag indicating the consistency check should check layout-related properties.
65 *
66 * @hide
67 */
68 public static final int CONSISTENCY_LAYOUT = 0x1;
69
70 /**
71 * Flag indicating the consistency check should check drawing-related properties.
72 *
73 * @hide
74 */
75 public static final int CONSISTENCY_DRAWING = 0x2;
76
77 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 * Enables or disables view hierarchy tracing. Any invoker of
79 * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
80 * check that this value is set to true as not to affect performance.
81 */
82 public static final boolean TRACE_HIERARCHY = false;
83
84 /**
85 * Enables or disables view recycler tracing. Any invoker of
86 * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
87 * check that this value is set to true as not to affect performance.
88 */
89 public static final boolean TRACE_RECYCLER = false;
90
91 /**
92 * The system property of dynamic switch for capturing view information
93 * when it is set, we dump interested fields and methods for the view on focus
94 */
95 static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
96
97 /**
98 * The system property of dynamic switch for capturing event information
99 * when it is set, we log key events, touch/motion and trackball events
100 */
101 static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 /**
Romain Guy13922e02009-05-12 17:56:14 -0700104 * Profiles drawing times in the events log.
105 *
106 * @hide
107 */
108 @Debug.DebugProperty
109 public static boolean profileDrawing = false;
110
111 /**
112 * Profiles layout times in the events log.
113 *
114 * @hide
115 */
116 @Debug.DebugProperty
117 public static boolean profileLayout = false;
118
119 /**
120 * Profiles real fps (times between draws) and displays the result.
121 *
122 * @hide
123 */
124 @Debug.DebugProperty
125 public static boolean showFps = false;
126
127 /**
128 * <p>Enables or disables views consistency check. Even when this property is enabled,
129 * view consistency checks happen only if {@link android.util.Config#DEBUG} is set
130 * to true. The value of this property can be configured externally in one of the
131 * following files:</p>
132 * <ul>
133 * <li>/system/debug.prop</li>
134 * <li>/debug.prop</li>
135 * <li>/data/debug.prop</li>
136 * </ul>
137 * @hide
138 */
139 @Debug.DebugProperty
140 public static boolean consistencyCheckEnabled = false;
141
142 static {
143 Debug.setFieldsOn(ViewDebug.class, true);
144 }
145
146 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 * This annotation can be used to mark fields and methods to be dumped by
148 * the view server. Only non-void methods with no arguments can be annotated
149 * by this annotation.
150 */
151 @Target({ ElementType.FIELD, ElementType.METHOD })
152 @Retention(RetentionPolicy.RUNTIME)
153 public @interface ExportedProperty {
154 /**
155 * When resolveId is true, and if the annotated field/method return value
156 * is an int, the value is converted to an Android's resource name.
157 *
158 * @return true if the property's value must be transformed into an Android
159 * resource name, false otherwise
160 */
161 boolean resolveId() default false;
162
163 /**
164 * A mapping can be defined to map int values to specific strings. For
165 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
166 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
167 * these human readable values:
168 *
169 * <pre>
170 * @ViewDebug.ExportedProperty(mapping = {
171 * @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
172 * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
173 * @ViewDebug.IntToString(from = 8, to = "GONE")
174 * })
175 * public int getVisibility() { ...
176 * <pre>
177 *
178 * @return An array of int to String mappings
179 *
180 * @see android.view.ViewDebug.IntToString
181 */
182 IntToString[] mapping() default { };
183
184 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700185 * A mapping can be defined to map array indices to specific strings.
186 * A mapping can be used to see human readable values for the indices
187 * of an array:
188 *
189 * <pre>
Romain Guy809a7f62009-05-14 15:44:42 -0700190 * @ViewDebug.ExportedProperty(indexMapping = {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700191 * @ViewDebug.IntToString(from = 0, to = "INVALID"),
192 * @ViewDebug.IntToString(from = 1, to = "FIRST"),
193 * @ViewDebug.IntToString(from = 2, to = "SECOND")
194 * })
195 * private int[] mElements;
196 * <pre>
197 *
198 * @return An array of int to String mappings
199 *
200 * @see android.view.ViewDebug.IntToString
201 * @see #mapping()
202 */
203 IntToString[] indexMapping() default { };
204
205 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700206 * A flags mapping can be defined to map flags encoded in an integer to
207 * specific strings. A mapping can be used to see human readable values
208 * for the flags of an integer:
209 *
210 * <pre>
211 * @ViewDebug.ExportedProperty(flagMapping = {
212 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"),
213 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"),
214 * })
215 * private int mFlags;
216 * <pre>
217 *
218 * A specified String is output when the following is true:
219 *
220 * @return An array of int to String mappings
221 */
222 FlagToString[] flagMapping() default { };
223
224 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 * When deep export is turned on, this property is not dumped. Instead, the
226 * properties contained in this property are dumped. Each child property
227 * is prefixed with the name of this property.
228 *
229 * @return true if the properties of this property should be dumped
230 *
231 * @see #prefix()
232 */
233 boolean deepExport() default false;
234
235 /**
236 * The prefix to use on child properties when deep export is enabled
237 *
238 * @return a prefix as a String
239 *
240 * @see #deepExport()
241 */
242 String prefix() default "";
243 }
244
245 /**
246 * Defines a mapping from an int value to a String. Such a mapping can be used
247 * in a @ExportedProperty to provide more meaningful values to the end user.
248 *
249 * @see android.view.ViewDebug.ExportedProperty
250 */
251 @Target({ ElementType.TYPE })
252 @Retention(RetentionPolicy.RUNTIME)
253 public @interface IntToString {
254 /**
255 * The original int value to map to a String.
256 *
257 * @return An arbitrary int value.
258 */
259 int from();
260
261 /**
262 * The String to use in place of the original int value.
263 *
264 * @return An arbitrary non-null String.
265 */
266 String to();
267 }
Romain Guy809a7f62009-05-14 15:44:42 -0700268
269 /**
270 * Defines a mapping from an flag to a String. Such a mapping can be used
271 * in a @ExportedProperty to provide more meaningful values to the end user.
272 *
273 * @see android.view.ViewDebug.ExportedProperty
274 */
275 @Target({ ElementType.TYPE })
276 @Retention(RetentionPolicy.RUNTIME)
277 public @interface FlagToString {
278 /**
279 * The mask to apply to the original value.
280 *
281 * @return An arbitrary int value.
282 */
283 int mask();
284
285 /**
286 * The value to compare to the result of:
287 * <code>original value &amp; {@link #mask()}</code>.
288 *
289 * @return An arbitrary value.
290 */
291 int equals();
292
293 /**
294 * The String to use in place of the original int value.
295 *
296 * @return An arbitrary non-null String.
297 */
298 String name();
299
300 /**
301 * Indicates whether to output the flag when the test is true,
302 * or false. Defaults to true.
303 */
304 boolean outputIf() default true;
305 }
306
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 /**
308 * This annotation can be used to mark fields and methods to be dumped when
309 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700310 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 */
312 @Target({ ElementType.FIELD, ElementType.METHOD })
313 @Retention(RetentionPolicy.RUNTIME)
314 public @interface CapturedViewProperty {
315 /**
316 * When retrieveReturn is true, we need to retrieve second level methods
317 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
318 * we will set retrieveReturn = true on the annotation of
319 * myView.getFirstLevelMethod()
320 * @return true if we need the second level methods
321 */
322 boolean retrieveReturn() default false;
323 }
324
325 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
326 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
327
328 // Maximum delay in ms after which we stop trying to capture a View's drawing
329 private static final int CAPTURE_TIMEOUT = 4000;
330
331 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
332 private static final String REMOTE_COMMAND_DUMP = "DUMP";
333 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
334 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700335 private static final String REMOTE_PROFILE = "PROFILE";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336
337 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
338 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700339 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
340
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 /**
342 * Defines the type of hierarhcy trace to output to the hierarchy traces file.
343 */
344 public enum HierarchyTraceType {
345 INVALIDATE,
346 INVALIDATE_CHILD,
347 INVALIDATE_CHILD_IN_PARENT,
348 REQUEST_LAYOUT,
349 ON_LAYOUT,
350 ON_MEASURE,
351 DRAW,
352 BUILD_CACHE
353 }
354
355 private static BufferedWriter sHierarchyTraces;
356 private static ViewRoot sHierarhcyRoot;
357 private static String sHierarchyTracePrefix;
358
359 /**
360 * Defines the type of recycler trace to output to the recycler traces file.
361 */
362 public enum RecyclerTraceType {
363 NEW_VIEW,
364 BIND_VIEW,
365 RECYCLE_FROM_ACTIVE_HEAP,
366 RECYCLE_FROM_SCRAP_HEAP,
367 MOVE_TO_ACTIVE_HEAP,
368 MOVE_TO_SCRAP_HEAP,
369 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
370 }
371
372 private static class RecyclerTrace {
373 public int view;
374 public RecyclerTraceType type;
375 public int position;
376 public int indexOnScreen;
377 }
378
379 private static View sRecyclerOwnerView;
380 private static List<View> sRecyclerViews;
381 private static List<RecyclerTrace> sRecyclerTraces;
382 private static String sRecyclerTracePrefix;
383
384 /**
385 * Returns the number of instanciated Views.
386 *
387 * @return The number of Views instanciated in the current process.
388 *
389 * @hide
390 */
391 public static long getViewInstanceCount() {
392 return View.sInstanceCount;
393 }
394
395 /**
396 * Returns the number of instanciated ViewRoots.
397 *
398 * @return The number of ViewRoots instanciated in the current process.
399 *
400 * @hide
401 */
402 public static long getViewRootInstanceCount() {
403 return ViewRoot.getInstanceCount();
404 }
405
406 /**
407 * Outputs a trace to the currently opened recycler traces. The trace records the type of
408 * recycler action performed on the supplied view as well as a number of parameters.
409 *
410 * @param view the view to trace
411 * @param type the type of the trace
412 * @param parameters parameters depending on the type of the trace
413 */
414 public static void trace(View view, RecyclerTraceType type, int... parameters) {
415 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
416 return;
417 }
418
419 if (!sRecyclerViews.contains(view)) {
420 sRecyclerViews.add(view);
421 }
422
423 final int index = sRecyclerViews.indexOf(view);
424
425 RecyclerTrace trace = new RecyclerTrace();
426 trace.view = index;
427 trace.type = type;
428 trace.position = parameters[0];
429 trace.indexOnScreen = parameters[1];
430
431 sRecyclerTraces.add(trace);
432 }
433
434 /**
435 * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
436 * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
437 * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
438 *
439 * Only one view recycler can be traced at the same time. After calling this method, any
440 * other invocation will result in a <code>IllegalStateException</code> unless
441 * {@link #stopRecyclerTracing()} is invoked before.
442 *
443 * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
444 *
445 * This method will return immediately if TRACE_RECYCLER is false.
446 *
447 * @param prefix the traces files name prefix
448 * @param view the view whose recycler must be traced
449 *
450 * @see #stopRecyclerTracing()
451 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
452 */
453 public static void startRecyclerTracing(String prefix, View view) {
454 //noinspection PointlessBooleanExpression,ConstantConditions
455 if (!TRACE_RECYCLER) {
456 return;
457 }
458
459 if (sRecyclerOwnerView != null) {
460 throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
461 " a new trace!");
462 }
463
464 sRecyclerTracePrefix = prefix;
465 sRecyclerOwnerView = view;
466 sRecyclerViews = new ArrayList<View>();
467 sRecyclerTraces = new LinkedList<RecyclerTrace>();
468 }
469
470 /**
471 * Stops the current view recycer tracing.
472 *
473 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
474 * containing all the traces (or method calls) relative to the specified view's recycler.
475 *
476 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
477 * containing all of the views used by the recycler of the view supplied to
478 * {@link #startRecyclerTracing(String, View)}.
479 *
480 * This method will return immediately if TRACE_RECYCLER is false.
481 *
482 * @see #startRecyclerTracing(String, View)
483 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
484 */
485 public static void stopRecyclerTracing() {
486 //noinspection PointlessBooleanExpression,ConstantConditions
487 if (!TRACE_RECYCLER) {
488 return;
489 }
490
491 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
492 throw new IllegalStateException("You must call startRecyclerTracing() before" +
493 " stopRecyclerTracing()!");
494 }
495
496 File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700497 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 recyclerDump.mkdirs();
499
500 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
501 try {
502 final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
503
504 for (View view : sRecyclerViews) {
505 final String name = view.getClass().getName();
506 out.write(name);
507 out.newLine();
508 }
509
510 out.close();
511 } catch (IOException e) {
512 Log.e("View", "Could not dump recycler content");
513 return;
514 }
515
516 recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
517 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
518 try {
519 final FileOutputStream file = new FileOutputStream(recyclerDump);
520 final DataOutputStream out = new DataOutputStream(file);
521
522 for (RecyclerTrace trace : sRecyclerTraces) {
523 out.writeInt(trace.view);
524 out.writeInt(trace.type.ordinal());
525 out.writeInt(trace.position);
526 out.writeInt(trace.indexOnScreen);
527 out.flush();
528 }
529
530 out.close();
531 } catch (IOException e) {
532 Log.e("View", "Could not dump recycler traces");
533 return;
534 }
535
536 sRecyclerViews.clear();
537 sRecyclerViews = null;
538
539 sRecyclerTraces.clear();
540 sRecyclerTraces = null;
541
542 sRecyclerOwnerView = null;
543 }
544
545 /**
546 * Outputs a trace to the currently opened traces file. The trace contains the class name
547 * and instance's hashcode of the specified view as well as the supplied trace type.
548 *
549 * @param view the view to trace
550 * @param type the type of the trace
551 */
552 public static void trace(View view, HierarchyTraceType type) {
553 if (sHierarchyTraces == null) {
554 return;
555 }
556
557 try {
558 sHierarchyTraces.write(type.name());
559 sHierarchyTraces.write(' ');
560 sHierarchyTraces.write(view.getClass().getName());
561 sHierarchyTraces.write('@');
562 sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
563 sHierarchyTraces.newLine();
564 } catch (IOException e) {
565 Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
566 }
567 }
568
569 /**
570 * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
571 * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
572 * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
573 *
574 * Only one view hierarchy can be traced at the same time. After calling this method, any
575 * other invocation will result in a <code>IllegalStateException</code> unless
576 * {@link #stopHierarchyTracing()} is invoked before.
577 *
578 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
579 * containing all the traces (or method calls) relative to the specified view's hierarchy.
580 *
581 * This method will return immediately if TRACE_HIERARCHY is false.
582 *
583 * @param prefix the traces files name prefix
584 * @param view the view whose hierarchy must be traced
585 *
586 * @see #stopHierarchyTracing()
587 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
588 */
589 public static void startHierarchyTracing(String prefix, View view) {
590 //noinspection PointlessBooleanExpression,ConstantConditions
591 if (!TRACE_HIERARCHY) {
592 return;
593 }
594
595 if (sHierarhcyRoot != null) {
596 throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
597 " a new trace!");
598 }
599
600 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700601 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800602 hierarchyDump.mkdirs();
603
604 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
605 sHierarchyTracePrefix = prefix;
606
607 try {
608 sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
609 } catch (IOException e) {
610 Log.e("View", "Could not dump view hierarchy");
611 return;
612 }
613
614 sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
615 }
616
617 /**
618 * Stops the current view hierarchy tracing. This method closes the file
619 * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
620 *
621 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
622 * containing the view hierarchy of the view supplied to
623 * {@link #startHierarchyTracing(String, View)}.
624 *
625 * This method will return immediately if TRACE_HIERARCHY is false.
626 *
627 * @see #startHierarchyTracing(String, View)
628 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
629 */
630 public static void stopHierarchyTracing() {
631 //noinspection PointlessBooleanExpression,ConstantConditions
632 if (!TRACE_HIERARCHY) {
633 return;
634 }
635
636 if (sHierarhcyRoot == null || sHierarchyTraces == null) {
637 throw new IllegalStateException("You must call startHierarchyTracing() before" +
638 " stopHierarchyTracing()!");
639 }
640
641 try {
642 sHierarchyTraces.close();
643 } catch (IOException e) {
644 Log.e("View", "Could not write view traces");
645 }
646 sHierarchyTraces = null;
647
648 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700649 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800650 hierarchyDump.mkdirs();
651 hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
652
653 BufferedWriter out;
654 try {
655 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
656 } catch (IOException e) {
657 Log.e("View", "Could not dump view hierarchy");
658 return;
659 }
660
661 View view = sHierarhcyRoot.getView();
662 if (view instanceof ViewGroup) {
663 ViewGroup group = (ViewGroup) view;
664 dumpViewHierarchy(group, out, 0);
665 try {
666 out.close();
667 } catch (IOException e) {
668 Log.e("View", "Could not dump view hierarchy");
669 }
670 }
671
672 sHierarhcyRoot = null;
673 }
674
675 static void dispatchCommand(View view, String command, String parameters,
676 OutputStream clientStream) throws IOException {
677
678 // Paranoid but safe...
679 view = view.getRootView();
680
681 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
682 dump(view, clientStream);
683 } else {
684 final String[] params = parameters.split(" ");
685 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
686 capture(view, clientStream, params[0]);
687 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
688 invalidate(view, params[0]);
689 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
690 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700691 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
692 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800693 }
694 }
695 }
696
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700697 private static View findView(View root, String parameter) {
698 // Look by type/hashcode
699 if (parameter.indexOf('@') != -1) {
700 final String[] ids = parameter.split("@");
701 final String className = ids[0];
702 final int hashCode = Integer.parseInt(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700704 View view = root.getRootView();
705 if (view instanceof ViewGroup) {
706 return findView((ViewGroup) view, className, hashCode);
707 }
708 } else {
709 // Look by id
710 final int id = root.getResources().getIdentifier(parameter, null, null);
711 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 }
713
714 return null;
715 }
716
717 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700718 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800719 if (view != null) {
720 view.postInvalidate();
721 }
722 }
723
724 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700725 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 if (view != null) {
727 root.post(new Runnable() {
728 public void run() {
729 view.requestLayout();
730 }
731 });
732 }
733 }
734
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700735 private static void profile(View root, OutputStream clientStream, String parameter)
736 throws IOException {
737
738 final View view = findView(root, parameter);
739 BufferedWriter out = null;
740 try {
741 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
742
743 if (view != null) {
744 final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
745 public Void[] pre() {
746 forceLayout(view);
747 return null;
748 }
749
750 private void forceLayout(View view) {
751 view.forceLayout();
752 if (view instanceof ViewGroup) {
753 ViewGroup group = (ViewGroup) view;
754 final int count = group.getChildCount();
755 for (int i = 0; i < count; i++) {
756 forceLayout(group.getChildAt(i));
757 }
758 }
759 }
760
761 public void run(Void... data) {
762 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
763 }
764
765 public void post(Void... data) {
766 }
767 });
768
769 final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
770 public Void[] pre() {
771 return null;
772 }
773
774 public void run(Void... data) {
775 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
776 }
777
778 public void post(Void... data) {
779 }
780 });
781
782 final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
783 public Object[] pre() {
784 final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
785 final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700786 metrics.heightPixels, Bitmap.Config.RGB_565);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700787 final Canvas canvas = new Canvas(bitmap);
788 return new Object[] { bitmap, canvas };
789 }
790
791 public void run(Object... data) {
792 view.draw((Canvas) data[1]);
793 }
794
795 public void post(Object... data) {
796 ((Bitmap) data[0]).recycle();
797 }
798 });
799
800 out.write(String.valueOf(durationMeasure));
801 out.write(' ');
802 out.write(String.valueOf(durationLayout));
803 out.write(' ');
804 out.write(String.valueOf(durationDraw));
805 out.newLine();
806 } else {
807 out.write("-1 -1 -1");
808 out.newLine();
809 }
810 } catch (Exception e) {
811 android.util.Log.w("View", "Problem profiling the view:", e);
812 } finally {
813 if (out != null) {
814 out.close();
815 }
816 }
817 }
818
819 interface ViewOperation<T> {
820 T[] pre();
821 void run(T... data);
822 void post(T... data);
823 }
824
825 private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
826 final CountDownLatch latch = new CountDownLatch(1);
827 final long[] duration = new long[1];
828
829 view.post(new Runnable() {
830 public void run() {
831 try {
832 T[] data = operation.pre();
833 long start = Debug.threadCpuTimeNanos();
834 operation.run(data);
835 duration[0] = Debug.threadCpuTimeNanos() - start;
836 operation.post(data);
837 } finally {
838 latch.countDown();
839 }
840 }
841 });
842
843 try {
844 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
845 } catch (InterruptedException e) {
846 Log.w("View", "Could not complete the profiling of the view " + view);
847 Thread.currentThread().interrupt();
848 return -1;
849 }
850
851 return duration[0];
852 }
853
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 private static void capture(View root, final OutputStream clientStream, String parameter)
855 throws IOException {
856
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700857 final View captureView = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858
859 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700860 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 final Bitmap[] cache = new Bitmap[1];
862
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 root.post(new Runnable() {
864 public void run() {
865 try {
Dianne Hackborn958b9ad2009-03-31 18:00:36 -0700866 cache[0] = captureView.createSnapshot(
867 Bitmap.Config.ARGB_8888, 0);
868 } catch (OutOfMemoryError e) {
869 try {
870 cache[0] = captureView.createSnapshot(
871 Bitmap.Config.ARGB_4444, 0);
872 } catch (OutOfMemoryError e2) {
873 Log.w("View", "Out of memory for bitmap");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 } finally {
876 latch.countDown();
877 }
878 }
879 });
880
881 try {
882 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
883
884 if (cache[0] != null) {
885 BufferedOutputStream out = null;
886 try {
887 out = new BufferedOutputStream(clientStream, 32 * 1024);
888 cache[0].compress(Bitmap.CompressFormat.PNG, 100, out);
889 out.flush();
890 } finally {
891 if (out != null) {
892 out.close();
893 }
Dianne Hackborn958b9ad2009-03-31 18:00:36 -0700894 cache[0].recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800895 }
Dianne Hackborn958b9ad2009-03-31 18:00:36 -0700896 } else {
897 Log.w("View", "Failed to create capture bitmap!");
898 clientStream.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 }
900 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700901 Log.w("View", "Could not complete the capture of the view " + captureView);
902 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800903 }
904 }
905 }
906
907 private static void dump(View root, OutputStream clientStream) throws IOException {
908 BufferedWriter out = null;
909 try {
910 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
911 View view = root.getRootView();
912 if (view instanceof ViewGroup) {
913 ViewGroup group = (ViewGroup) view;
The Android Open Source Project10592532009-03-18 17:39:46 -0700914 dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800915 }
916 out.write("DONE.");
917 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700918 } catch (Exception e) {
919 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800920 } finally {
921 if (out != null) {
922 out.close();
923 }
924 }
925 }
926
927 private static View findView(ViewGroup group, String className, int hashCode) {
928 if (isRequestedView(group, className, hashCode)) {
929 return group;
930 }
931
932 final int count = group.getChildCount();
933 for (int i = 0; i < count; i++) {
934 final View view = group.getChildAt(i);
935 if (view instanceof ViewGroup) {
936 final View found = findView((ViewGroup) view, className, hashCode);
937 if (found != null) {
938 return found;
939 }
940 } else if (isRequestedView(view, className, hashCode)) {
941 return view;
942 }
943 }
944
945 return null;
946 }
947
948 private static boolean isRequestedView(View view, String className, int hashCode) {
949 return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
950 }
951
The Android Open Source Project10592532009-03-18 17:39:46 -0700952 private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953 BufferedWriter out, int level) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700954 if (!dumpViewWithProperties(context, group, out, level)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 return;
956 }
957
958 final int count = group.getChildCount();
959 for (int i = 0; i < count; i++) {
960 final View view = group.getChildAt(i);
961 if (view instanceof ViewGroup) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700962 dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700964 dumpViewWithProperties(context, view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800965 }
966 }
967 }
968
The Android Open Source Project10592532009-03-18 17:39:46 -0700969 private static boolean dumpViewWithProperties(Context context, View view,
970 BufferedWriter out, int level) {
971
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800972 try {
973 for (int i = 0; i < level; i++) {
974 out.write(' ');
975 }
976 out.write(view.getClass().getName());
977 out.write('@');
978 out.write(Integer.toHexString(view.hashCode()));
979 out.write(' ');
The Android Open Source Project10592532009-03-18 17:39:46 -0700980 dumpViewProperties(context, view, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800981 out.newLine();
982 } catch (IOException e) {
983 Log.w("View", "Error while dumping hierarchy tree");
984 return false;
985 }
986 return true;
987 }
988
989 private static Field[] getExportedPropertyFields(Class<?> klass) {
990 if (sFieldsForClasses == null) {
991 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
992 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700993 if (sAnnotations == null) {
994 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
995 }
996
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800997 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700998 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800999
1000 Field[] fields = map.get(klass);
1001 if (fields != null) {
1002 return fields;
1003 }
1004
1005 final ArrayList<Field> foundFields = new ArrayList<Field>();
1006 fields = klass.getDeclaredFields();
1007
1008 int count = fields.length;
1009 for (int i = 0; i < count; i++) {
1010 final Field field = fields[i];
1011 if (field.isAnnotationPresent(ExportedProperty.class)) {
1012 field.setAccessible(true);
1013 foundFields.add(field);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001014 annotations.put(field, field.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 }
1016 }
1017
1018 fields = foundFields.toArray(new Field[foundFields.size()]);
1019 map.put(klass, fields);
1020
1021 return fields;
1022 }
1023
1024 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1025 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001026 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001028 if (sAnnotations == null) {
1029 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1030 }
1031
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001033 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034
1035 Method[] methods = map.get(klass);
1036 if (methods != null) {
1037 return methods;
1038 }
1039
1040 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1041 methods = klass.getDeclaredMethods();
1042
1043 int count = methods.length;
1044 for (int i = 0; i < count; i++) {
1045 final Method method = methods[i];
1046 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001047 method.isAnnotationPresent(ExportedProperty.class) &&
1048 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001049 method.setAccessible(true);
1050 foundMethods.add(method);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001051 annotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001052 }
1053 }
1054
1055 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1056 map.put(klass, methods);
1057
1058 return methods;
1059 }
1060
The Android Open Source Project10592532009-03-18 17:39:46 -07001061 private static void dumpViewProperties(Context context, Object view,
1062 BufferedWriter out) throws IOException {
1063
1064 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 }
1066
The Android Open Source Project10592532009-03-18 17:39:46 -07001067 private static void dumpViewProperties(Context context, Object view,
1068 BufferedWriter out, String prefix) throws IOException {
1069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001070 Class<?> klass = view.getClass();
1071
1072 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001073 exportFields(context, view, out, klass, prefix);
1074 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001075 klass = klass.getSuperclass();
1076 } while (klass != Object.class);
1077 }
1078
The Android Open Source Project10592532009-03-18 17:39:46 -07001079 private static void exportMethods(Context context, Object view, BufferedWriter out,
1080 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081
1082 final Method[] methods = getExportedPropertyMethods(klass);
1083
1084 int count = methods.length;
1085 for (int i = 0; i < count; i++) {
1086 final Method method = methods[i];
1087 //noinspection EmptyCatchBlock
1088 try {
1089 // TODO: This should happen on the UI thread
1090 Object methodValue = method.invoke(view, (Object[]) null);
1091 final Class<?> returnType = method.getReturnType();
1092
1093 if (returnType == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001094 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project10592532009-03-18 17:39:46 -07001095 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001097 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001099 final FlagToString[] flagsMapping = property.flagMapping();
1100 if (flagsMapping.length > 0) {
1101 final int intValue = (Integer) methodValue;
1102 final String valuePrefix = prefix + method.getName() + '_';
1103 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1104 }
1105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 final IntToString[] mapping = property.mapping();
1107 if (mapping.length > 0) {
1108 final int intValue = (Integer) methodValue;
1109 boolean mapped = false;
1110 int mappingCount = mapping.length;
1111 for (int j = 0; j < mappingCount; j++) {
1112 final IntToString mapper = mapping[j];
1113 if (mapper.from() == intValue) {
1114 methodValue = mapper.to();
1115 mapped = true;
1116 break;
1117 }
1118 }
1119
1120 if (!mapped) {
1121 methodValue = intValue;
1122 }
1123 }
1124 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001125 } else if (returnType == int[].class) {
1126 final ExportedProperty property = sAnnotations.get(method);
1127 final int[] array = (int[]) methodValue;
1128 final String valuePrefix = prefix + method.getName() + '_';
1129 final String suffix = "()";
1130
The Android Open Source Project10592532009-03-18 17:39:46 -07001131 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 } else if (!returnType.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001133 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001135 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 continue;
1137 }
1138 }
1139
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001140 writeEntry(out, prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 } catch (IllegalAccessException e) {
1142 } catch (InvocationTargetException e) {
1143 }
1144 }
1145 }
1146
The Android Open Source Project10592532009-03-18 17:39:46 -07001147 private static void exportFields(Context context, Object view, BufferedWriter out,
1148 Class<?> klass, String prefix) throws IOException {
1149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 final Field[] fields = getExportedPropertyFields(klass);
1151
1152 int count = fields.length;
1153 for (int i = 0; i < count; i++) {
1154 final Field field = fields[i];
1155
1156 //noinspection EmptyCatchBlock
1157 try {
1158 Object fieldValue = null;
1159 final Class<?> type = field.getType();
1160
1161 if (type == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001162 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project10592532009-03-18 17:39:46 -07001163 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001164 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001165 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001167 final FlagToString[] flagsMapping = property.flagMapping();
1168 if (flagsMapping.length > 0) {
1169 final int intValue = field.getInt(view);
1170 final String valuePrefix = prefix + field.getName() + '_';
1171 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1172 }
1173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 final IntToString[] mapping = property.mapping();
1175 if (mapping.length > 0) {
1176 final int intValue = field.getInt(view);
1177 int mappingCount = mapping.length;
1178 for (int j = 0; j < mappingCount; j++) {
1179 final IntToString mapped = mapping[j];
1180 if (mapped.from() == intValue) {
1181 fieldValue = mapped.to();
1182 break;
1183 }
1184 }
1185
1186 if (fieldValue == null) {
1187 fieldValue = intValue;
1188 }
1189 }
1190 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001191 } else if (type == int[].class) {
1192 final ExportedProperty property = sAnnotations.get(field);
1193 final int[] array = (int[]) field.get(view);
1194 final String valuePrefix = prefix + field.getName() + '_';
1195 final String suffix = "";
1196
The Android Open Source Project10592532009-03-18 17:39:46 -07001197 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001198
1199 // We exit here!
1200 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 } else if (!type.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001202 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001204 dumpViewProperties(context, field.get(view), out,
1205 prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001206 continue;
1207 }
1208 }
1209
1210 if (fieldValue == null) {
1211 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 }
1213
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001214 writeEntry(out, prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 } catch (IllegalAccessException e) {
1216 }
1217 }
1218 }
1219
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001220 private static void writeEntry(BufferedWriter out, String prefix, String name,
1221 String suffix, Object value) throws IOException {
1222
1223 out.write(prefix);
1224 out.write(name);
1225 out.write(suffix);
1226 out.write("=");
1227 writeValue(out, value);
1228 out.write(' ');
1229 }
1230
Romain Guy809a7f62009-05-14 15:44:42 -07001231 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1232 int intValue, String prefix) throws IOException {
1233
1234 final int count = mapping.length;
1235 for (int j = 0; j < count; j++) {
1236 final FlagToString flagMapping = mapping[j];
1237 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001238 final int maskResult = intValue & flagMapping.mask();
1239 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001240 if ((test && ifTrue) || (!test && !ifTrue)) {
1241 final String name = flagMapping.name();
Romain Guy5bcdff42009-05-14 21:27:18 -07001242 final String value = "0x" + Integer.toHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001243 writeEntry(out, prefix, name, "", value);
1244 }
1245 }
1246 }
1247
The Android Open Source Project10592532009-03-18 17:39:46 -07001248 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001249 ExportedProperty property, int[] array, String prefix, String suffix)
1250 throws IOException {
1251
1252 final IntToString[] indexMapping = property.indexMapping();
1253 final boolean hasIndexMapping = indexMapping.length > 0;
1254
1255 final IntToString[] mapping = property.mapping();
1256 final boolean hasMapping = mapping.length > 0;
1257
The Android Open Source Project10592532009-03-18 17:39:46 -07001258 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001259 final int valuesCount = array.length;
1260
1261 for (int j = 0; j < valuesCount; j++) {
1262 String name;
1263 String value;
1264
1265 final int intValue = array[j];
1266
1267 name = String.valueOf(j);
1268 if (hasIndexMapping) {
1269 int mappingCount = indexMapping.length;
1270 for (int k = 0; k < mappingCount; k++) {
1271 final IntToString mapped = indexMapping[k];
1272 if (mapped.from() == j) {
1273 name = mapped.to();
1274 break;
1275 }
1276 }
1277 }
1278
1279 value = String.valueOf(intValue);
1280 if (hasMapping) {
1281 int mappingCount = mapping.length;
1282 for (int k = 0; k < mappingCount; k++) {
1283 final IntToString mapped = mapping[k];
1284 if (mapped.from() == intValue) {
1285 value = mapped.to();
1286 break;
1287 }
1288 }
1289 }
1290
1291 if (resolveId) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001292 value = (String) resolveId(context, intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001293 }
1294
1295 writeEntry(out, prefix, name, suffix, value);
1296 }
1297 }
1298
The Android Open Source Project10592532009-03-18 17:39:46 -07001299 private static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001300 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001301 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001302 if (id >= 0) {
1303 try {
1304 fieldValue = resources.getResourceTypeName(id) + '/' +
1305 resources.getResourceEntryName(id);
1306 } catch (Resources.NotFoundException e) {
1307 fieldValue = "id/0x" + Integer.toHexString(id);
1308 }
1309 } else {
1310 fieldValue = "NO_ID";
1311 }
1312 return fieldValue;
1313 }
1314
1315 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1316 if (value != null) {
1317 String output = value.toString().replace("\n", "\\n");
1318 out.write(String.valueOf(output.length()));
1319 out.write(",");
1320 out.write(output);
1321 } else {
1322 out.write("4,null");
1323 }
1324 }
1325
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326 private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
1327 if (!dumpView(group, out, level)) {
1328 return;
1329 }
1330
1331 final int count = group.getChildCount();
1332 for (int i = 0; i < count; i++) {
1333 final View view = group.getChildAt(i);
1334 if (view instanceof ViewGroup) {
1335 dumpViewHierarchy((ViewGroup) view, out, level + 1);
1336 } else {
1337 dumpView(view, out, level + 1);
1338 }
1339 }
1340 }
1341
1342 private static boolean dumpView(Object view, BufferedWriter out, int level) {
1343 try {
1344 for (int i = 0; i < level; i++) {
1345 out.write(' ');
1346 }
1347 out.write(view.getClass().getName());
1348 out.write('@');
1349 out.write(Integer.toHexString(view.hashCode()));
1350 out.newLine();
1351 } catch (IOException e) {
1352 Log.w("View", "Error while dumping hierarchy tree");
1353 return false;
1354 }
1355 return true;
1356 }
1357
1358 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1359 if (mCapturedViewFieldsForClasses == null) {
1360 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1361 }
1362 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1363
1364 Field[] fields = map.get(klass);
1365 if (fields != null) {
1366 return fields;
1367 }
1368
1369 final ArrayList<Field> foundFields = new ArrayList<Field>();
1370 fields = klass.getFields();
1371
1372 int count = fields.length;
1373 for (int i = 0; i < count; i++) {
1374 final Field field = fields[i];
1375 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1376 field.setAccessible(true);
1377 foundFields.add(field);
1378 }
1379 }
1380
1381 fields = foundFields.toArray(new Field[foundFields.size()]);
1382 map.put(klass, fields);
1383
1384 return fields;
1385 }
1386
1387 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1388 if (mCapturedViewMethodsForClasses == null) {
1389 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1390 }
1391 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1392
1393 Method[] methods = map.get(klass);
1394 if (methods != null) {
1395 return methods;
1396 }
1397
1398 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1399 methods = klass.getMethods();
1400
1401 int count = methods.length;
1402 for (int i = 0; i < count; i++) {
1403 final Method method = methods[i];
1404 if (method.getParameterTypes().length == 0 &&
1405 method.isAnnotationPresent(CapturedViewProperty.class) &&
1406 method.getReturnType() != Void.class) {
1407 method.setAccessible(true);
1408 foundMethods.add(method);
1409 }
1410 }
1411
1412 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1413 map.put(klass, methods);
1414
1415 return methods;
1416 }
1417
1418 private static String capturedViewExportMethods(Object obj, Class<?> klass,
1419 String prefix) {
1420
1421 if (obj == null) {
1422 return "null";
1423 }
1424
1425 StringBuilder sb = new StringBuilder();
1426 final Method[] methods = capturedViewGetPropertyMethods(klass);
1427
1428 int count = methods.length;
1429 for (int i = 0; i < count; i++) {
1430 final Method method = methods[i];
1431 try {
1432 Object methodValue = method.invoke(obj, (Object[]) null);
1433 final Class<?> returnType = method.getReturnType();
1434
1435 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1436 if (property.retrieveReturn()) {
1437 //we are interested in the second level data only
1438 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
1439 } else {
1440 sb.append(prefix);
1441 sb.append(method.getName());
1442 sb.append("()=");
1443
1444 if (methodValue != null) {
1445 final String value = methodValue.toString().replace("\n", "\\n");
1446 sb.append(value);
1447 } else {
1448 sb.append("null");
1449 }
1450 sb.append("; ");
1451 }
1452 } catch (IllegalAccessException e) {
1453 //Exception IllegalAccess, it is OK here
1454 //we simply ignore this method
1455 } catch (InvocationTargetException e) {
1456 //Exception InvocationTarget, it is OK here
1457 //we simply ignore this method
1458 }
1459 }
1460 return sb.toString();
1461 }
1462
1463 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
1464
1465 if (obj == null) {
1466 return "null";
1467 }
1468
1469 StringBuilder sb = new StringBuilder();
1470 final Field[] fields = capturedViewGetPropertyFields(klass);
1471
1472 int count = fields.length;
1473 for (int i = 0; i < count; i++) {
1474 final Field field = fields[i];
1475 try {
1476 Object fieldValue = field.get(obj);
1477
1478 sb.append(prefix);
1479 sb.append(field.getName());
1480 sb.append("=");
1481
1482 if (fieldValue != null) {
1483 final String value = fieldValue.toString().replace("\n", "\\n");
1484 sb.append(value);
1485 } else {
1486 sb.append("null");
1487 }
1488 sb.append(' ');
1489 } catch (IllegalAccessException e) {
1490 //Exception IllegalAccess, it is OK here
1491 //we simply ignore this field
1492 }
1493 }
1494 return sb.toString();
1495 }
1496
1497 /**
Andy Stadlerf8a7cea2009-04-10 16:24:47 -07001498 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001499 * (and possibly further data analysis). The results are dumped
1500 * to the log.
1501 * @param tag for log
1502 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 */
1504 public static void dumpCapturedView(String tag, Object view) {
1505 Class<?> klass = view.getClass();
1506 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1507 sb.append(capturedViewExportFields(view, klass, ""));
1508 sb.append(capturedViewExportMethods(view, klass, ""));
1509 Log.d(tag, sb.toString());
1510 }
1511}