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