blob: 367c9a22d953c9e1cf50f94f224078d700146d0e [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 /**
57 * Enables or disables view hierarchy tracing. Any invoker of
58 * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
59 * check that this value is set to true as not to affect performance.
60 */
61 public static final boolean TRACE_HIERARCHY = false;
62
63 /**
64 * Enables or disables view recycler tracing. Any invoker of
65 * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
66 * check that this value is set to true as not to affect performance.
67 */
68 public static final boolean TRACE_RECYCLER = false;
69
70 /**
71 * The system property of dynamic switch for capturing view information
72 * when it is set, we dump interested fields and methods for the view on focus
73 */
74 static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
75
76 /**
77 * The system property of dynamic switch for capturing event information
78 * when it is set, we log key events, touch/motion and trackball events
79 */
80 static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 /**
83 * This annotation can be used to mark fields and methods to be dumped by
84 * the view server. Only non-void methods with no arguments can be annotated
85 * by this annotation.
86 */
87 @Target({ ElementType.FIELD, ElementType.METHOD })
88 @Retention(RetentionPolicy.RUNTIME)
89 public @interface ExportedProperty {
90 /**
91 * When resolveId is true, and if the annotated field/method return value
92 * is an int, the value is converted to an Android's resource name.
93 *
94 * @return true if the property's value must be transformed into an Android
95 * resource name, false otherwise
96 */
97 boolean resolveId() default false;
98
99 /**
100 * A mapping can be defined to map int values to specific strings. For
101 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
102 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
103 * these human readable values:
104 *
105 * <pre>
106 * @ViewDebug.ExportedProperty(mapping = {
107 * @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
108 * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
109 * @ViewDebug.IntToString(from = 8, to = "GONE")
110 * })
111 * public int getVisibility() { ...
112 * <pre>
113 *
114 * @return An array of int to String mappings
115 *
116 * @see android.view.ViewDebug.IntToString
117 */
118 IntToString[] mapping() default { };
119
120 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700121 * A mapping can be defined to map array indices to specific strings.
122 * A mapping can be used to see human readable values for the indices
123 * of an array:
124 *
125 * <pre>
126 * @ViewDebug.ExportedProperty(mapping = {
127 * @ViewDebug.IntToString(from = 0, to = "INVALID"),
128 * @ViewDebug.IntToString(from = 1, to = "FIRST"),
129 * @ViewDebug.IntToString(from = 2, to = "SECOND")
130 * })
131 * private int[] mElements;
132 * <pre>
133 *
134 * @return An array of int to String mappings
135 *
136 * @see android.view.ViewDebug.IntToString
137 * @see #mapping()
138 */
139 IntToString[] indexMapping() default { };
140
141 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 * When deep export is turned on, this property is not dumped. Instead, the
143 * properties contained in this property are dumped. Each child property
144 * is prefixed with the name of this property.
145 *
146 * @return true if the properties of this property should be dumped
147 *
148 * @see #prefix()
149 */
150 boolean deepExport() default false;
151
152 /**
153 * The prefix to use on child properties when deep export is enabled
154 *
155 * @return a prefix as a String
156 *
157 * @see #deepExport()
158 */
159 String prefix() default "";
160 }
161
162 /**
163 * Defines a mapping from an int value to a String. Such a mapping can be used
164 * in a @ExportedProperty to provide more meaningful values to the end user.
165 *
166 * @see android.view.ViewDebug.ExportedProperty
167 */
168 @Target({ ElementType.TYPE })
169 @Retention(RetentionPolicy.RUNTIME)
170 public @interface IntToString {
171 /**
172 * The original int value to map to a String.
173 *
174 * @return An arbitrary int value.
175 */
176 int from();
177
178 /**
179 * The String to use in place of the original int value.
180 *
181 * @return An arbitrary non-null String.
182 */
183 String to();
184 }
185
186 /**
187 * This annotation can be used to mark fields and methods to be dumped when
188 * the view is captured. Methods with this annotation must have no arguments
Dianne Hackborn935ae462009-04-13 16:11:55 -0700189 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 */
191 @Target({ ElementType.FIELD, ElementType.METHOD })
192 @Retention(RetentionPolicy.RUNTIME)
193 public @interface CapturedViewProperty {
194 /**
195 * When retrieveReturn is true, we need to retrieve second level methods
196 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
197 * we will set retrieveReturn = true on the annotation of
198 * myView.getFirstLevelMethod()
199 * @return true if we need the second level methods
200 */
201 boolean retrieveReturn() default false;
202 }
203
204 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
205 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
206
207 // Maximum delay in ms after which we stop trying to capture a View's drawing
208 private static final int CAPTURE_TIMEOUT = 4000;
209
210 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
211 private static final String REMOTE_COMMAND_DUMP = "DUMP";
212 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
213 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700214 private static final String REMOTE_PROFILE = "PROFILE";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215
216 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
217 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700218 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 /**
221 * Defines the type of hierarhcy trace to output to the hierarchy traces file.
222 */
223 public enum HierarchyTraceType {
224 INVALIDATE,
225 INVALIDATE_CHILD,
226 INVALIDATE_CHILD_IN_PARENT,
227 REQUEST_LAYOUT,
228 ON_LAYOUT,
229 ON_MEASURE,
230 DRAW,
231 BUILD_CACHE
232 }
233
234 private static BufferedWriter sHierarchyTraces;
235 private static ViewRoot sHierarhcyRoot;
236 private static String sHierarchyTracePrefix;
237
238 /**
239 * Defines the type of recycler trace to output to the recycler traces file.
240 */
241 public enum RecyclerTraceType {
242 NEW_VIEW,
243 BIND_VIEW,
244 RECYCLE_FROM_ACTIVE_HEAP,
245 RECYCLE_FROM_SCRAP_HEAP,
246 MOVE_TO_ACTIVE_HEAP,
247 MOVE_TO_SCRAP_HEAP,
248 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
249 }
250
251 private static class RecyclerTrace {
252 public int view;
253 public RecyclerTraceType type;
254 public int position;
255 public int indexOnScreen;
256 }
257
258 private static View sRecyclerOwnerView;
259 private static List<View> sRecyclerViews;
260 private static List<RecyclerTrace> sRecyclerTraces;
261 private static String sRecyclerTracePrefix;
262
263 /**
264 * Returns the number of instanciated Views.
265 *
266 * @return The number of Views instanciated in the current process.
267 *
268 * @hide
269 */
270 public static long getViewInstanceCount() {
271 return View.sInstanceCount;
272 }
273
274 /**
275 * Returns the number of instanciated ViewRoots.
276 *
277 * @return The number of ViewRoots instanciated in the current process.
278 *
279 * @hide
280 */
281 public static long getViewRootInstanceCount() {
282 return ViewRoot.getInstanceCount();
283 }
284
285 /**
286 * Outputs a trace to the currently opened recycler traces. The trace records the type of
287 * recycler action performed on the supplied view as well as a number of parameters.
288 *
289 * @param view the view to trace
290 * @param type the type of the trace
291 * @param parameters parameters depending on the type of the trace
292 */
293 public static void trace(View view, RecyclerTraceType type, int... parameters) {
294 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
295 return;
296 }
297
298 if (!sRecyclerViews.contains(view)) {
299 sRecyclerViews.add(view);
300 }
301
302 final int index = sRecyclerViews.indexOf(view);
303
304 RecyclerTrace trace = new RecyclerTrace();
305 trace.view = index;
306 trace.type = type;
307 trace.position = parameters[0];
308 trace.indexOnScreen = parameters[1];
309
310 sRecyclerTraces.add(trace);
311 }
312
313 /**
314 * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
315 * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
316 * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
317 *
318 * Only one view recycler can be traced at the same time. After calling this method, any
319 * other invocation will result in a <code>IllegalStateException</code> unless
320 * {@link #stopRecyclerTracing()} is invoked before.
321 *
322 * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
323 *
324 * This method will return immediately if TRACE_RECYCLER is false.
325 *
326 * @param prefix the traces files name prefix
327 * @param view the view whose recycler must be traced
328 *
329 * @see #stopRecyclerTracing()
330 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
331 */
332 public static void startRecyclerTracing(String prefix, View view) {
333 //noinspection PointlessBooleanExpression,ConstantConditions
334 if (!TRACE_RECYCLER) {
335 return;
336 }
337
338 if (sRecyclerOwnerView != null) {
339 throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
340 " a new trace!");
341 }
342
343 sRecyclerTracePrefix = prefix;
344 sRecyclerOwnerView = view;
345 sRecyclerViews = new ArrayList<View>();
346 sRecyclerTraces = new LinkedList<RecyclerTrace>();
347 }
348
349 /**
350 * Stops the current view recycer tracing.
351 *
352 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
353 * containing all the traces (or method calls) relative to the specified view's recycler.
354 *
355 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
356 * containing all of the views used by the recycler of the view supplied to
357 * {@link #startRecyclerTracing(String, View)}.
358 *
359 * This method will return immediately if TRACE_RECYCLER is false.
360 *
361 * @see #startRecyclerTracing(String, View)
362 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
363 */
364 public static void stopRecyclerTracing() {
365 //noinspection PointlessBooleanExpression,ConstantConditions
366 if (!TRACE_RECYCLER) {
367 return;
368 }
369
370 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
371 throw new IllegalStateException("You must call startRecyclerTracing() before" +
372 " stopRecyclerTracing()!");
373 }
374
375 File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700376 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 recyclerDump.mkdirs();
378
379 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
380 try {
381 final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
382
383 for (View view : sRecyclerViews) {
384 final String name = view.getClass().getName();
385 out.write(name);
386 out.newLine();
387 }
388
389 out.close();
390 } catch (IOException e) {
391 Log.e("View", "Could not dump recycler content");
392 return;
393 }
394
395 recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
396 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
397 try {
398 final FileOutputStream file = new FileOutputStream(recyclerDump);
399 final DataOutputStream out = new DataOutputStream(file);
400
401 for (RecyclerTrace trace : sRecyclerTraces) {
402 out.writeInt(trace.view);
403 out.writeInt(trace.type.ordinal());
404 out.writeInt(trace.position);
405 out.writeInt(trace.indexOnScreen);
406 out.flush();
407 }
408
409 out.close();
410 } catch (IOException e) {
411 Log.e("View", "Could not dump recycler traces");
412 return;
413 }
414
415 sRecyclerViews.clear();
416 sRecyclerViews = null;
417
418 sRecyclerTraces.clear();
419 sRecyclerTraces = null;
420
421 sRecyclerOwnerView = null;
422 }
423
424 /**
425 * Outputs a trace to the currently opened traces file. The trace contains the class name
426 * and instance's hashcode of the specified view as well as the supplied trace type.
427 *
428 * @param view the view to trace
429 * @param type the type of the trace
430 */
431 public static void trace(View view, HierarchyTraceType type) {
432 if (sHierarchyTraces == null) {
433 return;
434 }
435
436 try {
437 sHierarchyTraces.write(type.name());
438 sHierarchyTraces.write(' ');
439 sHierarchyTraces.write(view.getClass().getName());
440 sHierarchyTraces.write('@');
441 sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
442 sHierarchyTraces.newLine();
443 } catch (IOException e) {
444 Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
445 }
446 }
447
448 /**
449 * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
450 * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
451 * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
452 *
453 * Only one view hierarchy can be traced at the same time. After calling this method, any
454 * other invocation will result in a <code>IllegalStateException</code> unless
455 * {@link #stopHierarchyTracing()} is invoked before.
456 *
457 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
458 * containing all the traces (or method calls) relative to the specified view's hierarchy.
459 *
460 * This method will return immediately if TRACE_HIERARCHY is false.
461 *
462 * @param prefix the traces files name prefix
463 * @param view the view whose hierarchy must be traced
464 *
465 * @see #stopHierarchyTracing()
466 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
467 */
468 public static void startHierarchyTracing(String prefix, View view) {
469 //noinspection PointlessBooleanExpression,ConstantConditions
470 if (!TRACE_HIERARCHY) {
471 return;
472 }
473
474 if (sHierarhcyRoot != null) {
475 throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
476 " a new trace!");
477 }
478
479 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700480 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800481 hierarchyDump.mkdirs();
482
483 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
484 sHierarchyTracePrefix = prefix;
485
486 try {
487 sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
488 } catch (IOException e) {
489 Log.e("View", "Could not dump view hierarchy");
490 return;
491 }
492
493 sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
494 }
495
496 /**
497 * Stops the current view hierarchy tracing. This method closes the file
498 * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
499 *
500 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
501 * containing the view hierarchy of the view supplied to
502 * {@link #startHierarchyTracing(String, View)}.
503 *
504 * This method will return immediately if TRACE_HIERARCHY is false.
505 *
506 * @see #startHierarchyTracing(String, View)
507 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
508 */
509 public static void stopHierarchyTracing() {
510 //noinspection PointlessBooleanExpression,ConstantConditions
511 if (!TRACE_HIERARCHY) {
512 return;
513 }
514
515 if (sHierarhcyRoot == null || sHierarchyTraces == null) {
516 throw new IllegalStateException("You must call startHierarchyTracing() before" +
517 " stopHierarchyTracing()!");
518 }
519
520 try {
521 sHierarchyTraces.close();
522 } catch (IOException e) {
523 Log.e("View", "Could not write view traces");
524 }
525 sHierarchyTraces = null;
526
527 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700528 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 hierarchyDump.mkdirs();
530 hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
531
532 BufferedWriter out;
533 try {
534 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
535 } catch (IOException e) {
536 Log.e("View", "Could not dump view hierarchy");
537 return;
538 }
539
540 View view = sHierarhcyRoot.getView();
541 if (view instanceof ViewGroup) {
542 ViewGroup group = (ViewGroup) view;
543 dumpViewHierarchy(group, out, 0);
544 try {
545 out.close();
546 } catch (IOException e) {
547 Log.e("View", "Could not dump view hierarchy");
548 }
549 }
550
551 sHierarhcyRoot = null;
552 }
553
554 static void dispatchCommand(View view, String command, String parameters,
555 OutputStream clientStream) throws IOException {
556
557 // Paranoid but safe...
558 view = view.getRootView();
559
560 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
561 dump(view, clientStream);
562 } else {
563 final String[] params = parameters.split(" ");
564 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
565 capture(view, clientStream, params[0]);
566 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
567 invalidate(view, params[0]);
568 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
569 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700570 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
571 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800572 }
573 }
574 }
575
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700576 private static View findView(View root, String parameter) {
577 // Look by type/hashcode
578 if (parameter.indexOf('@') != -1) {
579 final String[] ids = parameter.split("@");
580 final String className = ids[0];
581 final int hashCode = Integer.parseInt(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700583 View view = root.getRootView();
584 if (view instanceof ViewGroup) {
585 return findView((ViewGroup) view, className, hashCode);
586 }
587 } else {
588 // Look by id
589 final int id = root.getResources().getIdentifier(parameter, null, null);
590 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800591 }
592
593 return null;
594 }
595
596 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700597 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598 if (view != null) {
599 view.postInvalidate();
600 }
601 }
602
603 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700604 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 if (view != null) {
606 root.post(new Runnable() {
607 public void run() {
608 view.requestLayout();
609 }
610 });
611 }
612 }
613
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700614 private static void profile(View root, OutputStream clientStream, String parameter)
615 throws IOException {
616
617 final View view = findView(root, parameter);
618 BufferedWriter out = null;
619 try {
620 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
621
622 if (view != null) {
623 final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
624 public Void[] pre() {
625 forceLayout(view);
626 return null;
627 }
628
629 private void forceLayout(View view) {
630 view.forceLayout();
631 if (view instanceof ViewGroup) {
632 ViewGroup group = (ViewGroup) view;
633 final int count = group.getChildCount();
634 for (int i = 0; i < count; i++) {
635 forceLayout(group.getChildAt(i));
636 }
637 }
638 }
639
640 public void run(Void... data) {
641 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
642 }
643
644 public void post(Void... data) {
645 }
646 });
647
648 final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
649 public Void[] pre() {
650 return null;
651 }
652
653 public void run(Void... data) {
654 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
655 }
656
657 public void post(Void... data) {
658 }
659 });
660
661 final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
662 public Object[] pre() {
663 final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
664 final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700665 metrics.heightPixels, Bitmap.Config.RGB_565);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700666 final Canvas canvas = new Canvas(bitmap);
667 return new Object[] { bitmap, canvas };
668 }
669
670 public void run(Object... data) {
671 view.draw((Canvas) data[1]);
672 }
673
674 public void post(Object... data) {
675 ((Bitmap) data[0]).recycle();
676 }
677 });
678
679 out.write(String.valueOf(durationMeasure));
680 out.write(' ');
681 out.write(String.valueOf(durationLayout));
682 out.write(' ');
683 out.write(String.valueOf(durationDraw));
684 out.newLine();
685 } else {
686 out.write("-1 -1 -1");
687 out.newLine();
688 }
689 } catch (Exception e) {
690 android.util.Log.w("View", "Problem profiling the view:", e);
691 } finally {
692 if (out != null) {
693 out.close();
694 }
695 }
696 }
697
698 interface ViewOperation<T> {
699 T[] pre();
700 void run(T... data);
701 void post(T... data);
702 }
703
704 private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
705 final CountDownLatch latch = new CountDownLatch(1);
706 final long[] duration = new long[1];
707
708 view.post(new Runnable() {
709 public void run() {
710 try {
711 T[] data = operation.pre();
712 long start = Debug.threadCpuTimeNanos();
713 operation.run(data);
714 duration[0] = Debug.threadCpuTimeNanos() - start;
715 operation.post(data);
716 } finally {
717 latch.countDown();
718 }
719 }
720 });
721
722 try {
723 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
724 } catch (InterruptedException e) {
725 Log.w("View", "Could not complete the profiling of the view " + view);
726 Thread.currentThread().interrupt();
727 return -1;
728 }
729
730 return duration[0];
731 }
732
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 private static void capture(View root, final OutputStream clientStream, String parameter)
734 throws IOException {
735
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700736 final View captureView = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737
738 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700739 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800740 final Bitmap[] cache = new Bitmap[1];
741
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 root.post(new Runnable() {
743 public void run() {
744 try {
Dianne Hackborn7ac3f672009-03-31 18:00:53 -0700745 cache[0] = captureView.createSnapshot(
746 Bitmap.Config.ARGB_8888, 0);
747 } catch (OutOfMemoryError e) {
748 try {
749 cache[0] = captureView.createSnapshot(
750 Bitmap.Config.ARGB_4444, 0);
751 } catch (OutOfMemoryError e2) {
752 Log.w("View", "Out of memory for bitmap");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 } finally {
755 latch.countDown();
756 }
757 }
758 });
759
760 try {
761 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
762
763 if (cache[0] != null) {
764 BufferedOutputStream out = null;
765 try {
766 out = new BufferedOutputStream(clientStream, 32 * 1024);
767 cache[0].compress(Bitmap.CompressFormat.PNG, 100, out);
768 out.flush();
769 } finally {
770 if (out != null) {
771 out.close();
772 }
Dianne Hackborn7ac3f672009-03-31 18:00:53 -0700773 cache[0].recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800774 }
Dianne Hackborn7ac3f672009-03-31 18:00:53 -0700775 } else {
776 Log.w("View", "Failed to create capture bitmap!");
777 clientStream.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 }
779 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700780 Log.w("View", "Could not complete the capture of the view " + captureView);
781 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800782 }
783 }
784 }
785
786 private static void dump(View root, OutputStream clientStream) throws IOException {
787 BufferedWriter out = null;
788 try {
789 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
790 View view = root.getRootView();
791 if (view instanceof ViewGroup) {
792 ViewGroup group = (ViewGroup) view;
The Android Open Source Project10592532009-03-18 17:39:46 -0700793 dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 }
795 out.write("DONE.");
796 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700797 } catch (Exception e) {
798 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 } finally {
800 if (out != null) {
801 out.close();
802 }
803 }
804 }
805
806 private static View findView(ViewGroup group, String className, int hashCode) {
807 if (isRequestedView(group, className, hashCode)) {
808 return group;
809 }
810
811 final int count = group.getChildCount();
812 for (int i = 0; i < count; i++) {
813 final View view = group.getChildAt(i);
814 if (view instanceof ViewGroup) {
815 final View found = findView((ViewGroup) view, className, hashCode);
816 if (found != null) {
817 return found;
818 }
819 } else if (isRequestedView(view, className, hashCode)) {
820 return view;
821 }
822 }
823
824 return null;
825 }
826
827 private static boolean isRequestedView(View view, String className, int hashCode) {
828 return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
829 }
830
The Android Open Source Project10592532009-03-18 17:39:46 -0700831 private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 BufferedWriter out, int level) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700833 if (!dumpViewWithProperties(context, group, out, level)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 return;
835 }
836
837 final int count = group.getChildCount();
838 for (int i = 0; i < count; i++) {
839 final View view = group.getChildAt(i);
840 if (view instanceof ViewGroup) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700841 dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -0700843 dumpViewWithProperties(context, view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800844 }
845 }
846 }
847
The Android Open Source Project10592532009-03-18 17:39:46 -0700848 private static boolean dumpViewWithProperties(Context context, View view,
849 BufferedWriter out, int level) {
850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 try {
852 for (int i = 0; i < level; i++) {
853 out.write(' ');
854 }
855 out.write(view.getClass().getName());
856 out.write('@');
857 out.write(Integer.toHexString(view.hashCode()));
858 out.write(' ');
The Android Open Source Project10592532009-03-18 17:39:46 -0700859 dumpViewProperties(context, view, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 out.newLine();
861 } catch (IOException e) {
862 Log.w("View", "Error while dumping hierarchy tree");
863 return false;
864 }
865 return true;
866 }
867
868 private static Field[] getExportedPropertyFields(Class<?> klass) {
869 if (sFieldsForClasses == null) {
870 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
871 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700872 if (sAnnotations == null) {
873 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
874 }
875
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700877 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878
879 Field[] fields = map.get(klass);
880 if (fields != null) {
881 return fields;
882 }
883
884 final ArrayList<Field> foundFields = new ArrayList<Field>();
885 fields = klass.getDeclaredFields();
886
887 int count = fields.length;
888 for (int i = 0; i < count; i++) {
889 final Field field = fields[i];
890 if (field.isAnnotationPresent(ExportedProperty.class)) {
891 field.setAccessible(true);
892 foundFields.add(field);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700893 annotations.put(field, field.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 }
895 }
896
897 fields = foundFields.toArray(new Field[foundFields.size()]);
898 map.put(klass, fields);
899
900 return fields;
901 }
902
903 private static Method[] getExportedPropertyMethods(Class<?> klass) {
904 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700905 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800906 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700907 if (sAnnotations == null) {
908 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
909 }
910
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700912 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800913
914 Method[] methods = map.get(klass);
915 if (methods != null) {
916 return methods;
917 }
918
919 final ArrayList<Method> foundMethods = new ArrayList<Method>();
920 methods = klass.getDeclaredMethods();
921
922 int count = methods.length;
923 for (int i = 0; i < count; i++) {
924 final Method method = methods[i];
925 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700926 method.isAnnotationPresent(ExportedProperty.class) &&
927 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800928 method.setAccessible(true);
929 foundMethods.add(method);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700930 annotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 }
932 }
933
934 methods = foundMethods.toArray(new Method[foundMethods.size()]);
935 map.put(klass, methods);
936
937 return methods;
938 }
939
The Android Open Source Project10592532009-03-18 17:39:46 -0700940 private static void dumpViewProperties(Context context, Object view,
941 BufferedWriter out) throws IOException {
942
943 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 }
945
The Android Open Source Project10592532009-03-18 17:39:46 -0700946 private static void dumpViewProperties(Context context, Object view,
947 BufferedWriter out, String prefix) throws IOException {
948
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800949 Class<?> klass = view.getClass();
950
951 do {
The Android Open Source Project10592532009-03-18 17:39:46 -0700952 exportFields(context, view, out, klass, prefix);
953 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800954 klass = klass.getSuperclass();
955 } while (klass != Object.class);
956 }
957
The Android Open Source Project10592532009-03-18 17:39:46 -0700958 private static void exportMethods(Context context, Object view, BufferedWriter out,
959 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960
961 final Method[] methods = getExportedPropertyMethods(klass);
962
963 int count = methods.length;
964 for (int i = 0; i < count; i++) {
965 final Method method = methods[i];
966 //noinspection EmptyCatchBlock
967 try {
968 // TODO: This should happen on the UI thread
969 Object methodValue = method.invoke(view, (Object[]) null);
970 final Class<?> returnType = method.getReturnType();
971
972 if (returnType == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700973 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project10592532009-03-18 17:39:46 -0700974 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -0700976 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800977 } else {
978 final IntToString[] mapping = property.mapping();
979 if (mapping.length > 0) {
980 final int intValue = (Integer) methodValue;
981 boolean mapped = false;
982 int mappingCount = mapping.length;
983 for (int j = 0; j < mappingCount; j++) {
984 final IntToString mapper = mapping[j];
985 if (mapper.from() == intValue) {
986 methodValue = mapper.to();
987 mapped = true;
988 break;
989 }
990 }
991
992 if (!mapped) {
993 methodValue = intValue;
994 }
995 }
996 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700997 } else if (returnType == int[].class) {
998 final ExportedProperty property = sAnnotations.get(method);
999 final int[] array = (int[]) methodValue;
1000 final String valuePrefix = prefix + method.getName() + '_';
1001 final String suffix = "()";
1002
The Android Open Source Project10592532009-03-18 17:39:46 -07001003 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 } else if (!returnType.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001005 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001007 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 continue;
1009 }
1010 }
1011
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001012 writeEntry(out, prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001013 } catch (IllegalAccessException e) {
1014 } catch (InvocationTargetException e) {
1015 }
1016 }
1017 }
1018
The Android Open Source Project10592532009-03-18 17:39:46 -07001019 private static void exportFields(Context context, Object view, BufferedWriter out,
1020 Class<?> klass, String prefix) throws IOException {
1021
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001022 final Field[] fields = getExportedPropertyFields(klass);
1023
1024 int count = fields.length;
1025 for (int i = 0; i < count; i++) {
1026 final Field field = fields[i];
1027
1028 //noinspection EmptyCatchBlock
1029 try {
1030 Object fieldValue = null;
1031 final Class<?> type = field.getType();
1032
1033 if (type == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001034 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project10592532009-03-18 17:39:46 -07001035 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001037 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 } else {
1039 final IntToString[] mapping = property.mapping();
1040 if (mapping.length > 0) {
1041 final int intValue = field.getInt(view);
1042 int mappingCount = mapping.length;
1043 for (int j = 0; j < mappingCount; j++) {
1044 final IntToString mapped = mapping[j];
1045 if (mapped.from() == intValue) {
1046 fieldValue = mapped.to();
1047 break;
1048 }
1049 }
1050
1051 if (fieldValue == null) {
1052 fieldValue = intValue;
1053 }
1054 }
1055 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001056 } else if (type == int[].class) {
1057 final ExportedProperty property = sAnnotations.get(field);
1058 final int[] array = (int[]) field.get(view);
1059 final String valuePrefix = prefix + field.getName() + '_';
1060 final String suffix = "";
1061
The Android Open Source Project10592532009-03-18 17:39:46 -07001062 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001063
1064 // We exit here!
1065 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 } else if (!type.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001067 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001069 dumpViewProperties(context, field.get(view), out,
1070 prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 continue;
1072 }
1073 }
1074
1075 if (fieldValue == null) {
1076 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 }
1078
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001079 writeEntry(out, prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 } catch (IllegalAccessException e) {
1081 }
1082 }
1083 }
1084
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001085 private static void writeEntry(BufferedWriter out, String prefix, String name,
1086 String suffix, Object value) throws IOException {
1087
1088 out.write(prefix);
1089 out.write(name);
1090 out.write(suffix);
1091 out.write("=");
1092 writeValue(out, value);
1093 out.write(' ');
1094 }
1095
The Android Open Source Project10592532009-03-18 17:39:46 -07001096 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001097 ExportedProperty property, int[] array, String prefix, String suffix)
1098 throws IOException {
1099
1100 final IntToString[] indexMapping = property.indexMapping();
1101 final boolean hasIndexMapping = indexMapping.length > 0;
1102
1103 final IntToString[] mapping = property.mapping();
1104 final boolean hasMapping = mapping.length > 0;
1105
The Android Open Source Project10592532009-03-18 17:39:46 -07001106 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001107 final int valuesCount = array.length;
1108
1109 for (int j = 0; j < valuesCount; j++) {
1110 String name;
1111 String value;
1112
1113 final int intValue = array[j];
1114
1115 name = String.valueOf(j);
1116 if (hasIndexMapping) {
1117 int mappingCount = indexMapping.length;
1118 for (int k = 0; k < mappingCount; k++) {
1119 final IntToString mapped = indexMapping[k];
1120 if (mapped.from() == j) {
1121 name = mapped.to();
1122 break;
1123 }
1124 }
1125 }
1126
1127 value = String.valueOf(intValue);
1128 if (hasMapping) {
1129 int mappingCount = mapping.length;
1130 for (int k = 0; k < mappingCount; k++) {
1131 final IntToString mapped = mapping[k];
1132 if (mapped.from() == intValue) {
1133 value = mapped.to();
1134 break;
1135 }
1136 }
1137 }
1138
1139 if (resolveId) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001140 value = (String) resolveId(context, intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001141 }
1142
1143 writeEntry(out, prefix, name, suffix, value);
1144 }
1145 }
1146
The Android Open Source Project10592532009-03-18 17:39:46 -07001147 private static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001148 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001149 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001150 if (id >= 0) {
1151 try {
1152 fieldValue = resources.getResourceTypeName(id) + '/' +
1153 resources.getResourceEntryName(id);
1154 } catch (Resources.NotFoundException e) {
1155 fieldValue = "id/0x" + Integer.toHexString(id);
1156 }
1157 } else {
1158 fieldValue = "NO_ID";
1159 }
1160 return fieldValue;
1161 }
1162
1163 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1164 if (value != null) {
1165 String output = value.toString().replace("\n", "\\n");
1166 out.write(String.valueOf(output.length()));
1167 out.write(",");
1168 out.write(output);
1169 } else {
1170 out.write("4,null");
1171 }
1172 }
1173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
1175 if (!dumpView(group, out, level)) {
1176 return;
1177 }
1178
1179 final int count = group.getChildCount();
1180 for (int i = 0; i < count; i++) {
1181 final View view = group.getChildAt(i);
1182 if (view instanceof ViewGroup) {
1183 dumpViewHierarchy((ViewGroup) view, out, level + 1);
1184 } else {
1185 dumpView(view, out, level + 1);
1186 }
1187 }
1188 }
1189
1190 private static boolean dumpView(Object view, BufferedWriter out, int level) {
1191 try {
1192 for (int i = 0; i < level; i++) {
1193 out.write(' ');
1194 }
1195 out.write(view.getClass().getName());
1196 out.write('@');
1197 out.write(Integer.toHexString(view.hashCode()));
1198 out.newLine();
1199 } catch (IOException e) {
1200 Log.w("View", "Error while dumping hierarchy tree");
1201 return false;
1202 }
1203 return true;
1204 }
1205
1206 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1207 if (mCapturedViewFieldsForClasses == null) {
1208 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1209 }
1210 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1211
1212 Field[] fields = map.get(klass);
1213 if (fields != null) {
1214 return fields;
1215 }
1216
1217 final ArrayList<Field> foundFields = new ArrayList<Field>();
1218 fields = klass.getFields();
1219
1220 int count = fields.length;
1221 for (int i = 0; i < count; i++) {
1222 final Field field = fields[i];
1223 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1224 field.setAccessible(true);
1225 foundFields.add(field);
1226 }
1227 }
1228
1229 fields = foundFields.toArray(new Field[foundFields.size()]);
1230 map.put(klass, fields);
1231
1232 return fields;
1233 }
1234
1235 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1236 if (mCapturedViewMethodsForClasses == null) {
1237 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1238 }
1239 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1240
1241 Method[] methods = map.get(klass);
1242 if (methods != null) {
1243 return methods;
1244 }
1245
1246 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1247 methods = klass.getMethods();
1248
1249 int count = methods.length;
1250 for (int i = 0; i < count; i++) {
1251 final Method method = methods[i];
1252 if (method.getParameterTypes().length == 0 &&
1253 method.isAnnotationPresent(CapturedViewProperty.class) &&
1254 method.getReturnType() != Void.class) {
1255 method.setAccessible(true);
1256 foundMethods.add(method);
1257 }
1258 }
1259
1260 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1261 map.put(klass, methods);
1262
1263 return methods;
1264 }
1265
1266 private static String capturedViewExportMethods(Object obj, Class<?> klass,
1267 String prefix) {
1268
1269 if (obj == null) {
1270 return "null";
1271 }
1272
1273 StringBuilder sb = new StringBuilder();
1274 final Method[] methods = capturedViewGetPropertyMethods(klass);
1275
1276 int count = methods.length;
1277 for (int i = 0; i < count; i++) {
1278 final Method method = methods[i];
1279 try {
1280 Object methodValue = method.invoke(obj, (Object[]) null);
1281 final Class<?> returnType = method.getReturnType();
1282
1283 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1284 if (property.retrieveReturn()) {
1285 //we are interested in the second level data only
1286 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
1287 } else {
1288 sb.append(prefix);
1289 sb.append(method.getName());
1290 sb.append("()=");
1291
1292 if (methodValue != null) {
1293 final String value = methodValue.toString().replace("\n", "\\n");
1294 sb.append(value);
1295 } else {
1296 sb.append("null");
1297 }
1298 sb.append("; ");
1299 }
1300 } catch (IllegalAccessException e) {
1301 //Exception IllegalAccess, it is OK here
1302 //we simply ignore this method
1303 } catch (InvocationTargetException e) {
1304 //Exception InvocationTarget, it is OK here
1305 //we simply ignore this method
1306 }
1307 }
1308 return sb.toString();
1309 }
1310
1311 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
1312
1313 if (obj == null) {
1314 return "null";
1315 }
1316
1317 StringBuilder sb = new StringBuilder();
1318 final Field[] fields = capturedViewGetPropertyFields(klass);
1319
1320 int count = fields.length;
1321 for (int i = 0; i < count; i++) {
1322 final Field field = fields[i];
1323 try {
1324 Object fieldValue = field.get(obj);
1325
1326 sb.append(prefix);
1327 sb.append(field.getName());
1328 sb.append("=");
1329
1330 if (fieldValue != null) {
1331 final String value = fieldValue.toString().replace("\n", "\\n");
1332 sb.append(value);
1333 } else {
1334 sb.append("null");
1335 }
1336 sb.append(' ');
1337 } catch (IllegalAccessException e) {
1338 //Exception IllegalAccess, it is OK here
1339 //we simply ignore this field
1340 }
1341 }
1342 return sb.toString();
1343 }
1344
1345 /**
Dianne Hackborn935ae462009-04-13 16:11:55 -07001346 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347 * (and possibly further data analysis). The results are dumped
1348 * to the log.
1349 * @param tag for log
1350 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001351 */
1352 public static void dumpCapturedView(String tag, Object view) {
1353 Class<?> klass = view.getClass();
1354 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1355 sb.append(capturedViewExportFields(view, klass, ""));
1356 sb.append(capturedViewExportMethods(view, klass, ""));
1357 Log.d(tag, sb.toString());
1358 }
1359}