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