blob: d2563a87713fca5334e2cca2362a0a09d88a53a8 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
Romain Guye551dc72009-07-28 15:30:11 -070019import android.util.Config;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.util.Log;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070021import android.util.DisplayMetrics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.res.Resources;
The Android Open Source Project10592532009-03-18 17:39:46 -070023import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.graphics.Bitmap;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070025import android.graphics.Canvas;
Romain Guy223ff5c2010-03-02 17:07:47 -080026import android.graphics.Rect;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Environment;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070028import android.os.Debug;
Romain Guy223ff5c2010-03-02 17:07:47 -080029import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
Romain Guy223ff5c2010-03-02 17:07:47 -080031import java.io.ByteArrayOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import java.io.File;
33import java.io.BufferedWriter;
34import java.io.FileWriter;
35import java.io.IOException;
36import java.io.FileOutputStream;
37import java.io.DataOutputStream;
38import java.io.OutputStreamWriter;
39import java.io.BufferedOutputStream;
40import java.io.OutputStream;
41import java.util.List;
42import java.util.LinkedList;
43import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.concurrent.CountDownLatch;
46import java.util.concurrent.TimeUnit;
47import java.lang.annotation.Target;
48import java.lang.annotation.ElementType;
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51import java.lang.reflect.Field;
52import java.lang.reflect.Method;
53import java.lang.reflect.InvocationTargetException;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070054import java.lang.reflect.AccessibleObject;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56/**
57 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
58 */
59public class ViewDebug {
60 /**
Romain Guy13922e02009-05-12 17:56:14 -070061 * Log tag used to log errors related to the consistency of the view hierarchy.
62 *
63 * @hide
64 */
65 public static final String CONSISTENCY_LOG_TAG = "ViewConsistency";
66
67 /**
68 * Flag indicating the consistency check should check layout-related properties.
69 *
70 * @hide
71 */
72 public static final int CONSISTENCY_LAYOUT = 0x1;
73
74 /**
75 * Flag indicating the consistency check should check drawing-related properties.
76 *
77 * @hide
78 */
79 public static final int CONSISTENCY_DRAWING = 0x2;
80
81 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 * Enables or disables view hierarchy tracing. Any invoker of
83 * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first
84 * check that this value is set to true as not to affect performance.
85 */
86 public static final boolean TRACE_HIERARCHY = false;
87
88 /**
89 * Enables or disables view recycler tracing. Any invoker of
90 * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first
91 * check that this value is set to true as not to affect performance.
92 */
93 public static final boolean TRACE_RECYCLER = false;
Romain Guya1f3e4a2009-06-04 15:10:46 -070094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095 /**
Romain Guycf635ae2010-01-22 11:00:29 -080096 * Enables or disables motion events tracing. Any invoker of
97 * {@link #trace(View, MotionEvent, MotionEventTraceType)} should first check
98 * that this value is set to true as not to affect performance.
99 *
100 * @hide
101 */
102 public static final boolean TRACE_MOTION_EVENTS = false;
103
104 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 * The system property of dynamic switch for capturing view information
106 * when it is set, we dump interested fields and methods for the view on focus
Romain Guya1f3e4a2009-06-04 15:10:46 -0700107 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
Romain Guya1f3e4a2009-06-04 15:10:46 -0700109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 /**
111 * The system property of dynamic switch for capturing event information
112 * when it is set, we log key events, touch/motion and trackball events
Romain Guya1f3e4a2009-06-04 15:10:46 -0700113 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 /**
Romain Guy13922e02009-05-12 17:56:14 -0700117 * Profiles drawing times in the events log.
118 *
119 * @hide
120 */
121 @Debug.DebugProperty
122 public static boolean profileDrawing = false;
123
124 /**
125 * Profiles layout times in the events log.
126 *
127 * @hide
128 */
129 @Debug.DebugProperty
130 public static boolean profileLayout = false;
131
132 /**
133 * Profiles real fps (times between draws) and displays the result.
134 *
135 * @hide
136 */
137 @Debug.DebugProperty
138 public static boolean showFps = false;
139
140 /**
141 * <p>Enables or disables views consistency check. Even when this property is enabled,
142 * view consistency checks happen only if {@link android.util.Config#DEBUG} is set
143 * to true. The value of this property can be configured externally in one of the
144 * following files:</p>
145 * <ul>
146 * <li>/system/debug.prop</li>
147 * <li>/debug.prop</li>
148 * <li>/data/debug.prop</li>
149 * </ul>
150 * @hide
151 */
152 @Debug.DebugProperty
153 public static boolean consistencyCheckEnabled = false;
154
155 static {
Romain Guye551dc72009-07-28 15:30:11 -0700156 if (Config.DEBUG) {
157 Debug.setFieldsOn(ViewDebug.class, true);
158 }
Romain Guy13922e02009-05-12 17:56:14 -0700159 }
160
161 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 * This annotation can be used to mark fields and methods to be dumped by
163 * the view server. Only non-void methods with no arguments can be annotated
164 * by this annotation.
165 */
166 @Target({ ElementType.FIELD, ElementType.METHOD })
167 @Retention(RetentionPolicy.RUNTIME)
168 public @interface ExportedProperty {
169 /**
170 * When resolveId is true, and if the annotated field/method return value
171 * is an int, the value is converted to an Android's resource name.
172 *
173 * @return true if the property's value must be transformed into an Android
174 * resource name, false otherwise
175 */
176 boolean resolveId() default false;
177
178 /**
179 * A mapping can be defined to map int values to specific strings. For
180 * instance, View.getVisibility() returns 0, 4 or 8. However, these values
181 * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
182 * these human readable values:
183 *
184 * <pre>
185 * @ViewDebug.ExportedProperty(mapping = {
186 * @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
187 * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
188 * @ViewDebug.IntToString(from = 8, to = "GONE")
189 * })
190 * public int getVisibility() { ...
191 * <pre>
192 *
193 * @return An array of int to String mappings
194 *
195 * @see android.view.ViewDebug.IntToString
196 */
197 IntToString[] mapping() default { };
198
199 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700200 * A mapping can be defined to map array indices to specific strings.
201 * A mapping can be used to see human readable values for the indices
202 * of an array:
203 *
204 * <pre>
Romain Guy809a7f62009-05-14 15:44:42 -0700205 * @ViewDebug.ExportedProperty(indexMapping = {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700206 * @ViewDebug.IntToString(from = 0, to = "INVALID"),
207 * @ViewDebug.IntToString(from = 1, to = "FIRST"),
208 * @ViewDebug.IntToString(from = 2, to = "SECOND")
209 * })
210 * private int[] mElements;
211 * <pre>
212 *
213 * @return An array of int to String mappings
214 *
215 * @see android.view.ViewDebug.IntToString
216 * @see #mapping()
217 */
218 IntToString[] indexMapping() default { };
219
220 /**
Romain Guy809a7f62009-05-14 15:44:42 -0700221 * A flags mapping can be defined to map flags encoded in an integer to
222 * specific strings. A mapping can be used to see human readable values
223 * for the flags of an integer:
224 *
225 * <pre>
226 * @ViewDebug.ExportedProperty(flagMapping = {
227 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"),
228 * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"),
229 * })
230 * private int mFlags;
231 * <pre>
232 *
233 * A specified String is output when the following is true:
Romain Guya1f3e4a2009-06-04 15:10:46 -0700234 *
Romain Guy809a7f62009-05-14 15:44:42 -0700235 * @return An array of int to String mappings
236 */
237 FlagToString[] flagMapping() default { };
238
239 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 * When deep export is turned on, this property is not dumped. Instead, the
241 * properties contained in this property are dumped. Each child property
242 * is prefixed with the name of this property.
243 *
244 * @return true if the properties of this property should be dumped
245 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700246 * @see #prefix()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 */
248 boolean deepExport() default false;
249
250 /**
251 * The prefix to use on child properties when deep export is enabled
252 *
253 * @return a prefix as a String
254 *
255 * @see #deepExport()
256 */
257 String prefix() default "";
258 }
259
260 /**
261 * Defines a mapping from an int value to a String. Such a mapping can be used
262 * in a @ExportedProperty to provide more meaningful values to the end user.
263 *
264 * @see android.view.ViewDebug.ExportedProperty
265 */
266 @Target({ ElementType.TYPE })
267 @Retention(RetentionPolicy.RUNTIME)
268 public @interface IntToString {
269 /**
270 * The original int value to map to a String.
271 *
272 * @return An arbitrary int value.
273 */
274 int from();
275
276 /**
277 * The String to use in place of the original int value.
278 *
279 * @return An arbitrary non-null String.
280 */
281 String to();
282 }
Romain Guy809a7f62009-05-14 15:44:42 -0700283
284 /**
285 * Defines a mapping from an flag to a String. Such a mapping can be used
286 * in a @ExportedProperty to provide more meaningful values to the end user.
287 *
288 * @see android.view.ViewDebug.ExportedProperty
289 */
290 @Target({ ElementType.TYPE })
291 @Retention(RetentionPolicy.RUNTIME)
292 public @interface FlagToString {
293 /**
294 * The mask to apply to the original value.
295 *
296 * @return An arbitrary int value.
297 */
298 int mask();
299
300 /**
301 * The value to compare to the result of:
302 * <code>original value &amp; {@link #mask()}</code>.
303 *
304 * @return An arbitrary value.
305 */
306 int equals();
307
308 /**
309 * The String to use in place of the original int value.
310 *
311 * @return An arbitrary non-null String.
312 */
313 String name();
314
315 /**
316 * Indicates whether to output the flag when the test is true,
317 * or false. Defaults to true.
318 */
319 boolean outputIf() default true;
320 }
321
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322 /**
323 * This annotation can be used to mark fields and methods to be dumped when
324 * the view is captured. Methods with this annotation must have no arguments
Andy Stadlerf8a7cea2009-04-10 16:24:47 -0700325 * and must return a valid type of data.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 */
327 @Target({ ElementType.FIELD, ElementType.METHOD })
328 @Retention(RetentionPolicy.RUNTIME)
329 public @interface CapturedViewProperty {
330 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -0700331 * When retrieveReturn is true, we need to retrieve second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700333 * we will set retrieveReturn = true on the annotation of
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 * myView.getFirstLevelMethod()
Romain Guya1f3e4a2009-06-04 15:10:46 -0700335 * @return true if we need the second level methods
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 */
Romain Guya1f3e4a2009-06-04 15:10:46 -0700337 boolean retrieveReturn() default false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700339
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
341 private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
342
343 // Maximum delay in ms after which we stop trying to capture a View's drawing
344 private static final int CAPTURE_TIMEOUT = 4000;
345
346 private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
347 private static final String REMOTE_COMMAND_DUMP = "DUMP";
348 private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
349 private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700350 private static final String REMOTE_PROFILE = "PROFILE";
Romain Guy223ff5c2010-03-02 17:07:47 -0800351 private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352
353 private static HashMap<Class<?>, Field[]> sFieldsForClasses;
354 private static HashMap<Class<?>, Method[]> sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700355 private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
356
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 /**
358 * Defines the type of hierarhcy trace to output to the hierarchy traces file.
359 */
360 public enum HierarchyTraceType {
361 INVALIDATE,
362 INVALIDATE_CHILD,
363 INVALIDATE_CHILD_IN_PARENT,
364 REQUEST_LAYOUT,
365 ON_LAYOUT,
366 ON_MEASURE,
367 DRAW,
368 BUILD_CACHE
369 }
370
371 private static BufferedWriter sHierarchyTraces;
372 private static ViewRoot sHierarhcyRoot;
373 private static String sHierarchyTracePrefix;
374
375 /**
376 * Defines the type of recycler trace to output to the recycler traces file.
377 */
378 public enum RecyclerTraceType {
379 NEW_VIEW,
380 BIND_VIEW,
381 RECYCLE_FROM_ACTIVE_HEAP,
382 RECYCLE_FROM_SCRAP_HEAP,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 MOVE_TO_SCRAP_HEAP,
384 MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
385 }
386
387 private static class RecyclerTrace {
388 public int view;
389 public RecyclerTraceType type;
390 public int position;
391 public int indexOnScreen;
392 }
393
394 private static View sRecyclerOwnerView;
395 private static List<View> sRecyclerViews;
396 private static List<RecyclerTrace> sRecyclerTraces;
397 private static String sRecyclerTracePrefix;
398
399 /**
Romain Guycf635ae2010-01-22 11:00:29 -0800400 * Defines the type of motion events trace to output to the motion events traces file.
401 *
402 * @hide
403 */
404 public enum MotionEventTraceType {
405 DISPATCH,
406 ON_INTERCEPT,
407 ON_TOUCH
408 }
409
410 private static BufferedWriter sMotionEventTraces;
411 private static ViewRoot sMotionEventRoot;
412 private static String sMotionEventTracePrefix;
413
414 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 * Returns the number of instanciated Views.
416 *
417 * @return The number of Views instanciated in the current process.
418 *
419 * @hide
420 */
421 public static long getViewInstanceCount() {
422 return View.sInstanceCount;
423 }
424
425 /**
426 * Returns the number of instanciated ViewRoots.
427 *
428 * @return The number of ViewRoots instanciated in the current process.
429 *
430 * @hide
431 */
432 public static long getViewRootInstanceCount() {
433 return ViewRoot.getInstanceCount();
Romain Guya1f3e4a2009-06-04 15:10:46 -0700434 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435
436 /**
437 * Outputs a trace to the currently opened recycler traces. The trace records the type of
438 * recycler action performed on the supplied view as well as a number of parameters.
439 *
440 * @param view the view to trace
441 * @param type the type of the trace
442 * @param parameters parameters depending on the type of the trace
443 */
444 public static void trace(View view, RecyclerTraceType type, int... parameters) {
445 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
446 return;
447 }
448
449 if (!sRecyclerViews.contains(view)) {
450 sRecyclerViews.add(view);
451 }
452
453 final int index = sRecyclerViews.indexOf(view);
454
455 RecyclerTrace trace = new RecyclerTrace();
456 trace.view = index;
457 trace.type = type;
458 trace.position = parameters[0];
459 trace.indexOnScreen = parameters[1];
460
461 sRecyclerTraces.add(trace);
462 }
463
464 /**
465 * Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
466 * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
467 * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
468 *
469 * Only one view recycler can be traced at the same time. After calling this method, any
470 * other invocation will result in a <code>IllegalStateException</code> unless
471 * {@link #stopRecyclerTracing()} is invoked before.
472 *
473 * Traces files are created only after {@link #stopRecyclerTracing()} is invoked.
474 *
475 * This method will return immediately if TRACE_RECYCLER is false.
476 *
477 * @param prefix the traces files name prefix
478 * @param view the view whose recycler must be traced
479 *
480 * @see #stopRecyclerTracing()
481 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
482 */
483 public static void startRecyclerTracing(String prefix, View view) {
484 //noinspection PointlessBooleanExpression,ConstantConditions
485 if (!TRACE_RECYCLER) {
486 return;
487 }
488
489 if (sRecyclerOwnerView != null) {
490 throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
491 " a new trace!");
492 }
493
494 sRecyclerTracePrefix = prefix;
495 sRecyclerOwnerView = view;
496 sRecyclerViews = new ArrayList<View>();
497 sRecyclerTraces = new LinkedList<RecyclerTrace>();
498 }
499
500 /**
501 * Stops the current view recycer tracing.
502 *
503 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
504 * containing all the traces (or method calls) relative to the specified view's recycler.
505 *
506 * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
507 * containing all of the views used by the recycler of the view supplied to
508 * {@link #startRecyclerTracing(String, View)}.
509 *
510 * This method will return immediately if TRACE_RECYCLER is false.
511 *
512 * @see #startRecyclerTracing(String, View)
513 * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])
514 */
515 public static void stopRecyclerTracing() {
516 //noinspection PointlessBooleanExpression,ConstantConditions
517 if (!TRACE_RECYCLER) {
518 return;
519 }
520
521 if (sRecyclerOwnerView == null || sRecyclerViews == null) {
522 throw new IllegalStateException("You must call startRecyclerTracing() before" +
523 " stopRecyclerTracing()!");
524 }
525
526 File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700527 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 recyclerDump.mkdirs();
529
530 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
531 try {
532 final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);
533
534 for (View view : sRecyclerViews) {
535 final String name = view.getClass().getName();
536 out.write(name);
537 out.newLine();
538 }
539
540 out.close();
541 } catch (IOException e) {
542 Log.e("View", "Could not dump recycler content");
543 return;
544 }
545
546 recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
547 recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
548 try {
Dianne Hackborn1afd1c92010-03-18 22:47:17 -0700549 if (recyclerDump.exists()) {
550 recyclerDump.delete();
551 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 final FileOutputStream file = new FileOutputStream(recyclerDump);
553 final DataOutputStream out = new DataOutputStream(file);
554
555 for (RecyclerTrace trace : sRecyclerTraces) {
556 out.writeInt(trace.view);
557 out.writeInt(trace.type.ordinal());
558 out.writeInt(trace.position);
559 out.writeInt(trace.indexOnScreen);
560 out.flush();
561 }
562
563 out.close();
564 } catch (IOException e) {
565 Log.e("View", "Could not dump recycler traces");
566 return;
567 }
568
569 sRecyclerViews.clear();
570 sRecyclerViews = null;
571
572 sRecyclerTraces.clear();
573 sRecyclerTraces = null;
574
575 sRecyclerOwnerView = null;
576 }
577
578 /**
579 * Outputs a trace to the currently opened traces file. The trace contains the class name
580 * and instance's hashcode of the specified view as well as the supplied trace type.
581 *
582 * @param view the view to trace
583 * @param type the type of the trace
584 */
585 public static void trace(View view, HierarchyTraceType type) {
586 if (sHierarchyTraces == null) {
587 return;
588 }
589
590 try {
591 sHierarchyTraces.write(type.name());
592 sHierarchyTraces.write(' ');
593 sHierarchyTraces.write(view.getClass().getName());
594 sHierarchyTraces.write('@');
595 sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
596 sHierarchyTraces.newLine();
597 } catch (IOException e) {
598 Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
599 }
600 }
601
602 /**
603 * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
604 * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
605 * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
606 *
607 * Only one view hierarchy can be traced at the same time. After calling this method, any
608 * other invocation will result in a <code>IllegalStateException</code> unless
609 * {@link #stopHierarchyTracing()} is invoked before.
610 *
611 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
612 * containing all the traces (or method calls) relative to the specified view's hierarchy.
613 *
614 * This method will return immediately if TRACE_HIERARCHY is false.
615 *
616 * @param prefix the traces files name prefix
617 * @param view the view whose hierarchy must be traced
618 *
619 * @see #stopHierarchyTracing()
620 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
621 */
622 public static void startHierarchyTracing(String prefix, View view) {
623 //noinspection PointlessBooleanExpression,ConstantConditions
624 if (!TRACE_HIERARCHY) {
625 return;
626 }
627
628 if (sHierarhcyRoot != null) {
629 throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
630 " a new trace!");
631 }
632
633 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700634 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 hierarchyDump.mkdirs();
636
637 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
638 sHierarchyTracePrefix = prefix;
639
640 try {
641 sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
642 } catch (IOException e) {
643 Log.e("View", "Could not dump view hierarchy");
644 return;
645 }
646
647 sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
648 }
649
650 /**
651 * Stops the current view hierarchy tracing. This method closes the file
652 * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
653 *
654 * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
655 * containing the view hierarchy of the view supplied to
656 * {@link #startHierarchyTracing(String, View)}.
657 *
658 * This method will return immediately if TRACE_HIERARCHY is false.
659 *
Romain Guya1f3e4a2009-06-04 15:10:46 -0700660 * @see #startHierarchyTracing(String, View)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 * @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
662 */
663 public static void stopHierarchyTracing() {
664 //noinspection PointlessBooleanExpression,ConstantConditions
665 if (!TRACE_HIERARCHY) {
666 return;
667 }
668
669 if (sHierarhcyRoot == null || sHierarchyTraces == null) {
670 throw new IllegalStateException("You must call startHierarchyTracing() before" +
671 " stopHierarchyTracing()!");
672 }
673
674 try {
675 sHierarchyTraces.close();
676 } catch (IOException e) {
677 Log.e("View", "Could not write view traces");
678 }
679 sHierarchyTraces = null;
680
681 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700682 //noinspection ResultOfMethodCallIgnored
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 hierarchyDump.mkdirs();
684 hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
685
686 BufferedWriter out;
687 try {
688 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
689 } catch (IOException e) {
690 Log.e("View", "Could not dump view hierarchy");
691 return;
692 }
693
694 View view = sHierarhcyRoot.getView();
695 if (view instanceof ViewGroup) {
696 ViewGroup group = (ViewGroup) view;
697 dumpViewHierarchy(group, out, 0);
698 try {
699 out.close();
700 } catch (IOException e) {
701 Log.e("View", "Could not dump view hierarchy");
702 }
703 }
704
705 sHierarhcyRoot = null;
706 }
Romain Guya1f3e4a2009-06-04 15:10:46 -0700707
Romain Guycf635ae2010-01-22 11:00:29 -0800708 /**
709 * Outputs a trace to the currently opened traces file. The trace contains the class name
710 * and instance's hashcode of the specified view as well as the supplied trace type.
711 *
712 * @param view the view to trace
713 * @param event the event of the trace
714 * @param type the type of the trace
715 *
716 * @hide
717 */
718 public static void trace(View view, MotionEvent event, MotionEventTraceType type) {
719 if (sMotionEventTraces == null) {
720 return;
721 }
722
723 try {
724 sMotionEventTraces.write(type.name());
725 sMotionEventTraces.write(' ');
726 sMotionEventTraces.write(event.getAction());
727 sMotionEventTraces.write(' ');
728 sMotionEventTraces.write(view.getClass().getName());
729 sMotionEventTraces.write('@');
730 sMotionEventTraces.write(Integer.toHexString(view.hashCode()));
731 sHierarchyTraces.newLine();
732 } catch (IOException e) {
733 Log.w("View", "Error while dumping trace of event " + event + " for view " + view);
734 }
735 }
736
737 /**
738 * Starts tracing the motion events for the hierarchy of the specificy view.
739 * The trace is identified by a prefix, used to build the traces files names:
740 * <code>/EXTERNAL/motion-events/PREFIX.traces</code> and
741 * <code>/EXTERNAL/motion-events/PREFIX.tree</code>.
742 *
743 * Only one view hierarchy can be traced at the same time. After calling this method, any
744 * other invocation will result in a <code>IllegalStateException</code> unless
745 * {@link #stopMotionEventTracing()} is invoked before.
746 *
747 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.traces</code>
748 * containing all the traces (or method calls) relative to the specified view's hierarchy.
749 *
750 * This method will return immediately if TRACE_HIERARCHY is false.
751 *
752 * @param prefix the traces files name prefix
753 * @param view the view whose hierarchy must be traced
754 *
755 * @see #stopMotionEventTracing()
756 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
757 *
758 * @hide
759 */
760 public static void startMotionEventTracing(String prefix, View view) {
761 //noinspection PointlessBooleanExpression,ConstantConditions
762 if (!TRACE_MOTION_EVENTS) {
763 return;
764 }
765
766 if (sMotionEventRoot != null) {
767 throw new IllegalStateException("You must call stopMotionEventTracing() before running" +
768 " a new trace!");
769 }
770
771 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
772 //noinspection ResultOfMethodCallIgnored
773 hierarchyDump.mkdirs();
774
775 hierarchyDump = new File(hierarchyDump, prefix + ".traces");
776 sMotionEventTracePrefix = prefix;
777
778 try {
779 sMotionEventTraces = new BufferedWriter(new FileWriter(hierarchyDump), 32 * 1024);
780 } catch (IOException e) {
781 Log.e("View", "Could not dump view hierarchy");
782 return;
783 }
784
785 sMotionEventRoot = (ViewRoot) view.getRootView().getParent();
786 }
787
788 /**
789 * Stops the current motion events tracing. This method closes the file
790 * <code>/EXTERNAL/motion-events/PREFIX.traces</code>.
791 *
792 * Calling this method creates the file <code>/EXTERNAL/motion-events/PREFIX.tree</code>
793 * containing the view hierarchy of the view supplied to
794 * {@link #startMotionEventTracing(String, View)}.
795 *
796 * This method will return immediately if TRACE_HIERARCHY is false.
797 *
798 * @see #startMotionEventTracing(String, View)
799 * @see #trace(View, MotionEvent, android.view.ViewDebug.MotionEventTraceType)
800 *
801 * @hide
802 */
803 public static void stopMotionEventTracing() {
804 //noinspection PointlessBooleanExpression,ConstantConditions
805 if (!TRACE_MOTION_EVENTS) {
806 return;
807 }
808
809 if (sMotionEventRoot == null || sMotionEventTraces == null) {
810 throw new IllegalStateException("You must call startMotionEventTracing() before" +
811 " stopMotionEventTracing()!");
812 }
813
814 try {
815 sMotionEventTraces.close();
816 } catch (IOException e) {
817 Log.e("View", "Could not write view traces");
818 }
819 sMotionEventTraces = null;
820
821 File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "motion-events/");
822 //noinspection ResultOfMethodCallIgnored
823 hierarchyDump.mkdirs();
824 hierarchyDump = new File(hierarchyDump, sMotionEventTracePrefix + ".tree");
825
826 BufferedWriter out;
827 try {
828 out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
829 } catch (IOException e) {
830 Log.e("View", "Could not dump view hierarchy");
831 return;
832 }
833
834 View view = sMotionEventRoot.getView();
835 if (view instanceof ViewGroup) {
836 ViewGroup group = (ViewGroup) view;
837 dumpViewHierarchy(group, out, 0);
838 try {
839 out.close();
840 } catch (IOException e) {
841 Log.e("View", "Could not dump view hierarchy");
842 }
843 }
844
845 sHierarhcyRoot = null;
846 }
847
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848 static void dispatchCommand(View view, String command, String parameters,
849 OutputStream clientStream) throws IOException {
850
851 // Paranoid but safe...
852 view = view.getRootView();
853
854 if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
855 dump(view, clientStream);
Romain Guy223ff5c2010-03-02 17:07:47 -0800856 } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
857 captureLayers(view, new DataOutputStream(clientStream));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800858 } else {
859 final String[] params = parameters.split(" ");
860 if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
861 capture(view, clientStream, params[0]);
862 } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
863 invalidate(view, params[0]);
864 } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
865 requestLayout(view, params[0]);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700866 } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
867 profile(view, clientStream, params[0]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800868 }
869 }
870 }
871
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700872 private static View findView(View root, String parameter) {
873 // Look by type/hashcode
874 if (parameter.indexOf('@') != -1) {
875 final String[] ids = parameter.split("@");
876 final String className = ids[0];
Romain Guy236092a2009-12-14 15:31:48 -0800877 final int hashCode = (int) Long.parseLong(ids[1], 16);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700879 View view = root.getRootView();
880 if (view instanceof ViewGroup) {
881 return findView((ViewGroup) view, className, hashCode);
882 }
883 } else {
884 // Look by id
885 final int id = root.getResources().getIdentifier(parameter, null, null);
886 return root.getRootView().findViewById(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 }
888
889 return null;
890 }
891
892 private static void invalidate(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700893 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800894 if (view != null) {
895 view.postInvalidate();
896 }
897 }
898
899 private static void requestLayout(View root, String parameter) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700900 final View view = findView(root, parameter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800901 if (view != null) {
902 root.post(new Runnable() {
903 public void run() {
904 view.requestLayout();
905 }
906 });
907 }
908 }
909
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700910 private static void profile(View root, OutputStream clientStream, String parameter)
911 throws IOException {
912
913 final View view = findView(root, parameter);
914 BufferedWriter out = null;
915 try {
916 out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
917
918 if (view != null) {
919 final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
920 public Void[] pre() {
921 forceLayout(view);
922 return null;
923 }
924
925 private void forceLayout(View view) {
926 view.forceLayout();
927 if (view instanceof ViewGroup) {
928 ViewGroup group = (ViewGroup) view;
929 final int count = group.getChildCount();
930 for (int i = 0; i < count; i++) {
931 forceLayout(group.getChildAt(i));
932 }
933 }
934 }
935
936 public void run(Void... data) {
937 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
938 }
939
940 public void post(Void... data) {
941 }
942 });
943
944 final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
945 public Void[] pre() {
946 return null;
947 }
948
949 public void run(Void... data) {
950 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
951 }
952
953 public void post(Void... data) {
954 }
955 });
956
957 final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
958 public Object[] pre() {
959 final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
960 final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700961 metrics.heightPixels, Bitmap.Config.RGB_565);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700962 final Canvas canvas = new Canvas(bitmap);
963 return new Object[] { bitmap, canvas };
964 }
965
966 public void run(Object... data) {
967 view.draw((Canvas) data[1]);
968 }
969
970 public void post(Object... data) {
971 ((Bitmap) data[0]).recycle();
972 }
973 });
974
975 out.write(String.valueOf(durationMeasure));
976 out.write(' ');
977 out.write(String.valueOf(durationLayout));
978 out.write(' ');
979 out.write(String.valueOf(durationDraw));
980 out.newLine();
981 } else {
982 out.write("-1 -1 -1");
983 out.newLine();
984 }
985 } catch (Exception e) {
986 android.util.Log.w("View", "Problem profiling the view:", e);
987 } finally {
988 if (out != null) {
989 out.close();
990 }
991 }
992 }
993
994 interface ViewOperation<T> {
995 T[] pre();
996 void run(T... data);
997 void post(T... data);
998 }
999
1000 private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
1001 final CountDownLatch latch = new CountDownLatch(1);
1002 final long[] duration = new long[1];
1003
1004 view.post(new Runnable() {
1005 public void run() {
1006 try {
1007 T[] data = operation.pre();
1008 long start = Debug.threadCpuTimeNanos();
1009 operation.run(data);
1010 duration[0] = Debug.threadCpuTimeNanos() - start;
1011 operation.post(data);
1012 } finally {
1013 latch.countDown();
1014 }
1015 }
1016 });
1017
1018 try {
1019 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
1020 } catch (InterruptedException e) {
1021 Log.w("View", "Could not complete the profiling of the view " + view);
1022 Thread.currentThread().interrupt();
1023 return -1;
1024 }
1025
1026 return duration[0];
1027 }
1028
Romain Guy223ff5c2010-03-02 17:07:47 -08001029 private static void captureLayers(View root, final DataOutputStream clientStream)
1030 throws IOException {
1031
1032 try {
1033 Rect outRect = new Rect();
1034 try {
1035 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
1036 } catch (RemoteException e) {
1037 // Ignore
1038 }
1039
1040 clientStream.writeInt(outRect.width());
1041 clientStream.writeInt(outRect.height());
1042
Romain Guy65554f22010-03-22 18:58:21 -07001043 captureViewLayer(root, clientStream, true);
Romain Guy223ff5c2010-03-02 17:07:47 -08001044
1045 clientStream.write(2);
1046 } finally {
1047 clientStream.close();
1048 }
1049 }
1050
Romain Guy65554f22010-03-22 18:58:21 -07001051 private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
Romain Guy223ff5c2010-03-02 17:07:47 -08001052 throws IOException {
1053
Romain Guy65554f22010-03-22 18:58:21 -07001054 final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
1055
Romain Guy223ff5c2010-03-02 17:07:47 -08001056 if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) {
1057 final int id = view.getId();
1058 String name = view.getClass().getSimpleName();
1059 if (id != View.NO_ID) {
1060 name = resolveId(view.getContext(), id).toString();
1061 }
1062
1063 clientStream.write(1);
1064 clientStream.writeUTF(name);
Romain Guy65554f22010-03-22 18:58:21 -07001065 clientStream.writeByte(localVisible ? 1 : 0);
Romain Guy223ff5c2010-03-02 17:07:47 -08001066
1067 int[] position = new int[2];
1068 // XXX: Should happen on the UI thread
1069 view.getLocationInWindow(position);
1070
1071 clientStream.writeInt(position[0]);
1072 clientStream.writeInt(position[1]);
1073 clientStream.flush();
1074
1075 Bitmap b = performViewCapture(view, true);
1076 if (b != null) {
1077 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
1078 b.getHeight() * 2);
1079 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
1080 clientStream.writeInt(arrayOut.size());
1081 arrayOut.writeTo(clientStream);
1082 }
1083 clientStream.flush();
1084 }
1085
1086 if (view instanceof ViewGroup) {
1087 ViewGroup group = (ViewGroup) view;
1088 int count = group.getChildCount();
1089
1090 for (int i = 0; i < count; i++) {
Romain Guy65554f22010-03-22 18:58:21 -07001091 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
Romain Guy223ff5c2010-03-02 17:07:47 -08001092 }
1093 }
1094 }
1095
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 private static void capture(View root, final OutputStream clientStream, String parameter)
1097 throws IOException {
1098
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001099 final View captureView = findView(root, parameter);
Romain Guy223ff5c2010-03-02 17:07:47 -08001100 Bitmap b = performViewCapture(captureView, false);
1101
1102 if (b != null) {
1103 BufferedOutputStream out = null;
1104 try {
1105 out = new BufferedOutputStream(clientStream, 32 * 1024);
1106 b.compress(Bitmap.CompressFormat.PNG, 100, out);
1107 out.flush();
1108 } finally {
1109 if (out != null) {
1110 out.close();
1111 }
1112 b.recycle();
1113 }
1114 } else {
1115 Log.w("View", "Failed to create capture bitmap!");
1116 clientStream.close();
1117 }
1118 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119
Romain Guy223ff5c2010-03-02 17:07:47 -08001120 private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 if (captureView != null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001122 final CountDownLatch latch = new CountDownLatch(1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001123 final Bitmap[] cache = new Bitmap[1];
1124
Romain Guy223ff5c2010-03-02 17:07:47 -08001125 captureView.post(new Runnable() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 public void run() {
1127 try {
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001128 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001129 Bitmap.Config.ARGB_8888, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001130 } catch (OutOfMemoryError e) {
1131 try {
1132 cache[0] = captureView.createSnapshot(
Romain Guy223ff5c2010-03-02 17:07:47 -08001133 Bitmap.Config.ARGB_4444, 0, skpiChildren);
Dianne Hackborn958b9ad2009-03-31 18:00:36 -07001134 } catch (OutOfMemoryError e2) {
1135 Log.w("View", "Out of memory for bitmap");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 } finally {
1138 latch.countDown();
1139 }
1140 }
1141 });
1142
1143 try {
1144 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
Romain Guy223ff5c2010-03-02 17:07:47 -08001145 return cache[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146 } catch (InterruptedException e) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001147 Log.w("View", "Could not complete the capture of the view " + captureView);
1148 Thread.currentThread().interrupt();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001149 }
1150 }
Romain Guy223ff5c2010-03-02 17:07:47 -08001151
1152 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153 }
1154
1155 private static void dump(View root, OutputStream clientStream) throws IOException {
1156 BufferedWriter out = null;
1157 try {
Romain Guy38e951b2009-12-17 13:28:30 -08001158 out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 View view = root.getRootView();
1160 if (view instanceof ViewGroup) {
1161 ViewGroup group = (ViewGroup) view;
The Android Open Source Project10592532009-03-18 17:39:46 -07001162 dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 }
1164 out.write("DONE.");
1165 out.newLine();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001166 } catch (Exception e) {
1167 android.util.Log.w("View", "Problem dumping the view:", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 } finally {
1169 if (out != null) {
1170 out.close();
1171 }
1172 }
1173 }
1174
1175 private static View findView(ViewGroup group, String className, int hashCode) {
1176 if (isRequestedView(group, className, hashCode)) {
1177 return group;
1178 }
1179
1180 final int count = group.getChildCount();
1181 for (int i = 0; i < count; i++) {
1182 final View view = group.getChildAt(i);
1183 if (view instanceof ViewGroup) {
1184 final View found = findView((ViewGroup) view, className, hashCode);
1185 if (found != null) {
1186 return found;
1187 }
1188 } else if (isRequestedView(view, className, hashCode)) {
1189 return view;
1190 }
1191 }
1192
1193 return null;
1194 }
1195
1196 private static boolean isRequestedView(View view, String className, int hashCode) {
1197 return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
1198 }
1199
The Android Open Source Project10592532009-03-18 17:39:46 -07001200 private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 BufferedWriter out, int level) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001202 if (!dumpViewWithProperties(context, group, out, level)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 return;
1204 }
1205
1206 final int count = group.getChildCount();
1207 for (int i = 0; i < count; i++) {
1208 final View view = group.getChildAt(i);
1209 if (view instanceof ViewGroup) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001210 dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001211 } else {
The Android Open Source Project10592532009-03-18 17:39:46 -07001212 dumpViewWithProperties(context, view, out, level + 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
1214 }
1215 }
1216
The Android Open Source Project10592532009-03-18 17:39:46 -07001217 private static boolean dumpViewWithProperties(Context context, View view,
1218 BufferedWriter out, int level) {
1219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 try {
1221 for (int i = 0; i < level; i++) {
1222 out.write(' ');
1223 }
1224 out.write(view.getClass().getName());
1225 out.write('@');
1226 out.write(Integer.toHexString(view.hashCode()));
1227 out.write(' ');
The Android Open Source Project10592532009-03-18 17:39:46 -07001228 dumpViewProperties(context, view, out);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001229 out.newLine();
1230 } catch (IOException e) {
1231 Log.w("View", "Error while dumping hierarchy tree");
1232 return false;
1233 }
1234 return true;
1235 }
1236
1237 private static Field[] getExportedPropertyFields(Class<?> klass) {
1238 if (sFieldsForClasses == null) {
1239 sFieldsForClasses = new HashMap<Class<?>, Field[]>();
1240 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001241 if (sAnnotations == null) {
1242 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1243 }
1244
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001245 final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001246 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001247
1248 Field[] fields = map.get(klass);
1249 if (fields != null) {
1250 return fields;
1251 }
1252
1253 final ArrayList<Field> foundFields = new ArrayList<Field>();
1254 fields = klass.getDeclaredFields();
1255
1256 int count = fields.length;
1257 for (int i = 0; i < count; i++) {
1258 final Field field = fields[i];
1259 if (field.isAnnotationPresent(ExportedProperty.class)) {
1260 field.setAccessible(true);
1261 foundFields.add(field);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001262 annotations.put(field, field.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 }
1264 }
1265
1266 fields = foundFields.toArray(new Field[foundFields.size()]);
1267 map.put(klass, fields);
1268
1269 return fields;
1270 }
1271
1272 private static Method[] getExportedPropertyMethods(Class<?> klass) {
1273 if (sMethodsForClasses == null) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001274 sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001276 if (sAnnotations == null) {
1277 sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
1278 }
1279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001280 final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001281 final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282
1283 Method[] methods = map.get(klass);
1284 if (methods != null) {
1285 return methods;
1286 }
1287
1288 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1289 methods = klass.getDeclaredMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001290
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001291 int count = methods.length;
1292 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001293 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001294 if (method.getParameterTypes().length == 0 &&
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001295 method.isAnnotationPresent(ExportedProperty.class) &&
1296 method.getReturnType() != Void.class) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 method.setAccessible(true);
1298 foundMethods.add(method);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001299 annotations.put(method, method.getAnnotation(ExportedProperty.class));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 }
1301 }
1302
1303 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1304 map.put(klass, methods);
1305
1306 return methods;
1307 }
1308
The Android Open Source Project10592532009-03-18 17:39:46 -07001309 private static void dumpViewProperties(Context context, Object view,
1310 BufferedWriter out) throws IOException {
1311
1312 dumpViewProperties(context, view, out, "");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001313 }
1314
The Android Open Source Project10592532009-03-18 17:39:46 -07001315 private static void dumpViewProperties(Context context, Object view,
1316 BufferedWriter out, String prefix) throws IOException {
1317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 Class<?> klass = view.getClass();
1319
1320 do {
The Android Open Source Project10592532009-03-18 17:39:46 -07001321 exportFields(context, view, out, klass, prefix);
1322 exportMethods(context, view, out, klass, prefix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 klass = klass.getSuperclass();
1324 } while (klass != Object.class);
1325 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001326
The Android Open Source Project10592532009-03-18 17:39:46 -07001327 private static void exportMethods(Context context, Object view, BufferedWriter out,
1328 Class<?> klass, String prefix) throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001329
1330 final Method[] methods = getExportedPropertyMethods(klass);
1331
1332 int count = methods.length;
1333 for (int i = 0; i < count; i++) {
1334 final Method method = methods[i];
1335 //noinspection EmptyCatchBlock
1336 try {
1337 // TODO: This should happen on the UI thread
1338 Object methodValue = method.invoke(view, (Object[]) null);
1339 final Class<?> returnType = method.getReturnType();
1340
1341 if (returnType == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001342 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project10592532009-03-18 17:39:46 -07001343 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001344 final int id = (Integer) methodValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001345 methodValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001347 final FlagToString[] flagsMapping = property.flagMapping();
1348 if (flagsMapping.length > 0) {
1349 final int intValue = (Integer) methodValue;
1350 final String valuePrefix = prefix + method.getName() + '_';
1351 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1352 }
1353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001354 final IntToString[] mapping = property.mapping();
1355 if (mapping.length > 0) {
1356 final int intValue = (Integer) methodValue;
1357 boolean mapped = false;
1358 int mappingCount = mapping.length;
1359 for (int j = 0; j < mappingCount; j++) {
1360 final IntToString mapper = mapping[j];
1361 if (mapper.from() == intValue) {
1362 methodValue = mapper.to();
1363 mapped = true;
1364 break;
1365 }
1366 }
1367
1368 if (!mapped) {
1369 methodValue = intValue;
1370 }
1371 }
1372 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001373 } else if (returnType == int[].class) {
1374 final ExportedProperty property = sAnnotations.get(method);
1375 final int[] array = (int[]) methodValue;
1376 final String valuePrefix = prefix + method.getName() + '_';
1377 final String suffix = "()";
1378
The Android Open Source Project10592532009-03-18 17:39:46 -07001379 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 } else if (!returnType.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001381 final ExportedProperty property = sAnnotations.get(method);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001383 dumpViewProperties(context, methodValue, out, prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001384 continue;
1385 }
1386 }
1387
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001388 writeEntry(out, prefix, method.getName(), "()", methodValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001389 } catch (IllegalAccessException e) {
1390 } catch (InvocationTargetException e) {
1391 }
1392 }
1393 }
1394
The Android Open Source Project10592532009-03-18 17:39:46 -07001395 private static void exportFields(Context context, Object view, BufferedWriter out,
1396 Class<?> klass, String prefix) throws IOException {
1397
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 final Field[] fields = getExportedPropertyFields(klass);
1399
1400 int count = fields.length;
1401 for (int i = 0; i < count; i++) {
1402 final Field field = fields[i];
1403
1404 //noinspection EmptyCatchBlock
1405 try {
1406 Object fieldValue = null;
1407 final Class<?> type = field.getType();
1408
1409 if (type == int.class) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001410 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project10592532009-03-18 17:39:46 -07001411 if (property.resolveId() && context != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001412 final int id = field.getInt(view);
The Android Open Source Project10592532009-03-18 17:39:46 -07001413 fieldValue = resolveId(context, id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414 } else {
Romain Guy809a7f62009-05-14 15:44:42 -07001415 final FlagToString[] flagsMapping = property.flagMapping();
1416 if (flagsMapping.length > 0) {
1417 final int intValue = field.getInt(view);
1418 final String valuePrefix = prefix + field.getName() + '_';
1419 exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1420 }
1421
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001422 final IntToString[] mapping = property.mapping();
1423 if (mapping.length > 0) {
1424 final int intValue = field.getInt(view);
1425 int mappingCount = mapping.length;
1426 for (int j = 0; j < mappingCount; j++) {
1427 final IntToString mapped = mapping[j];
1428 if (mapped.from() == intValue) {
1429 fieldValue = mapped.to();
1430 break;
1431 }
1432 }
1433
1434 if (fieldValue == null) {
1435 fieldValue = intValue;
1436 }
1437 }
1438 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001439 } else if (type == int[].class) {
1440 final ExportedProperty property = sAnnotations.get(field);
1441 final int[] array = (int[]) field.get(view);
1442 final String valuePrefix = prefix + field.getName() + '_';
1443 final String suffix = "";
1444
The Android Open Source Project10592532009-03-18 17:39:46 -07001445 exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001446
1447 // We exit here!
1448 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 } else if (!type.isPrimitive()) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001450 final ExportedProperty property = sAnnotations.get(field);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001451 if (property.deepExport()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07001452 dumpViewProperties(context, field.get(view), out,
1453 prefix + property.prefix());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454 continue;
1455 }
1456 }
1457
1458 if (fieldValue == null) {
1459 fieldValue = field.get(view);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 }
1461
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001462 writeEntry(out, prefix, field.getName(), "", fieldValue);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001463 } catch (IllegalAccessException e) {
1464 }
1465 }
1466 }
1467
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001468 private static void writeEntry(BufferedWriter out, String prefix, String name,
1469 String suffix, Object value) throws IOException {
1470
1471 out.write(prefix);
1472 out.write(name);
1473 out.write(suffix);
1474 out.write("=");
1475 writeValue(out, value);
1476 out.write(' ');
1477 }
1478
Romain Guy809a7f62009-05-14 15:44:42 -07001479 private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1480 int intValue, String prefix) throws IOException {
1481
1482 final int count = mapping.length;
1483 for (int j = 0; j < count; j++) {
1484 final FlagToString flagMapping = mapping[j];
1485 final boolean ifTrue = flagMapping.outputIf();
Romain Guy5bcdff42009-05-14 21:27:18 -07001486 final int maskResult = intValue & flagMapping.mask();
1487 final boolean test = maskResult == flagMapping.equals();
Romain Guy809a7f62009-05-14 15:44:42 -07001488 if ((test && ifTrue) || (!test && !ifTrue)) {
1489 final String name = flagMapping.name();
Romain Guy5bcdff42009-05-14 21:27:18 -07001490 final String value = "0x" + Integer.toHexString(maskResult);
Romain Guy809a7f62009-05-14 15:44:42 -07001491 writeEntry(out, prefix, name, "", value);
1492 }
1493 }
1494 }
1495
The Android Open Source Project10592532009-03-18 17:39:46 -07001496 private static void exportUnrolledArray(Context context, BufferedWriter out,
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001497 ExportedProperty property, int[] array, String prefix, String suffix)
1498 throws IOException {
1499
1500 final IntToString[] indexMapping = property.indexMapping();
1501 final boolean hasIndexMapping = indexMapping.length > 0;
1502
1503 final IntToString[] mapping = property.mapping();
1504 final boolean hasMapping = mapping.length > 0;
1505
The Android Open Source Project10592532009-03-18 17:39:46 -07001506 final boolean resolveId = property.resolveId() && context != null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001507 final int valuesCount = array.length;
1508
1509 for (int j = 0; j < valuesCount; j++) {
1510 String name;
Romain Guya1f3e4a2009-06-04 15:10:46 -07001511 String value = null;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001512
1513 final int intValue = array[j];
1514
1515 name = String.valueOf(j);
1516 if (hasIndexMapping) {
1517 int mappingCount = indexMapping.length;
1518 for (int k = 0; k < mappingCount; k++) {
1519 final IntToString mapped = indexMapping[k];
1520 if (mapped.from() == j) {
1521 name = mapped.to();
1522 break;
1523 }
1524 }
1525 }
1526
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001527 if (hasMapping) {
1528 int mappingCount = mapping.length;
1529 for (int k = 0; k < mappingCount; k++) {
1530 final IntToString mapped = mapping[k];
1531 if (mapped.from() == intValue) {
1532 value = mapped.to();
1533 break;
1534 }
1535 }
1536 }
1537
1538 if (resolveId) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001539 if (value == null) value = (String) resolveId(context, intValue);
1540 } else {
1541 value = String.valueOf(intValue);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001542 }
1543
1544 writeEntry(out, prefix, name, suffix, value);
1545 }
1546 }
1547
Romain Guy237c1ce2009-12-08 11:30:25 -08001548 static Object resolveId(Context context, int id) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001549 Object fieldValue;
The Android Open Source Project10592532009-03-18 17:39:46 -07001550 final Resources resources = context.getResources();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001551 if (id >= 0) {
1552 try {
1553 fieldValue = resources.getResourceTypeName(id) + '/' +
1554 resources.getResourceEntryName(id);
1555 } catch (Resources.NotFoundException e) {
1556 fieldValue = "id/0x" + Integer.toHexString(id);
1557 }
1558 } else {
1559 fieldValue = "NO_ID";
1560 }
1561 return fieldValue;
1562 }
1563
1564 private static void writeValue(BufferedWriter out, Object value) throws IOException {
1565 if (value != null) {
1566 String output = value.toString().replace("\n", "\\n");
1567 out.write(String.valueOf(output.length()));
1568 out.write(",");
1569 out.write(output);
1570 } else {
1571 out.write("4,null");
1572 }
1573 }
1574
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001575 private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) {
1576 if (!dumpView(group, out, level)) {
1577 return;
1578 }
1579
1580 final int count = group.getChildCount();
1581 for (int i = 0; i < count; i++) {
1582 final View view = group.getChildAt(i);
1583 if (view instanceof ViewGroup) {
1584 dumpViewHierarchy((ViewGroup) view, out, level + 1);
1585 } else {
1586 dumpView(view, out, level + 1);
1587 }
1588 }
1589 }
1590
1591 private static boolean dumpView(Object view, BufferedWriter out, int level) {
1592 try {
1593 for (int i = 0; i < level; i++) {
1594 out.write(' ');
1595 }
1596 out.write(view.getClass().getName());
1597 out.write('@');
1598 out.write(Integer.toHexString(view.hashCode()));
1599 out.newLine();
1600 } catch (IOException e) {
1601 Log.w("View", "Error while dumping hierarchy tree");
1602 return false;
1603 }
1604 return true;
1605 }
1606
1607 private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1608 if (mCapturedViewFieldsForClasses == null) {
1609 mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1610 }
1611 final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1612
1613 Field[] fields = map.get(klass);
1614 if (fields != null) {
1615 return fields;
1616 }
1617
1618 final ArrayList<Field> foundFields = new ArrayList<Field>();
1619 fields = klass.getFields();
1620
1621 int count = fields.length;
1622 for (int i = 0; i < count; i++) {
1623 final Field field = fields[i];
1624 if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1625 field.setAccessible(true);
1626 foundFields.add(field);
1627 }
1628 }
1629
1630 fields = foundFields.toArray(new Field[foundFields.size()]);
1631 map.put(klass, fields);
1632
1633 return fields;
1634 }
1635
1636 private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1637 if (mCapturedViewMethodsForClasses == null) {
1638 mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1639 }
1640 final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1641
1642 Method[] methods = map.get(klass);
1643 if (methods != null) {
1644 return methods;
1645 }
1646
1647 final ArrayList<Method> foundMethods = new ArrayList<Method>();
1648 methods = klass.getMethods();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001649
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001650 int count = methods.length;
1651 for (int i = 0; i < count; i++) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001652 final Method method = methods[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001653 if (method.getParameterTypes().length == 0 &&
1654 method.isAnnotationPresent(CapturedViewProperty.class) &&
1655 method.getReturnType() != Void.class) {
1656 method.setAccessible(true);
1657 foundMethods.add(method);
1658 }
1659 }
1660
1661 methods = foundMethods.toArray(new Method[foundMethods.size()]);
1662 map.put(klass, methods);
1663
1664 return methods;
1665 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001666
1667 private static String capturedViewExportMethods(Object obj, Class<?> klass,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001668 String prefix) {
1669
1670 if (obj == null) {
1671 return "null";
1672 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001674 StringBuilder sb = new StringBuilder();
1675 final Method[] methods = capturedViewGetPropertyMethods(klass);
1676
1677 int count = methods.length;
1678 for (int i = 0; i < count; i++) {
1679 final Method method = methods[i];
1680 try {
1681 Object methodValue = method.invoke(obj, (Object[]) null);
1682 final Class<?> returnType = method.getReturnType();
Romain Guya1f3e4a2009-06-04 15:10:46 -07001683
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001684 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1685 if (property.retrieveReturn()) {
1686 //we are interested in the second level data only
1687 sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001688 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001689 sb.append(prefix);
1690 sb.append(method.getName());
1691 sb.append("()=");
Romain Guya1f3e4a2009-06-04 15:10:46 -07001692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 if (methodValue != null) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001694 final String value = methodValue.toString().replace("\n", "\\n");
1695 sb.append(value);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001696 } else {
1697 sb.append("null");
1698 }
1699 sb.append("; ");
1700 }
1701 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001702 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001703 //we simply ignore this method
1704 } catch (InvocationTargetException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001705 //Exception InvocationTarget, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001706 //we simply ignore this method
Romain Guya1f3e4a2009-06-04 15:10:46 -07001707 }
1708 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001709 return sb.toString();
1710 }
1711
1712 private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001713
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001714 if (obj == null) {
1715 return "null";
1716 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001717
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001718 StringBuilder sb = new StringBuilder();
1719 final Field[] fields = capturedViewGetPropertyFields(klass);
1720
1721 int count = fields.length;
1722 for (int i = 0; i < count; i++) {
1723 final Field field = fields[i];
1724 try {
1725 Object fieldValue = field.get(obj);
1726
1727 sb.append(prefix);
1728 sb.append(field.getName());
1729 sb.append("=");
1730
1731 if (fieldValue != null) {
1732 final String value = fieldValue.toString().replace("\n", "\\n");
1733 sb.append(value);
1734 } else {
1735 sb.append("null");
1736 }
1737 sb.append(' ');
1738 } catch (IllegalAccessException e) {
Romain Guya1f3e4a2009-06-04 15:10:46 -07001739 //Exception IllegalAccess, it is OK here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740 //we simply ignore this field
1741 }
1742 }
1743 return sb.toString();
1744 }
Romain Guya1f3e4a2009-06-04 15:10:46 -07001745
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001746 /**
Romain Guya1f3e4a2009-06-04 15:10:46 -07001747 * Dump view info for id based instrument test generation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001748 * (and possibly further data analysis). The results are dumped
Romain Guya1f3e4a2009-06-04 15:10:46 -07001749 * to the log.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001750 * @param tag for log
1751 * @param view for dump
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001752 */
Romain Guya1f3e4a2009-06-04 15:10:46 -07001753 public static void dumpCapturedView(String tag, Object view) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001754 Class<?> klass = view.getClass();
1755 StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1756 sb.append(capturedViewExportFields(view, klass, ""));
Romain Guya1f3e4a2009-06-04 15:10:46 -07001757 sb.append(capturedViewExportMethods(view, klass, ""));
1758 Log.d(tag, sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001759 }
1760}