• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.view;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Rect;
24 import android.os.Debug;
25 import android.os.Handler;
26 import android.os.RemoteException;
27 import android.util.DisplayMetrics;
28 import android.util.Log;
29 
30 import java.io.BufferedOutputStream;
31 import java.io.BufferedWriter;
32 import java.io.ByteArrayOutputStream;
33 import java.io.DataOutputStream;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.io.OutputStreamWriter;
37 import java.lang.annotation.ElementType;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.lang.annotation.Target;
41 import java.lang.reflect.AccessibleObject;
42 import java.lang.reflect.Field;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.concurrent.Callable;
48 import java.util.concurrent.CancellationException;
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.ExecutionException;
51 import java.util.concurrent.FutureTask;
52 import java.util.concurrent.TimeoutException;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicReference;
55 
56 /**
57  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
58  */
59 public class ViewDebug {
60     /**
61      * @deprecated This flag is now unused
62      */
63     @Deprecated
64     public static final boolean TRACE_HIERARCHY = false;
65 
66     /**
67      * @deprecated This flag is now unused
68      */
69     @Deprecated
70     public static final boolean TRACE_RECYCLER = false;
71 
72     /**
73      * Enables detailed logging of drag/drop operations.
74      * @hide
75      */
76     public static final boolean DEBUG_DRAG = false;
77 
78     /**
79      * This annotation can be used to mark fields and methods to be dumped by
80      * the view server. Only non-void methods with no arguments can be annotated
81      * by this annotation.
82      */
83     @Target({ ElementType.FIELD, ElementType.METHOD })
84     @Retention(RetentionPolicy.RUNTIME)
85     public @interface ExportedProperty {
86         /**
87          * When resolveId is true, and if the annotated field/method return value
88          * is an int, the value is converted to an Android's resource name.
89          *
90          * @return true if the property's value must be transformed into an Android
91          *         resource name, false otherwise
92          */
resolveId()93         boolean resolveId() default false;
94 
95         /**
96          * A mapping can be defined to map int values to specific strings. For
97          * instance, View.getVisibility() returns 0, 4 or 8. However, these values
98          * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see
99          * these human readable values:
100          *
101          * <pre>
102          * @ViewDebug.ExportedProperty(mapping = {
103          *     @ViewDebug.IntToString(from = 0, to = "VISIBLE"),
104          *     @ViewDebug.IntToString(from = 4, to = "INVISIBLE"),
105          *     @ViewDebug.IntToString(from = 8, to = "GONE")
106          * })
107          * public int getVisibility() { ...
108          * <pre>
109          *
110          * @return An array of int to String mappings
111          *
112          * @see android.view.ViewDebug.IntToString
113          */
mapping()114         IntToString[] mapping() default { };
115 
116         /**
117          * A mapping can be defined to map array indices to specific strings.
118          * A mapping can be used to see human readable values for the indices
119          * of an array:
120          *
121          * <pre>
122          * @ViewDebug.ExportedProperty(indexMapping = {
123          *     @ViewDebug.IntToString(from = 0, to = "INVALID"),
124          *     @ViewDebug.IntToString(from = 1, to = "FIRST"),
125          *     @ViewDebug.IntToString(from = 2, to = "SECOND")
126          * })
127          * private int[] mElements;
128          * <pre>
129          *
130          * @return An array of int to String mappings
131          *
132          * @see android.view.ViewDebug.IntToString
133          * @see #mapping()
134          */
indexMapping()135         IntToString[] indexMapping() default { };
136 
137         /**
138          * A flags mapping can be defined to map flags encoded in an integer to
139          * specific strings. A mapping can be used to see human readable values
140          * for the flags of an integer:
141          *
142          * <pre>
143          * @ViewDebug.ExportedProperty(flagMapping = {
144          *     @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"),
145          *     @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"),
146          * })
147          * private int mFlags;
148          * <pre>
149          *
150          * A specified String is output when the following is true:
151          *
152          * @return An array of int to String mappings
153          */
flagMapping()154         FlagToString[] flagMapping() default { };
155 
156         /**
157          * When deep export is turned on, this property is not dumped. Instead, the
158          * properties contained in this property are dumped. Each child property
159          * is prefixed with the name of this property.
160          *
161          * @return true if the properties of this property should be dumped
162          *
163          * @see #prefix()
164          */
deepExport()165         boolean deepExport() default false;
166 
167         /**
168          * The prefix to use on child properties when deep export is enabled
169          *
170          * @return a prefix as a String
171          *
172          * @see #deepExport()
173          */
prefix()174         String prefix() default "";
175 
176         /**
177          * Specifies the category the property falls into, such as measurement,
178          * layout, drawing, etc.
179          *
180          * @return the category as String
181          */
category()182         String category() default "";
183     }
184 
185     /**
186      * Defines a mapping from an int value to a String. Such a mapping can be used
187      * in an @ExportedProperty to provide more meaningful values to the end user.
188      *
189      * @see android.view.ViewDebug.ExportedProperty
190      */
191     @Target({ ElementType.TYPE })
192     @Retention(RetentionPolicy.RUNTIME)
193     public @interface IntToString {
194         /**
195          * The original int value to map to a String.
196          *
197          * @return An arbitrary int value.
198          */
from()199         int from();
200 
201         /**
202          * The String to use in place of the original int value.
203          *
204          * @return An arbitrary non-null String.
205          */
to()206         String to();
207     }
208 
209     /**
210      * Defines a mapping from a flag to a String. Such a mapping can be used
211      * in an @ExportedProperty to provide more meaningful values to the end user.
212      *
213      * @see android.view.ViewDebug.ExportedProperty
214      */
215     @Target({ ElementType.TYPE })
216     @Retention(RetentionPolicy.RUNTIME)
217     public @interface FlagToString {
218         /**
219          * The mask to apply to the original value.
220          *
221          * @return An arbitrary int value.
222          */
mask()223         int mask();
224 
225         /**
226          * The value to compare to the result of:
227          * <code>original value &amp; {@link #mask()}</code>.
228          *
229          * @return An arbitrary value.
230          */
equals()231         int equals();
232 
233         /**
234          * The String to use in place of the original int value.
235          *
236          * @return An arbitrary non-null String.
237          */
name()238         String name();
239 
240         /**
241          * Indicates whether to output the flag when the test is true,
242          * or false. Defaults to true.
243          */
outputIf()244         boolean outputIf() default true;
245     }
246 
247     /**
248      * This annotation can be used to mark fields and methods to be dumped when
249      * the view is captured. Methods with this annotation must have no arguments
250      * and must return a valid type of data.
251      */
252     @Target({ ElementType.FIELD, ElementType.METHOD })
253     @Retention(RetentionPolicy.RUNTIME)
254     public @interface CapturedViewProperty {
255         /**
256          * When retrieveReturn is true, we need to retrieve second level methods
257          * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
258          * we will set retrieveReturn = true on the annotation of
259          * myView.getFirstLevelMethod()
260          * @return true if we need the second level methods
261          */
retrieveReturn()262         boolean retrieveReturn() default false;
263     }
264 
265     /**
266      * Allows a View to inject custom children into HierarchyViewer. For example,
267      * WebView uses this to add its internal layer tree as a child to itself
268      * @hide
269      */
270     public interface HierarchyHandler {
271         /**
272          * Dumps custom children to hierarchy viewer.
273          * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
274          * for the format
275          *
276          * An empty implementation should simply do nothing
277          *
278          * @param out The output writer
279          * @param level The indentation level
280          */
dumpViewHierarchyWithProperties(BufferedWriter out, int level)281         public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
282 
283         /**
284          * Returns a View to enable grabbing screenshots from custom children
285          * returned in dumpViewHierarchyWithProperties.
286          *
287          * @param className The className of the view to find
288          * @param hashCode The hashCode of the view to find
289          * @return the View to capture from, or null if not found
290          */
findHierarchyView(String className, int hashCode)291         public View findHierarchyView(String className, int hashCode);
292     }
293 
294     private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
295     private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
296 
297     // Maximum delay in ms after which we stop trying to capture a View's drawing
298     private static final int CAPTURE_TIMEOUT = 4000;
299 
300     private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
301     private static final String REMOTE_COMMAND_DUMP = "DUMP";
302     private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
303     private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT";
304     private static final String REMOTE_PROFILE = "PROFILE";
305     private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
306     private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
307 
308     private static HashMap<Class<?>, Field[]> sFieldsForClasses;
309     private static HashMap<Class<?>, Method[]> sMethodsForClasses;
310     private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
311 
312     /**
313      * @deprecated This enum is now unused
314      */
315     @Deprecated
316     public enum HierarchyTraceType {
317         INVALIDATE,
318         INVALIDATE_CHILD,
319         INVALIDATE_CHILD_IN_PARENT,
320         REQUEST_LAYOUT,
321         ON_LAYOUT,
322         ON_MEASURE,
323         DRAW,
324         BUILD_CACHE
325     }
326 
327     /**
328      * @deprecated This enum is now unused
329      */
330     @Deprecated
331     public enum RecyclerTraceType {
332         NEW_VIEW,
333         BIND_VIEW,
334         RECYCLE_FROM_ACTIVE_HEAP,
335         RECYCLE_FROM_SCRAP_HEAP,
336         MOVE_TO_SCRAP_HEAP,
337         MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
338     }
339 
340     /**
341      * Returns the number of instanciated Views.
342      *
343      * @return The number of Views instanciated in the current process.
344      *
345      * @hide
346      */
getViewInstanceCount()347     public static long getViewInstanceCount() {
348         return Debug.countInstancesOfClass(View.class);
349     }
350 
351     /**
352      * Returns the number of instanciated ViewAncestors.
353      *
354      * @return The number of ViewAncestors instanciated in the current process.
355      *
356      * @hide
357      */
getViewRootImplCount()358     public static long getViewRootImplCount() {
359         return Debug.countInstancesOfClass(ViewRootImpl.class);
360     }
361 
362     /**
363      * @deprecated This method is now unused and invoking it is a no-op
364      */
365     @Deprecated
366     @SuppressWarnings({ "UnusedParameters", "deprecation" })
trace(View view, RecyclerTraceType type, int... parameters)367     public static void trace(View view, RecyclerTraceType type, int... parameters) {
368     }
369 
370     /**
371      * @deprecated This method is now unused and invoking it is a no-op
372      */
373     @Deprecated
374     @SuppressWarnings("UnusedParameters")
startRecyclerTracing(String prefix, View view)375     public static void startRecyclerTracing(String prefix, View view) {
376     }
377 
378     /**
379      * @deprecated This method is now unused and invoking it is a no-op
380      */
381     @Deprecated
382     @SuppressWarnings("UnusedParameters")
stopRecyclerTracing()383     public static void stopRecyclerTracing() {
384     }
385 
386     /**
387      * @deprecated This method is now unused and invoking it is a no-op
388      */
389     @Deprecated
390     @SuppressWarnings({ "UnusedParameters", "deprecation" })
trace(View view, HierarchyTraceType type)391     public static void trace(View view, HierarchyTraceType type) {
392     }
393 
394     /**
395      * @deprecated This method is now unused and invoking it is a no-op
396      */
397     @Deprecated
398     @SuppressWarnings("UnusedParameters")
startHierarchyTracing(String prefix, View view)399     public static void startHierarchyTracing(String prefix, View view) {
400     }
401 
402     /**
403      * @deprecated This method is now unused and invoking it is a no-op
404      */
405     @Deprecated
stopHierarchyTracing()406     public static void stopHierarchyTracing() {
407     }
408 
dispatchCommand(View view, String command, String parameters, OutputStream clientStream)409     static void dispatchCommand(View view, String command, String parameters,
410             OutputStream clientStream) throws IOException {
411 
412         // Paranoid but safe...
413         view = view.getRootView();
414 
415         if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
416             dump(view, false, true, clientStream);
417         } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
418             captureLayers(view, new DataOutputStream(clientStream));
419         } else {
420             final String[] params = parameters.split(" ");
421             if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
422                 capture(view, clientStream, params[0]);
423             } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) {
424                 outputDisplayList(view, params[0]);
425             } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
426                 invalidate(view, params[0]);
427             } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
428                 requestLayout(view, params[0]);
429             } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
430                 profile(view, clientStream, params[0]);
431             }
432         }
433     }
434 
435     /** @hide */
findView(View root, String parameter)436     public static View findView(View root, String parameter) {
437         // Look by type/hashcode
438         if (parameter.indexOf('@') != -1) {
439             final String[] ids = parameter.split("@");
440             final String className = ids[0];
441             final int hashCode = (int) Long.parseLong(ids[1], 16);
442 
443             View view = root.getRootView();
444             if (view instanceof ViewGroup) {
445                 return findView((ViewGroup) view, className, hashCode);
446             }
447         } else {
448             // Look by id
449             final int id = root.getResources().getIdentifier(parameter, null, null);
450             return root.getRootView().findViewById(id);
451         }
452 
453         return null;
454     }
455 
invalidate(View root, String parameter)456     private static void invalidate(View root, String parameter) {
457         final View view = findView(root, parameter);
458         if (view != null) {
459             view.postInvalidate();
460         }
461     }
462 
requestLayout(View root, String parameter)463     private static void requestLayout(View root, String parameter) {
464         final View view = findView(root, parameter);
465         if (view != null) {
466             root.post(new Runnable() {
467                 public void run() {
468                     view.requestLayout();
469                 }
470             });
471         }
472     }
473 
profile(View root, OutputStream clientStream, String parameter)474     private static void profile(View root, OutputStream clientStream, String parameter)
475             throws IOException {
476 
477         final View view = findView(root, parameter);
478         BufferedWriter out = null;
479         try {
480             out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
481 
482             if (view != null) {
483                 profileViewAndChildren(view, out);
484             } else {
485                 out.write("-1 -1 -1");
486                 out.newLine();
487             }
488             out.write("DONE.");
489             out.newLine();
490         } catch (Exception e) {
491             android.util.Log.w("View", "Problem profiling the view:", e);
492         } finally {
493             if (out != null) {
494                 out.close();
495             }
496         }
497     }
498 
499     /** @hide */
profileViewAndChildren(final View view, BufferedWriter out)500     public static void profileViewAndChildren(final View view, BufferedWriter out)
501             throws IOException {
502         profileViewAndChildren(view, out, true);
503     }
504 
profileViewAndChildren(final View view, BufferedWriter out, boolean root)505     private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
506             throws IOException {
507 
508         long durationMeasure =
509                 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
510                 ? profileViewOperation(view, new ViewOperation<Void>() {
511                             public Void[] pre() {
512                                 forceLayout(view);
513                                 return null;
514                             }
515 
516                             private void forceLayout(View view) {
517                                 view.forceLayout();
518                                 if (view instanceof ViewGroup) {
519                                     ViewGroup group = (ViewGroup) view;
520                                     final int count = group.getChildCount();
521                                     for (int i = 0; i < count; i++) {
522                                         forceLayout(group.getChildAt(i));
523                                     }
524                                 }
525                             }
526 
527                             public void run(Void... data) {
528                                 view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
529                             }
530 
531                             public void post(Void... data) {
532                             }
533                         })
534                         : 0;
535         long durationLayout =
536                 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
537                 ? profileViewOperation(view, new ViewOperation<Void>() {
538                             public Void[] pre() {
539                                 return null;
540                             }
541 
542                             public void run(Void... data) {
543                                 view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
544                             }
545 
546                             public void post(Void... data) {
547                             }
548                         }) : 0;
549         long durationDraw =
550                 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
551                 ? profileViewOperation(view, new ViewOperation<Object>() {
552                             public Object[] pre() {
553                                 final DisplayMetrics metrics =
554                                         (view != null && view.getResources() != null) ?
555                                                 view.getResources().getDisplayMetrics() : null;
556                                 final Bitmap bitmap = metrics != null ?
557                                         Bitmap.createBitmap(metrics, metrics.widthPixels,
558                                                 metrics.heightPixels, Bitmap.Config.RGB_565) : null;
559                                 final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
560                                 return new Object[] {
561                                         bitmap, canvas
562                                 };
563                             }
564 
565                             public void run(Object... data) {
566                                 if (data[1] != null) {
567                                     view.draw((Canvas) data[1]);
568                                 }
569                             }
570 
571                             public void post(Object... data) {
572                                 if (data[1] != null) {
573                                     ((Canvas) data[1]).setBitmap(null);
574                                 }
575                                 if (data[0] != null) {
576                                     ((Bitmap) data[0]).recycle();
577                                 }
578                             }
579                         }) : 0;
580         out.write(String.valueOf(durationMeasure));
581         out.write(' ');
582         out.write(String.valueOf(durationLayout));
583         out.write(' ');
584         out.write(String.valueOf(durationDraw));
585         out.newLine();
586         if (view instanceof ViewGroup) {
587             ViewGroup group = (ViewGroup) view;
588             final int count = group.getChildCount();
589             for (int i = 0; i < count; i++) {
590                 profileViewAndChildren(group.getChildAt(i), out, false);
591             }
592         }
593     }
594 
595     interface ViewOperation<T> {
pre()596         T[] pre();
run(T... data)597         void run(T... data);
post(T... data)598         void post(T... data);
599     }
600 
profileViewOperation(View view, final ViewOperation<T> operation)601     private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
602         final CountDownLatch latch = new CountDownLatch(1);
603         final long[] duration = new long[1];
604 
605         view.post(new Runnable() {
606             public void run() {
607                 try {
608                     T[] data = operation.pre();
609                     long start = Debug.threadCpuTimeNanos();
610                     //noinspection unchecked
611                     operation.run(data);
612                     duration[0] = Debug.threadCpuTimeNanos() - start;
613                     //noinspection unchecked
614                     operation.post(data);
615                 } finally {
616                     latch.countDown();
617                 }
618             }
619         });
620 
621         try {
622             if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
623                 Log.w("View", "Could not complete the profiling of the view " + view);
624                 return -1;
625             }
626         } catch (InterruptedException e) {
627             Log.w("View", "Could not complete the profiling of the view " + view);
628             Thread.currentThread().interrupt();
629             return -1;
630         }
631 
632         return duration[0];
633     }
634 
635     /** @hide */
captureLayers(View root, final DataOutputStream clientStream)636     public static void captureLayers(View root, final DataOutputStream clientStream)
637             throws IOException {
638 
639         try {
640             Rect outRect = new Rect();
641             try {
642                 root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
643             } catch (RemoteException e) {
644                 // Ignore
645             }
646 
647             clientStream.writeInt(outRect.width());
648             clientStream.writeInt(outRect.height());
649 
650             captureViewLayer(root, clientStream, true);
651 
652             clientStream.write(2);
653         } finally {
654             clientStream.close();
655         }
656     }
657 
captureViewLayer(View view, DataOutputStream clientStream, boolean visible)658     private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
659             throws IOException {
660 
661         final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
662 
663         if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
664             final int id = view.getId();
665             String name = view.getClass().getSimpleName();
666             if (id != View.NO_ID) {
667                 name = resolveId(view.getContext(), id).toString();
668             }
669 
670             clientStream.write(1);
671             clientStream.writeUTF(name);
672             clientStream.writeByte(localVisible ? 1 : 0);
673 
674             int[] position = new int[2];
675             // XXX: Should happen on the UI thread
676             view.getLocationInWindow(position);
677 
678             clientStream.writeInt(position[0]);
679             clientStream.writeInt(position[1]);
680             clientStream.flush();
681 
682             Bitmap b = performViewCapture(view, true);
683             if (b != null) {
684                 ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
685                         b.getHeight() * 2);
686                 b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
687                 clientStream.writeInt(arrayOut.size());
688                 arrayOut.writeTo(clientStream);
689             }
690             clientStream.flush();
691         }
692 
693         if (view instanceof ViewGroup) {
694             ViewGroup group = (ViewGroup) view;
695             int count = group.getChildCount();
696 
697             for (int i = 0; i < count; i++) {
698                 captureViewLayer(group.getChildAt(i), clientStream, localVisible);
699             }
700         }
701     }
702 
outputDisplayList(View root, String parameter)703     private static void outputDisplayList(View root, String parameter) throws IOException {
704         final View view = findView(root, parameter);
705         view.getViewRootImpl().outputDisplayList(view);
706     }
707 
708     /** @hide */
outputDisplayList(View root, View target)709     public static void outputDisplayList(View root, View target) {
710         root.getViewRootImpl().outputDisplayList(target);
711     }
712 
capture(View root, final OutputStream clientStream, String parameter)713     private static void capture(View root, final OutputStream clientStream, String parameter)
714             throws IOException {
715 
716         final View captureView = findView(root, parameter);
717         capture(root, clientStream, captureView);
718     }
719 
720     /** @hide */
capture(View root, final OutputStream clientStream, View captureView)721     public static void capture(View root, final OutputStream clientStream, View captureView)
722             throws IOException {
723         Bitmap b = performViewCapture(captureView, false);
724 
725         if (b == null) {
726             Log.w("View", "Failed to create capture bitmap!");
727             // Send an empty one so that it doesn't get stuck waiting for
728             // something.
729             b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
730                     1, 1, Bitmap.Config.ARGB_8888);
731         }
732 
733         BufferedOutputStream out = null;
734         try {
735             out = new BufferedOutputStream(clientStream, 32 * 1024);
736             b.compress(Bitmap.CompressFormat.PNG, 100, out);
737             out.flush();
738         } finally {
739             if (out != null) {
740                 out.close();
741             }
742             b.recycle();
743         }
744     }
745 
performViewCapture(final View captureView, final boolean skpiChildren)746     private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
747         if (captureView != null) {
748             final CountDownLatch latch = new CountDownLatch(1);
749             final Bitmap[] cache = new Bitmap[1];
750 
751             captureView.post(new Runnable() {
752                 public void run() {
753                     try {
754                         cache[0] = captureView.createSnapshot(
755                                 Bitmap.Config.ARGB_8888, 0, skpiChildren);
756                     } catch (OutOfMemoryError e) {
757                         Log.w("View", "Out of memory for bitmap");
758                     } finally {
759                         latch.countDown();
760                     }
761                 }
762             });
763 
764             try {
765                 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
766                 return cache[0];
767             } catch (InterruptedException e) {
768                 Log.w("View", "Could not complete the capture of the view " + captureView);
769                 Thread.currentThread().interrupt();
770             }
771         }
772 
773         return null;
774     }
775 
776     /**
777      * Dumps the view hierarchy starting from the given view.
778      * @hide
779      */
dump(View root, boolean skipChildren, boolean includeProperties, OutputStream clientStream)780     public static void dump(View root, boolean skipChildren, boolean includeProperties,
781             OutputStream clientStream) throws IOException {
782         BufferedWriter out = null;
783         try {
784             out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
785             View view = root.getRootView();
786             if (view instanceof ViewGroup) {
787                 ViewGroup group = (ViewGroup) view;
788                 dumpViewHierarchy(group.getContext(), group, out, 0,
789                         skipChildren, includeProperties);
790             }
791             out.write("DONE.");
792             out.newLine();
793         } catch (Exception e) {
794             android.util.Log.w("View", "Problem dumping the view:", e);
795         } finally {
796             if (out != null) {
797                 out.close();
798             }
799         }
800     }
801 
findView(ViewGroup group, String className, int hashCode)802     private static View findView(ViewGroup group, String className, int hashCode) {
803         if (isRequestedView(group, className, hashCode)) {
804             return group;
805         }
806 
807         final int count = group.getChildCount();
808         for (int i = 0; i < count; i++) {
809             final View view = group.getChildAt(i);
810             if (view instanceof ViewGroup) {
811                 final View found = findView((ViewGroup) view, className, hashCode);
812                 if (found != null) {
813                     return found;
814                 }
815             } else if (isRequestedView(view, className, hashCode)) {
816                 return view;
817             }
818             if (view instanceof HierarchyHandler) {
819                 final View found = ((HierarchyHandler)view)
820                         .findHierarchyView(className, hashCode);
821                 if (found != null) {
822                     return found;
823                 }
824             }
825         }
826 
827         return null;
828     }
829 
isRequestedView(View view, String className, int hashCode)830     private static boolean isRequestedView(View view, String className, int hashCode) {
831         return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
832     }
833 
dumpViewHierarchy(Context context, ViewGroup group, BufferedWriter out, int level, boolean skipChildren, boolean includeProperties)834     private static void dumpViewHierarchy(Context context, ViewGroup group,
835             BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
836         if (!dumpView(context, group, out, level, includeProperties)) {
837             return;
838         }
839 
840         if (skipChildren) {
841             return;
842         }
843 
844         final int count = group.getChildCount();
845         for (int i = 0; i < count; i++) {
846             final View view = group.getChildAt(i);
847             if (view instanceof ViewGroup) {
848                 dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
849                         includeProperties);
850             } else {
851                 dumpView(context, view, out, level + 1, includeProperties);
852             }
853         }
854         if (group instanceof HierarchyHandler) {
855             ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
856         }
857     }
858 
dumpView(Context context, View view, BufferedWriter out, int level, boolean includeProperties)859     private static boolean dumpView(Context context, View view,
860             BufferedWriter out, int level, boolean includeProperties) {
861 
862         try {
863             for (int i = 0; i < level; i++) {
864                 out.write(' ');
865             }
866             out.write(view.getClass().getName());
867             out.write('@');
868             out.write(Integer.toHexString(view.hashCode()));
869             out.write(' ');
870             if (includeProperties) {
871                 dumpViewProperties(context, view, out);
872             }
873             out.newLine();
874         } catch (IOException e) {
875             Log.w("View", "Error while dumping hierarchy tree");
876             return false;
877         }
878         return true;
879     }
880 
getExportedPropertyFields(Class<?> klass)881     private static Field[] getExportedPropertyFields(Class<?> klass) {
882         if (sFieldsForClasses == null) {
883             sFieldsForClasses = new HashMap<Class<?>, Field[]>();
884         }
885         if (sAnnotations == null) {
886             sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
887         }
888 
889         final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
890 
891         Field[] fields = map.get(klass);
892         if (fields != null) {
893             return fields;
894         }
895 
896         final ArrayList<Field> foundFields = new ArrayList<Field>();
897         fields = klass.getDeclaredFields();
898 
899         int count = fields.length;
900         for (int i = 0; i < count; i++) {
901             final Field field = fields[i];
902             if (field.isAnnotationPresent(ExportedProperty.class)) {
903                 field.setAccessible(true);
904                 foundFields.add(field);
905                 sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
906             }
907         }
908 
909         fields = foundFields.toArray(new Field[foundFields.size()]);
910         map.put(klass, fields);
911 
912         return fields;
913     }
914 
getExportedPropertyMethods(Class<?> klass)915     private static Method[] getExportedPropertyMethods(Class<?> klass) {
916         if (sMethodsForClasses == null) {
917             sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
918         }
919         if (sAnnotations == null) {
920             sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
921         }
922 
923         final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
924 
925         Method[] methods = map.get(klass);
926         if (methods != null) {
927             return methods;
928         }
929 
930         final ArrayList<Method> foundMethods = new ArrayList<Method>();
931         methods = klass.getDeclaredMethods();
932 
933         int count = methods.length;
934         for (int i = 0; i < count; i++) {
935             final Method method = methods[i];
936             if (method.getParameterTypes().length == 0 &&
937                     method.isAnnotationPresent(ExportedProperty.class) &&
938                     method.getReturnType() != Void.class) {
939                 method.setAccessible(true);
940                 foundMethods.add(method);
941                 sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
942             }
943         }
944 
945         methods = foundMethods.toArray(new Method[foundMethods.size()]);
946         map.put(klass, methods);
947 
948         return methods;
949     }
950 
dumpViewProperties(Context context, Object view, BufferedWriter out)951     private static void dumpViewProperties(Context context, Object view,
952             BufferedWriter out) throws IOException {
953 
954         dumpViewProperties(context, view, out, "");
955     }
956 
dumpViewProperties(Context context, Object view, BufferedWriter out, String prefix)957     private static void dumpViewProperties(Context context, Object view,
958             BufferedWriter out, String prefix) throws IOException {
959 
960         if (view == null) {
961             out.write(prefix + "=4,null ");
962             return;
963         }
964 
965         Class<?> klass = view.getClass();
966         do {
967             exportFields(context, view, out, klass, prefix);
968             exportMethods(context, view, out, klass, prefix);
969             klass = klass.getSuperclass();
970         } while (klass != Object.class);
971     }
972 
callMethodOnAppropriateTheadBlocking(final Method method, final Object object)973     private static Object callMethodOnAppropriateTheadBlocking(final Method method,
974             final Object object) throws IllegalAccessException, InvocationTargetException,
975             TimeoutException {
976         if (!(object instanceof View)) {
977             return method.invoke(object, (Object[]) null);
978         }
979 
980         final View view = (View) object;
981         Callable<Object> callable = new Callable<Object>() {
982                 @Override
983                 public Object call() throws IllegalAccessException, InvocationTargetException {
984                     return method.invoke(view, (Object[]) null);
985                 }
986         };
987         FutureTask<Object> future = new FutureTask<Object>(callable);
988         // Try to use the handler provided by the view
989         Handler handler = view.getHandler();
990         // Fall back on using the main thread
991         if (handler == null) {
992             handler = new Handler(android.os.Looper.getMainLooper());
993         }
994         handler.post(future);
995         while (true) {
996             try {
997                 return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
998             } catch (ExecutionException e) {
999                 Throwable t = e.getCause();
1000                 if (t instanceof IllegalAccessException) {
1001                     throw (IllegalAccessException)t;
1002                 }
1003                 if (t instanceof InvocationTargetException) {
1004                     throw (InvocationTargetException)t;
1005                 }
1006                 throw new RuntimeException("Unexpected exception", t);
1007             } catch (InterruptedException e) {
1008                 // Call get again
1009             } catch (CancellationException e) {
1010                 throw new RuntimeException("Unexpected cancellation exception", e);
1011             }
1012         }
1013     }
1014 
exportMethods(Context context, Object view, BufferedWriter out, Class<?> klass, String prefix)1015     private static void exportMethods(Context context, Object view, BufferedWriter out,
1016             Class<?> klass, String prefix) throws IOException {
1017 
1018         final Method[] methods = getExportedPropertyMethods(klass);
1019 
1020         int count = methods.length;
1021         for (int i = 0; i < count; i++) {
1022             final Method method = methods[i];
1023             //noinspection EmptyCatchBlock
1024             try {
1025                 Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
1026                 final Class<?> returnType = method.getReturnType();
1027                 final ExportedProperty property = sAnnotations.get(method);
1028                 String categoryPrefix =
1029                         property.category().length() != 0 ? property.category() + ":" : "";
1030 
1031                 if (returnType == int.class) {
1032 
1033                     if (property.resolveId() && context != null) {
1034                         final int id = (Integer) methodValue;
1035                         methodValue = resolveId(context, id);
1036                     } else {
1037                         final FlagToString[] flagsMapping = property.flagMapping();
1038                         if (flagsMapping.length > 0) {
1039                             final int intValue = (Integer) methodValue;
1040                             final String valuePrefix =
1041                                     categoryPrefix + prefix + method.getName() + '_';
1042                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1043                         }
1044 
1045                         final IntToString[] mapping = property.mapping();
1046                         if (mapping.length > 0) {
1047                             final int intValue = (Integer) methodValue;
1048                             boolean mapped = false;
1049                             int mappingCount = mapping.length;
1050                             for (int j = 0; j < mappingCount; j++) {
1051                                 final IntToString mapper = mapping[j];
1052                                 if (mapper.from() == intValue) {
1053                                     methodValue = mapper.to();
1054                                     mapped = true;
1055                                     break;
1056                                 }
1057                             }
1058 
1059                             if (!mapped) {
1060                                 methodValue = intValue;
1061                             }
1062                         }
1063                     }
1064                 } else if (returnType == int[].class) {
1065                     final int[] array = (int[]) methodValue;
1066                     final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
1067                     final String suffix = "()";
1068 
1069                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
1070 
1071                     // Probably want to return here, same as for fields.
1072                     return;
1073                 } else if (!returnType.isPrimitive()) {
1074                     if (property.deepExport()) {
1075                         dumpViewProperties(context, methodValue, out, prefix + property.prefix());
1076                         continue;
1077                     }
1078                 }
1079 
1080                 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
1081             } catch (IllegalAccessException e) {
1082             } catch (InvocationTargetException e) {
1083             } catch (TimeoutException e) {
1084             }
1085         }
1086     }
1087 
exportFields(Context context, Object view, BufferedWriter out, Class<?> klass, String prefix)1088     private static void exportFields(Context context, Object view, BufferedWriter out,
1089             Class<?> klass, String prefix) throws IOException {
1090 
1091         final Field[] fields = getExportedPropertyFields(klass);
1092 
1093         int count = fields.length;
1094         for (int i = 0; i < count; i++) {
1095             final Field field = fields[i];
1096 
1097             //noinspection EmptyCatchBlock
1098             try {
1099                 Object fieldValue = null;
1100                 final Class<?> type = field.getType();
1101                 final ExportedProperty property = sAnnotations.get(field);
1102                 String categoryPrefix =
1103                         property.category().length() != 0 ? property.category() + ":" : "";
1104 
1105                 if (type == int.class || type == byte.class) {
1106                     if (property.resolveId() && context != null) {
1107                         final int id = field.getInt(view);
1108                         fieldValue = resolveId(context, id);
1109                     } else {
1110                         final FlagToString[] flagsMapping = property.flagMapping();
1111                         if (flagsMapping.length > 0) {
1112                             final int intValue = field.getInt(view);
1113                             final String valuePrefix =
1114                                     categoryPrefix + prefix + field.getName() + '_';
1115                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
1116                         }
1117 
1118                         final IntToString[] mapping = property.mapping();
1119                         if (mapping.length > 0) {
1120                             final int intValue = field.getInt(view);
1121                             int mappingCount = mapping.length;
1122                             for (int j = 0; j < mappingCount; j++) {
1123                                 final IntToString mapped = mapping[j];
1124                                 if (mapped.from() == intValue) {
1125                                     fieldValue = mapped.to();
1126                                     break;
1127                                 }
1128                             }
1129 
1130                             if (fieldValue == null) {
1131                                 fieldValue = intValue;
1132                             }
1133                         }
1134                     }
1135                 } else if (type == int[].class) {
1136                     final int[] array = (int[]) field.get(view);
1137                     final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
1138                     final String suffix = "";
1139 
1140                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
1141 
1142                     // We exit here!
1143                     return;
1144                 } else if (!type.isPrimitive()) {
1145                     if (property.deepExport()) {
1146                         dumpViewProperties(context, field.get(view), out, prefix +
1147                                 property.prefix());
1148                         continue;
1149                     }
1150                 }
1151 
1152                 if (fieldValue == null) {
1153                     fieldValue = field.get(view);
1154                 }
1155 
1156                 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
1157             } catch (IllegalAccessException e) {
1158             }
1159         }
1160     }
1161 
writeEntry(BufferedWriter out, String prefix, String name, String suffix, Object value)1162     private static void writeEntry(BufferedWriter out, String prefix, String name,
1163             String suffix, Object value) throws IOException {
1164 
1165         out.write(prefix);
1166         out.write(name);
1167         out.write(suffix);
1168         out.write("=");
1169         writeValue(out, value);
1170         out.write(' ');
1171     }
1172 
exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping, int intValue, String prefix)1173     private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
1174             int intValue, String prefix) throws IOException {
1175 
1176         final int count = mapping.length;
1177         for (int j = 0; j < count; j++) {
1178             final FlagToString flagMapping = mapping[j];
1179             final boolean ifTrue = flagMapping.outputIf();
1180             final int maskResult = intValue & flagMapping.mask();
1181             final boolean test = maskResult == flagMapping.equals();
1182             if ((test && ifTrue) || (!test && !ifTrue)) {
1183                 final String name = flagMapping.name();
1184                 final String value = "0x" + Integer.toHexString(maskResult);
1185                 writeEntry(out, prefix, name, "", value);
1186             }
1187         }
1188     }
1189 
exportUnrolledArray(Context context, BufferedWriter out, ExportedProperty property, int[] array, String prefix, String suffix)1190     private static void exportUnrolledArray(Context context, BufferedWriter out,
1191             ExportedProperty property, int[] array, String prefix, String suffix)
1192             throws IOException {
1193 
1194         final IntToString[] indexMapping = property.indexMapping();
1195         final boolean hasIndexMapping = indexMapping.length > 0;
1196 
1197         final IntToString[] mapping = property.mapping();
1198         final boolean hasMapping = mapping.length > 0;
1199 
1200         final boolean resolveId = property.resolveId() && context != null;
1201         final int valuesCount = array.length;
1202 
1203         for (int j = 0; j < valuesCount; j++) {
1204             String name;
1205             String value = null;
1206 
1207             final int intValue = array[j];
1208 
1209             name = String.valueOf(j);
1210             if (hasIndexMapping) {
1211                 int mappingCount = indexMapping.length;
1212                 for (int k = 0; k < mappingCount; k++) {
1213                     final IntToString mapped = indexMapping[k];
1214                     if (mapped.from() == j) {
1215                         name = mapped.to();
1216                         break;
1217                     }
1218                 }
1219             }
1220 
1221             if (hasMapping) {
1222                 int mappingCount = mapping.length;
1223                 for (int k = 0; k < mappingCount; k++) {
1224                     final IntToString mapped = mapping[k];
1225                     if (mapped.from() == intValue) {
1226                         value = mapped.to();
1227                         break;
1228                     }
1229                 }
1230             }
1231 
1232             if (resolveId) {
1233                 if (value == null) value = (String) resolveId(context, intValue);
1234             } else {
1235                 value = String.valueOf(intValue);
1236             }
1237 
1238             writeEntry(out, prefix, name, suffix, value);
1239         }
1240     }
1241 
resolveId(Context context, int id)1242     static Object resolveId(Context context, int id) {
1243         Object fieldValue;
1244         final Resources resources = context.getResources();
1245         if (id >= 0) {
1246             try {
1247                 fieldValue = resources.getResourceTypeName(id) + '/' +
1248                         resources.getResourceEntryName(id);
1249             } catch (Resources.NotFoundException e) {
1250                 fieldValue = "id/0x" + Integer.toHexString(id);
1251             }
1252         } else {
1253             fieldValue = "NO_ID";
1254         }
1255         return fieldValue;
1256     }
1257 
writeValue(BufferedWriter out, Object value)1258     private static void writeValue(BufferedWriter out, Object value) throws IOException {
1259         if (value != null) {
1260             String output = "[EXCEPTION]";
1261             try {
1262                 output = value.toString().replace("\n", "\\n");
1263             } finally {
1264                 out.write(String.valueOf(output.length()));
1265                 out.write(",");
1266                 out.write(output);
1267             }
1268         } else {
1269             out.write("4,null");
1270         }
1271     }
1272 
capturedViewGetPropertyFields(Class<?> klass)1273     private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
1274         if (mCapturedViewFieldsForClasses == null) {
1275             mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
1276         }
1277         final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
1278 
1279         Field[] fields = map.get(klass);
1280         if (fields != null) {
1281             return fields;
1282         }
1283 
1284         final ArrayList<Field> foundFields = new ArrayList<Field>();
1285         fields = klass.getFields();
1286 
1287         int count = fields.length;
1288         for (int i = 0; i < count; i++) {
1289             final Field field = fields[i];
1290             if (field.isAnnotationPresent(CapturedViewProperty.class)) {
1291                 field.setAccessible(true);
1292                 foundFields.add(field);
1293             }
1294         }
1295 
1296         fields = foundFields.toArray(new Field[foundFields.size()]);
1297         map.put(klass, fields);
1298 
1299         return fields;
1300     }
1301 
capturedViewGetPropertyMethods(Class<?> klass)1302     private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
1303         if (mCapturedViewMethodsForClasses == null) {
1304             mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
1305         }
1306         final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
1307 
1308         Method[] methods = map.get(klass);
1309         if (methods != null) {
1310             return methods;
1311         }
1312 
1313         final ArrayList<Method> foundMethods = new ArrayList<Method>();
1314         methods = klass.getMethods();
1315 
1316         int count = methods.length;
1317         for (int i = 0; i < count; i++) {
1318             final Method method = methods[i];
1319             if (method.getParameterTypes().length == 0 &&
1320                     method.isAnnotationPresent(CapturedViewProperty.class) &&
1321                     method.getReturnType() != Void.class) {
1322                 method.setAccessible(true);
1323                 foundMethods.add(method);
1324             }
1325         }
1326 
1327         methods = foundMethods.toArray(new Method[foundMethods.size()]);
1328         map.put(klass, methods);
1329 
1330         return methods;
1331     }
1332 
capturedViewExportMethods(Object obj, Class<?> klass, String prefix)1333     private static String capturedViewExportMethods(Object obj, Class<?> klass,
1334             String prefix) {
1335 
1336         if (obj == null) {
1337             return "null";
1338         }
1339 
1340         StringBuilder sb = new StringBuilder();
1341         final Method[] methods = capturedViewGetPropertyMethods(klass);
1342 
1343         int count = methods.length;
1344         for (int i = 0; i < count; i++) {
1345             final Method method = methods[i];
1346             try {
1347                 Object methodValue = method.invoke(obj, (Object[]) null);
1348                 final Class<?> returnType = method.getReturnType();
1349 
1350                 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
1351                 if (property.retrieveReturn()) {
1352                     //we are interested in the second level data only
1353                     sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
1354                 } else {
1355                     sb.append(prefix);
1356                     sb.append(method.getName());
1357                     sb.append("()=");
1358 
1359                     if (methodValue != null) {
1360                         final String value = methodValue.toString().replace("\n", "\\n");
1361                         sb.append(value);
1362                     } else {
1363                         sb.append("null");
1364                     }
1365                     sb.append("; ");
1366                 }
1367               } catch (IllegalAccessException e) {
1368                   //Exception IllegalAccess, it is OK here
1369                   //we simply ignore this method
1370               } catch (InvocationTargetException e) {
1371                   //Exception InvocationTarget, it is OK here
1372                   //we simply ignore this method
1373               }
1374         }
1375         return sb.toString();
1376     }
1377 
capturedViewExportFields(Object obj, Class<?> klass, String prefix)1378     private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
1379         if (obj == null) {
1380             return "null";
1381         }
1382 
1383         StringBuilder sb = new StringBuilder();
1384         final Field[] fields = capturedViewGetPropertyFields(klass);
1385 
1386         int count = fields.length;
1387         for (int i = 0; i < count; i++) {
1388             final Field field = fields[i];
1389             try {
1390                 Object fieldValue = field.get(obj);
1391 
1392                 sb.append(prefix);
1393                 sb.append(field.getName());
1394                 sb.append("=");
1395 
1396                 if (fieldValue != null) {
1397                     final String value = fieldValue.toString().replace("\n", "\\n");
1398                     sb.append(value);
1399                 } else {
1400                     sb.append("null");
1401                 }
1402                 sb.append(' ');
1403             } catch (IllegalAccessException e) {
1404                 //Exception IllegalAccess, it is OK here
1405                 //we simply ignore this field
1406             }
1407         }
1408         return sb.toString();
1409     }
1410 
1411     /**
1412      * Dump view info for id based instrument test generation
1413      * (and possibly further data analysis). The results are dumped
1414      * to the log.
1415      * @param tag for log
1416      * @param view for dump
1417      */
dumpCapturedView(String tag, Object view)1418     public static void dumpCapturedView(String tag, Object view) {
1419         Class<?> klass = view.getClass();
1420         StringBuilder sb = new StringBuilder(klass.getName() + ": ");
1421         sb.append(capturedViewExportFields(view, klass, ""));
1422         sb.append(capturedViewExportMethods(view, klass, ""));
1423         Log.d(tag, sb.toString());
1424     }
1425 
1426     /**
1427      * Invoke a particular method on given view.
1428      * The given method is always invoked on the UI thread. The caller thread will stall until the
1429      * method invocation is complete. Returns an object equal to the result of the method
1430      * invocation, null if the method is declared to return void
1431      * @throws Exception if the method invocation caused any exception
1432      * @hide
1433      */
invokeViewMethod(final View view, final Method method, final Object[] args)1434     public static Object invokeViewMethod(final View view, final Method method,
1435             final Object[] args) {
1436         final CountDownLatch latch = new CountDownLatch(1);
1437         final AtomicReference<Object> result = new AtomicReference<Object>();
1438         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1439 
1440         view.post(new Runnable() {
1441             @Override
1442             public void run() {
1443                 try {
1444                     result.set(method.invoke(view, args));
1445                 } catch (InvocationTargetException e) {
1446                     exception.set(e.getCause());
1447                 } catch (Exception e) {
1448                     exception.set(e);
1449                 }
1450 
1451                 latch.countDown();
1452             }
1453         });
1454 
1455         try {
1456             latch.await();
1457         } catch (InterruptedException e) {
1458             throw new RuntimeException(e);
1459         }
1460 
1461         if (exception.get() != null) {
1462             throw new RuntimeException(exception.get());
1463         }
1464 
1465         return result.get();
1466     }
1467 
1468     /**
1469      * @hide
1470      */
setLayoutParameter(final View view, final String param, final int value)1471     public static void setLayoutParameter(final View view, final String param, final int value)
1472             throws NoSuchFieldException, IllegalAccessException {
1473         final ViewGroup.LayoutParams p = view.getLayoutParams();
1474         final Field f = p.getClass().getField(param);
1475         if (f.getType() != int.class) {
1476             throw new RuntimeException("Only integer layout parameters can be set. Field "
1477                         + param + " is of type " + f.getType().getSimpleName());
1478         }
1479 
1480         f.set(p, Integer.valueOf(value));
1481 
1482         view.post(new Runnable() {
1483             @Override
1484             public void run() {
1485                 view.setLayoutParams(p);
1486             }
1487         });
1488     }
1489 }
1490