Home | History | Annotate | Download | only in view
      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          */
     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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    238         String name();
    239 
    240         /**
    241          * Indicates whether to output the flag when the test is true,
    242          * or false. Defaults to true.
    243          */
    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          */
    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          */
    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          */
    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      */
    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      */
    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" })
    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")
    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")
    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" })
    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")
    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
    406     public static void stopHierarchyTracing() {
    407     }
    408 
    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 */
    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 
    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 
    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 
    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 */
    500     public static void profileViewAndChildren(final View view, BufferedWriter out)
    501             throws IOException {
    502         profileViewAndChildren(view, out, true);
    503     }
    504 
    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> {
    596         T[] pre();
    597         void run(T... data);
    598         void post(T... data);
    599     }
    600 
    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 */
    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 
    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         if (view.mOverlay != null) {
    703             ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup;
    704             captureViewLayer(overlayContainer, clientStream, localVisible);
    705         }
    706     }
    707 
    708     private static void outputDisplayList(View root, String parameter) throws IOException {
    709         final View view = findView(root, parameter);
    710         view.getViewRootImpl().outputDisplayList(view);
    711     }
    712 
    713     /** @hide */
    714     public static void outputDisplayList(View root, View target) {
    715         root.getViewRootImpl().outputDisplayList(target);
    716     }
    717 
    718     private static void capture(View root, final OutputStream clientStream, String parameter)
    719             throws IOException {
    720 
    721         final View captureView = findView(root, parameter);
    722         capture(root, clientStream, captureView);
    723     }
    724 
    725     /** @hide */
    726     public static void capture(View root, final OutputStream clientStream, View captureView)
    727             throws IOException {
    728         Bitmap b = performViewCapture(captureView, false);
    729 
    730         if (b == null) {
    731             Log.w("View", "Failed to create capture bitmap!");
    732             // Send an empty one so that it doesn't get stuck waiting for
    733             // something.
    734             b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
    735                     1, 1, Bitmap.Config.ARGB_8888);
    736         }
    737 
    738         BufferedOutputStream out = null;
    739         try {
    740             out = new BufferedOutputStream(clientStream, 32 * 1024);
    741             b.compress(Bitmap.CompressFormat.PNG, 100, out);
    742             out.flush();
    743         } finally {
    744             if (out != null) {
    745                 out.close();
    746             }
    747             b.recycle();
    748         }
    749     }
    750 
    751     private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) {
    752         if (captureView != null) {
    753             final CountDownLatch latch = new CountDownLatch(1);
    754             final Bitmap[] cache = new Bitmap[1];
    755 
    756             captureView.post(new Runnable() {
    757                 public void run() {
    758                     try {
    759                         cache[0] = captureView.createSnapshot(
    760                                 Bitmap.Config.ARGB_8888, 0, skipChildren);
    761                     } catch (OutOfMemoryError e) {
    762                         Log.w("View", "Out of memory for bitmap");
    763                     } finally {
    764                         latch.countDown();
    765                     }
    766                 }
    767             });
    768 
    769             try {
    770                 latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
    771                 return cache[0];
    772             } catch (InterruptedException e) {
    773                 Log.w("View", "Could not complete the capture of the view " + captureView);
    774                 Thread.currentThread().interrupt();
    775             }
    776         }
    777 
    778         return null;
    779     }
    780 
    781     /**
    782      * Dumps the view hierarchy starting from the given view.
    783      * @hide
    784      */
    785     public static void dump(View root, boolean skipChildren, boolean includeProperties,
    786             OutputStream clientStream) throws IOException {
    787         BufferedWriter out = null;
    788         try {
    789             out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
    790             View view = root.getRootView();
    791             if (view instanceof ViewGroup) {
    792                 ViewGroup group = (ViewGroup) view;
    793                 dumpViewHierarchy(group.getContext(), group, out, 0,
    794                         skipChildren, includeProperties);
    795             }
    796             out.write("DONE.");
    797             out.newLine();
    798         } catch (Exception e) {
    799             android.util.Log.w("View", "Problem dumping the view:", e);
    800         } finally {
    801             if (out != null) {
    802                 out.close();
    803             }
    804         }
    805     }
    806 
    807     private static View findView(ViewGroup group, String className, int hashCode) {
    808         if (isRequestedView(group, className, hashCode)) {
    809             return group;
    810         }
    811 
    812         final int count = group.getChildCount();
    813         for (int i = 0; i < count; i++) {
    814             final View view = group.getChildAt(i);
    815             if (view instanceof ViewGroup) {
    816                 final View found = findView((ViewGroup) view, className, hashCode);
    817                 if (found != null) {
    818                     return found;
    819                 }
    820             } else if (isRequestedView(view, className, hashCode)) {
    821                 return view;
    822             }
    823             if (view.mOverlay != null) {
    824                 final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup,
    825                         className, hashCode);
    826                 if (found != null) {
    827                     return found;
    828                 }
    829             }
    830             if (view instanceof HierarchyHandler) {
    831                 final View found = ((HierarchyHandler)view)
    832                         .findHierarchyView(className, hashCode);
    833                 if (found != null) {
    834                     return found;
    835                 }
    836             }
    837         }
    838         return null;
    839     }
    840 
    841     private static boolean isRequestedView(View view, String className, int hashCode) {
    842         if (view.hashCode() == hashCode) {
    843             String viewClassName = view.getClass().getName();
    844             if (className.equals("ViewOverlay")) {
    845                 return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup");
    846             } else {
    847                 return className.equals(viewClassName);
    848             }
    849         }
    850         return false;
    851     }
    852 
    853     private static void dumpViewHierarchy(Context context, ViewGroup group,
    854             BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
    855         if (!dumpView(context, group, out, level, includeProperties)) {
    856             return;
    857         }
    858 
    859         if (skipChildren) {
    860             return;
    861         }
    862 
    863         final int count = group.getChildCount();
    864         for (int i = 0; i < count; i++) {
    865             final View view = group.getChildAt(i);
    866             if (view instanceof ViewGroup) {
    867                 dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
    868                         includeProperties);
    869             } else {
    870                 dumpView(context, view, out, level + 1, includeProperties);
    871             }
    872             if (view.mOverlay != null) {
    873                 ViewOverlay overlay = view.getOverlay();
    874                 ViewGroup overlayContainer = overlay.mOverlayViewGroup;
    875                 dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
    876                         includeProperties);
    877             }
    878         }
    879         if (group instanceof HierarchyHandler) {
    880             ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
    881         }
    882     }
    883 
    884     private static boolean dumpView(Context context, View view,
    885             BufferedWriter out, int level, boolean includeProperties) {
    886 
    887         try {
    888             for (int i = 0; i < level; i++) {
    889                 out.write(' ');
    890             }
    891             String className = view.getClass().getName();
    892             if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) {
    893                 className = "ViewOverlay";
    894             }
    895             out.write(className);
    896             out.write('@');
    897             out.write(Integer.toHexString(view.hashCode()));
    898             out.write(' ');
    899             if (includeProperties) {
    900                 dumpViewProperties(context, view, out);
    901             }
    902             out.newLine();
    903         } catch (IOException e) {
    904             Log.w("View", "Error while dumping hierarchy tree");
    905             return false;
    906         }
    907         return true;
    908     }
    909 
    910     private static Field[] getExportedPropertyFields(Class<?> klass) {
    911         if (sFieldsForClasses == null) {
    912             sFieldsForClasses = new HashMap<Class<?>, Field[]>();
    913         }
    914         if (sAnnotations == null) {
    915             sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
    916         }
    917 
    918         final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
    919 
    920         Field[] fields = map.get(klass);
    921         if (fields != null) {
    922             return fields;
    923         }
    924 
    925         final ArrayList<Field> foundFields = new ArrayList<Field>();
    926         fields = klass.getDeclaredFields();
    927 
    928         int count = fields.length;
    929         for (int i = 0; i < count; i++) {
    930             final Field field = fields[i];
    931             if (field.isAnnotationPresent(ExportedProperty.class)) {
    932                 field.setAccessible(true);
    933                 foundFields.add(field);
    934                 sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
    935             }
    936         }
    937 
    938         fields = foundFields.toArray(new Field[foundFields.size()]);
    939         map.put(klass, fields);
    940 
    941         return fields;
    942     }
    943 
    944     private static Method[] getExportedPropertyMethods(Class<?> klass) {
    945         if (sMethodsForClasses == null) {
    946             sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
    947         }
    948         if (sAnnotations == null) {
    949             sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
    950         }
    951 
    952         final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
    953 
    954         Method[] methods = map.get(klass);
    955         if (methods != null) {
    956             return methods;
    957         }
    958 
    959         final ArrayList<Method> foundMethods = new ArrayList<Method>();
    960         methods = klass.getDeclaredMethods();
    961 
    962         int count = methods.length;
    963         for (int i = 0; i < count; i++) {
    964             final Method method = methods[i];
    965             if (method.getParameterTypes().length == 0 &&
    966                     method.isAnnotationPresent(ExportedProperty.class) &&
    967                     method.getReturnType() != Void.class) {
    968                 method.setAccessible(true);
    969                 foundMethods.add(method);
    970                 sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
    971             }
    972         }
    973 
    974         methods = foundMethods.toArray(new Method[foundMethods.size()]);
    975         map.put(klass, methods);
    976 
    977         return methods;
    978     }
    979 
    980     private static void dumpViewProperties(Context context, Object view,
    981             BufferedWriter out) throws IOException {
    982 
    983         dumpViewProperties(context, view, out, "");
    984     }
    985 
    986     private static void dumpViewProperties(Context context, Object view,
    987             BufferedWriter out, String prefix) throws IOException {
    988 
    989         if (view == null) {
    990             out.write(prefix + "=4,null ");
    991             return;
    992         }
    993 
    994         Class<?> klass = view.getClass();
    995         do {
    996             exportFields(context, view, out, klass, prefix);
    997             exportMethods(context, view, out, klass, prefix);
    998             klass = klass.getSuperclass();
    999         } while (klass != Object.class);
   1000     }
   1001 
   1002     private static Object callMethodOnAppropriateTheadBlocking(final Method method,
   1003             final Object object) throws IllegalAccessException, InvocationTargetException,
   1004             TimeoutException {
   1005         if (!(object instanceof View)) {
   1006             return method.invoke(object, (Object[]) null);
   1007         }
   1008 
   1009         final View view = (View) object;
   1010         Callable<Object> callable = new Callable<Object>() {
   1011                 @Override
   1012                 public Object call() throws IllegalAccessException, InvocationTargetException {
   1013                     return method.invoke(view, (Object[]) null);
   1014                 }
   1015         };
   1016         FutureTask<Object> future = new FutureTask<Object>(callable);
   1017         // Try to use the handler provided by the view
   1018         Handler handler = view.getHandler();
   1019         // Fall back on using the main thread
   1020         if (handler == null) {
   1021             handler = new Handler(android.os.Looper.getMainLooper());
   1022         }
   1023         handler.post(future);
   1024         while (true) {
   1025             try {
   1026                 return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
   1027             } catch (ExecutionException e) {
   1028                 Throwable t = e.getCause();
   1029                 if (t instanceof IllegalAccessException) {
   1030                     throw (IllegalAccessException)t;
   1031                 }
   1032                 if (t instanceof InvocationTargetException) {
   1033                     throw (InvocationTargetException)t;
   1034                 }
   1035                 throw new RuntimeException("Unexpected exception", t);
   1036             } catch (InterruptedException e) {
   1037                 // Call get again
   1038             } catch (CancellationException e) {
   1039                 throw new RuntimeException("Unexpected cancellation exception", e);
   1040             }
   1041         }
   1042     }
   1043 
   1044     private static void exportMethods(Context context, Object view, BufferedWriter out,
   1045             Class<?> klass, String prefix) throws IOException {
   1046 
   1047         final Method[] methods = getExportedPropertyMethods(klass);
   1048 
   1049         int count = methods.length;
   1050         for (int i = 0; i < count; i++) {
   1051             final Method method = methods[i];
   1052             //noinspection EmptyCatchBlock
   1053             try {
   1054                 Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
   1055                 final Class<?> returnType = method.getReturnType();
   1056                 final ExportedProperty property = sAnnotations.get(method);
   1057                 String categoryPrefix =
   1058                         property.category().length() != 0 ? property.category() + ":" : "";
   1059 
   1060                 if (returnType == int.class) {
   1061 
   1062                     if (property.resolveId() && context != null) {
   1063                         final int id = (Integer) methodValue;
   1064                         methodValue = resolveId(context, id);
   1065                     } else {
   1066                         final FlagToString[] flagsMapping = property.flagMapping();
   1067                         if (flagsMapping.length > 0) {
   1068                             final int intValue = (Integer) methodValue;
   1069                             final String valuePrefix =
   1070                                     categoryPrefix + prefix + method.getName() + '_';
   1071                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
   1072                         }
   1073 
   1074                         final IntToString[] mapping = property.mapping();
   1075                         if (mapping.length > 0) {
   1076                             final int intValue = (Integer) methodValue;
   1077                             boolean mapped = false;
   1078                             int mappingCount = mapping.length;
   1079                             for (int j = 0; j < mappingCount; j++) {
   1080                                 final IntToString mapper = mapping[j];
   1081                                 if (mapper.from() == intValue) {
   1082                                     methodValue = mapper.to();
   1083                                     mapped = true;
   1084                                     break;
   1085                                 }
   1086                             }
   1087 
   1088                             if (!mapped) {
   1089                                 methodValue = intValue;
   1090                             }
   1091                         }
   1092                     }
   1093                 } else if (returnType == int[].class) {
   1094                     final int[] array = (int[]) methodValue;
   1095                     final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
   1096                     final String suffix = "()";
   1097 
   1098                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
   1099 
   1100                     // Probably want to return here, same as for fields.
   1101                     return;
   1102                 } else if (!returnType.isPrimitive()) {
   1103                     if (property.deepExport()) {
   1104                         dumpViewProperties(context, methodValue, out, prefix + property.prefix());
   1105                         continue;
   1106                     }
   1107                 }
   1108 
   1109                 writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
   1110             } catch (IllegalAccessException e) {
   1111             } catch (InvocationTargetException e) {
   1112             } catch (TimeoutException e) {
   1113             }
   1114         }
   1115     }
   1116 
   1117     private static void exportFields(Context context, Object view, BufferedWriter out,
   1118             Class<?> klass, String prefix) throws IOException {
   1119 
   1120         final Field[] fields = getExportedPropertyFields(klass);
   1121 
   1122         int count = fields.length;
   1123         for (int i = 0; i < count; i++) {
   1124             final Field field = fields[i];
   1125 
   1126             //noinspection EmptyCatchBlock
   1127             try {
   1128                 Object fieldValue = null;
   1129                 final Class<?> type = field.getType();
   1130                 final ExportedProperty property = sAnnotations.get(field);
   1131                 String categoryPrefix =
   1132                         property.category().length() != 0 ? property.category() + ":" : "";
   1133 
   1134                 if (type == int.class || type == byte.class) {
   1135                     if (property.resolveId() && context != null) {
   1136                         final int id = field.getInt(view);
   1137                         fieldValue = resolveId(context, id);
   1138                     } else {
   1139                         final FlagToString[] flagsMapping = property.flagMapping();
   1140                         if (flagsMapping.length > 0) {
   1141                             final int intValue = field.getInt(view);
   1142                             final String valuePrefix =
   1143                                     categoryPrefix + prefix + field.getName() + '_';
   1144                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
   1145                         }
   1146 
   1147                         final IntToString[] mapping = property.mapping();
   1148                         if (mapping.length > 0) {
   1149                             final int intValue = field.getInt(view);
   1150                             int mappingCount = mapping.length;
   1151                             for (int j = 0; j < mappingCount; j++) {
   1152                                 final IntToString mapped = mapping[j];
   1153                                 if (mapped.from() == intValue) {
   1154                                     fieldValue = mapped.to();
   1155                                     break;
   1156                                 }
   1157                             }
   1158 
   1159                             if (fieldValue == null) {
   1160                                 fieldValue = intValue;
   1161                             }
   1162                         }
   1163                     }
   1164                 } else if (type == int[].class) {
   1165                     final int[] array = (int[]) field.get(view);
   1166                     final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
   1167                     final String suffix = "";
   1168 
   1169                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
   1170 
   1171                     // We exit here!
   1172                     return;
   1173                 } else if (!type.isPrimitive()) {
   1174                     if (property.deepExport()) {
   1175                         dumpViewProperties(context, field.get(view), out, prefix +
   1176                                 property.prefix());
   1177                         continue;
   1178                     }
   1179                 }
   1180 
   1181                 if (fieldValue == null) {
   1182                     fieldValue = field.get(view);
   1183                 }
   1184 
   1185                 writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
   1186             } catch (IllegalAccessException e) {
   1187             }
   1188         }
   1189     }
   1190 
   1191     private static void writeEntry(BufferedWriter out, String prefix, String name,
   1192             String suffix, Object value) throws IOException {
   1193 
   1194         out.write(prefix);
   1195         out.write(name);
   1196         out.write(suffix);
   1197         out.write("=");
   1198         writeValue(out, value);
   1199         out.write(' ');
   1200     }
   1201 
   1202     private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping,
   1203             int intValue, String prefix) throws IOException {
   1204 
   1205         final int count = mapping.length;
   1206         for (int j = 0; j < count; j++) {
   1207             final FlagToString flagMapping = mapping[j];
   1208             final boolean ifTrue = flagMapping.outputIf();
   1209             final int maskResult = intValue & flagMapping.mask();
   1210             final boolean test = maskResult == flagMapping.equals();
   1211             if ((test && ifTrue) || (!test && !ifTrue)) {
   1212                 final String name = flagMapping.name();
   1213                 final String value = "0x" + Integer.toHexString(maskResult);
   1214                 writeEntry(out, prefix, name, "", value);
   1215             }
   1216         }
   1217     }
   1218 
   1219     private static void exportUnrolledArray(Context context, BufferedWriter out,
   1220             ExportedProperty property, int[] array, String prefix, String suffix)
   1221             throws IOException {
   1222 
   1223         final IntToString[] indexMapping = property.indexMapping();
   1224         final boolean hasIndexMapping = indexMapping.length > 0;
   1225 
   1226         final IntToString[] mapping = property.mapping();
   1227         final boolean hasMapping = mapping.length > 0;
   1228 
   1229         final boolean resolveId = property.resolveId() && context != null;
   1230         final int valuesCount = array.length;
   1231 
   1232         for (int j = 0; j < valuesCount; j++) {
   1233             String name;
   1234             String value = null;
   1235 
   1236             final int intValue = array[j];
   1237 
   1238             name = String.valueOf(j);
   1239             if (hasIndexMapping) {
   1240                 int mappingCount = indexMapping.length;
   1241                 for (int k = 0; k < mappingCount; k++) {
   1242                     final IntToString mapped = indexMapping[k];
   1243                     if (mapped.from() == j) {
   1244                         name = mapped.to();
   1245                         break;
   1246                     }
   1247                 }
   1248             }
   1249 
   1250             if (hasMapping) {
   1251                 int mappingCount = mapping.length;
   1252                 for (int k = 0; k < mappingCount; k++) {
   1253                     final IntToString mapped = mapping[k];
   1254                     if (mapped.from() == intValue) {
   1255                         value = mapped.to();
   1256                         break;
   1257                     }
   1258                 }
   1259             }
   1260 
   1261             if (resolveId) {
   1262                 if (value == null) value = (String) resolveId(context, intValue);
   1263             } else {
   1264                 value = String.valueOf(intValue);
   1265             }
   1266 
   1267             writeEntry(out, prefix, name, suffix, value);
   1268         }
   1269     }
   1270 
   1271     static Object resolveId(Context context, int id) {
   1272         Object fieldValue;
   1273         final Resources resources = context.getResources();
   1274         if (id >= 0) {
   1275             try {
   1276                 fieldValue = resources.getResourceTypeName(id) + '/' +
   1277                         resources.getResourceEntryName(id);
   1278             } catch (Resources.NotFoundException e) {
   1279                 fieldValue = "id/0x" + Integer.toHexString(id);
   1280             }
   1281         } else {
   1282             fieldValue = "NO_ID";
   1283         }
   1284         return fieldValue;
   1285     }
   1286 
   1287     private static void writeValue(BufferedWriter out, Object value) throws IOException {
   1288         if (value != null) {
   1289             String output = "[EXCEPTION]";
   1290             try {
   1291                 output = value.toString().replace("\n", "\\n");
   1292             } finally {
   1293                 out.write(String.valueOf(output.length()));
   1294                 out.write(",");
   1295                 out.write(output);
   1296             }
   1297         } else {
   1298             out.write("4,null");
   1299         }
   1300     }
   1301 
   1302     private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
   1303         if (mCapturedViewFieldsForClasses == null) {
   1304             mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
   1305         }
   1306         final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
   1307 
   1308         Field[] fields = map.get(klass);
   1309         if (fields != null) {
   1310             return fields;
   1311         }
   1312 
   1313         final ArrayList<Field> foundFields = new ArrayList<Field>();
   1314         fields = klass.getFields();
   1315 
   1316         int count = fields.length;
   1317         for (int i = 0; i < count; i++) {
   1318             final Field field = fields[i];
   1319             if (field.isAnnotationPresent(CapturedViewProperty.class)) {
   1320                 field.setAccessible(true);
   1321                 foundFields.add(field);
   1322             }
   1323         }
   1324 
   1325         fields = foundFields.toArray(new Field[foundFields.size()]);
   1326         map.put(klass, fields);
   1327 
   1328         return fields;
   1329     }
   1330 
   1331     private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
   1332         if (mCapturedViewMethodsForClasses == null) {
   1333             mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
   1334         }
   1335         final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
   1336 
   1337         Method[] methods = map.get(klass);
   1338         if (methods != null) {
   1339             return methods;
   1340         }
   1341 
   1342         final ArrayList<Method> foundMethods = new ArrayList<Method>();
   1343         methods = klass.getMethods();
   1344 
   1345         int count = methods.length;
   1346         for (int i = 0; i < count; i++) {
   1347             final Method method = methods[i];
   1348             if (method.getParameterTypes().length == 0 &&
   1349                     method.isAnnotationPresent(CapturedViewProperty.class) &&
   1350                     method.getReturnType() != Void.class) {
   1351                 method.setAccessible(true);
   1352                 foundMethods.add(method);
   1353             }
   1354         }
   1355 
   1356         methods = foundMethods.toArray(new Method[foundMethods.size()]);
   1357         map.put(klass, methods);
   1358 
   1359         return methods;
   1360     }
   1361 
   1362     private static String capturedViewExportMethods(Object obj, Class<?> klass,
   1363             String prefix) {
   1364 
   1365         if (obj == null) {
   1366             return "null";
   1367         }
   1368 
   1369         StringBuilder sb = new StringBuilder();
   1370         final Method[] methods = capturedViewGetPropertyMethods(klass);
   1371 
   1372         int count = methods.length;
   1373         for (int i = 0; i < count; i++) {
   1374             final Method method = methods[i];
   1375             try {
   1376                 Object methodValue = method.invoke(obj, (Object[]) null);
   1377                 final Class<?> returnType = method.getReturnType();
   1378 
   1379                 CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
   1380                 if (property.retrieveReturn()) {
   1381                     //we are interested in the second level data only
   1382                     sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
   1383                 } else {
   1384                     sb.append(prefix);
   1385                     sb.append(method.getName());
   1386                     sb.append("()=");
   1387 
   1388                     if (methodValue != null) {
   1389                         final String value = methodValue.toString().replace("\n", "\\n");
   1390                         sb.append(value);
   1391                     } else {
   1392                         sb.append("null");
   1393                     }
   1394                     sb.append("; ");
   1395                 }
   1396               } catch (IllegalAccessException e) {
   1397                   //Exception IllegalAccess, it is OK here
   1398                   //we simply ignore this method
   1399               } catch (InvocationTargetException e) {
   1400                   //Exception InvocationTarget, it is OK here
   1401                   //we simply ignore this method
   1402               }
   1403         }
   1404         return sb.toString();
   1405     }
   1406 
   1407     private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
   1408         if (obj == null) {
   1409             return "null";
   1410         }
   1411 
   1412         StringBuilder sb = new StringBuilder();
   1413         final Field[] fields = capturedViewGetPropertyFields(klass);
   1414 
   1415         int count = fields.length;
   1416         for (int i = 0; i < count; i++) {
   1417             final Field field = fields[i];
   1418             try {
   1419                 Object fieldValue = field.get(obj);
   1420 
   1421                 sb.append(prefix);
   1422                 sb.append(field.getName());
   1423                 sb.append("=");
   1424 
   1425                 if (fieldValue != null) {
   1426                     final String value = fieldValue.toString().replace("\n", "\\n");
   1427                     sb.append(value);
   1428                 } else {
   1429                     sb.append("null");
   1430                 }
   1431                 sb.append(' ');
   1432             } catch (IllegalAccessException e) {
   1433                 //Exception IllegalAccess, it is OK here
   1434                 //we simply ignore this field
   1435             }
   1436         }
   1437         return sb.toString();
   1438     }
   1439 
   1440     /**
   1441      * Dump view info for id based instrument test generation
   1442      * (and possibly further data analysis). The results are dumped
   1443      * to the log.
   1444      * @param tag for log
   1445      * @param view for dump
   1446      */
   1447     public static void dumpCapturedView(String tag, Object view) {
   1448         Class<?> klass = view.getClass();
   1449         StringBuilder sb = new StringBuilder(klass.getName() + ": ");
   1450         sb.append(capturedViewExportFields(view, klass, ""));
   1451         sb.append(capturedViewExportMethods(view, klass, ""));
   1452         Log.d(tag, sb.toString());
   1453     }
   1454 
   1455     /**
   1456      * Invoke a particular method on given view.
   1457      * The given method is always invoked on the UI thread. The caller thread will stall until the
   1458      * method invocation is complete. Returns an object equal to the result of the method
   1459      * invocation, null if the method is declared to return void
   1460      * @throws Exception if the method invocation caused any exception
   1461      * @hide
   1462      */
   1463     public static Object invokeViewMethod(final View view, final Method method,
   1464             final Object[] args) {
   1465         final CountDownLatch latch = new CountDownLatch(1);
   1466         final AtomicReference<Object> result = new AtomicReference<Object>();
   1467         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
   1468 
   1469         view.post(new Runnable() {
   1470             @Override
   1471             public void run() {
   1472                 try {
   1473                     result.set(method.invoke(view, args));
   1474                 } catch (InvocationTargetException e) {
   1475                     exception.set(e.getCause());
   1476                 } catch (Exception e) {
   1477                     exception.set(e);
   1478                 }
   1479 
   1480                 latch.countDown();
   1481             }
   1482         });
   1483 
   1484         try {
   1485             latch.await();
   1486         } catch (InterruptedException e) {
   1487             throw new RuntimeException(e);
   1488         }
   1489 
   1490         if (exception.get() != null) {
   1491             throw new RuntimeException(exception.get());
   1492         }
   1493 
   1494         return result.get();
   1495     }
   1496 
   1497     /**
   1498      * @hide
   1499      */
   1500     public static void setLayoutParameter(final View view, final String param, final int value)
   1501             throws NoSuchFieldException, IllegalAccessException {
   1502         final ViewGroup.LayoutParams p = view.getLayoutParams();
   1503         final Field f = p.getClass().getField(param);
   1504         if (f.getType() != int.class) {
   1505             throw new RuntimeException("Only integer layout parameters can be set. Field "
   1506                         + param + " is of type " + f.getType().getSimpleName());
   1507         }
   1508 
   1509         f.set(p, Integer.valueOf(value));
   1510 
   1511         view.post(new Runnable() {
   1512             @Override
   1513             public void run() {
   1514                 view.setLayoutParams(p);
   1515             }
   1516         });
   1517     }
   1518 }
   1519