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