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