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