FileDocCategorySizeDatePackage
ViewDebug.javaAPI DocAndroid 1.5 API49985Wed May 06 22:41:56 BST 2009android.view

ViewDebug

public class ViewDebug extends Object
Various debugging/tracing tools related to {@link View} and the view hierarchy.

Fields Summary
public static final boolean
TRACE_HIERARCHY
Enables or disables view hierarchy tracing. Any invoker of {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first check that this value is set to true as not to affect performance.
public static final boolean
TRACE_RECYCLER
Enables or disables view recycler tracing. Any invoker of {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first check that this value is set to true as not to affect performance.
static final String
SYSTEM_PROPERTY_CAPTURE_VIEW
The system property of dynamic switch for capturing view information when it is set, we dump interested fields and methods for the view on focus
static final String
SYSTEM_PROPERTY_CAPTURE_EVENT
The system property of dynamic switch for capturing event information when it is set, we log key events, touch/motion and trackball events
private static HashMap
mCapturedViewMethodsForClasses
private static HashMap
mCapturedViewFieldsForClasses
private static final int
CAPTURE_TIMEOUT
private static final String
REMOTE_COMMAND_CAPTURE
private static final String
REMOTE_COMMAND_DUMP
private static final String
REMOTE_COMMAND_INVALIDATE
private static final String
REMOTE_COMMAND_REQUEST_LAYOUT
private static final String
REMOTE_PROFILE
private static HashMap
sFieldsForClasses
private static HashMap
sMethodsForClasses
private static HashMap
sAnnotations
private static BufferedWriter
sHierarchyTraces
private static ViewRoot
sHierarhcyRoot
private static String
sHierarchyTracePrefix
private static View
sRecyclerOwnerView
private static List
sRecyclerViews
private static List
sRecyclerTraces
private static String
sRecyclerTracePrefix
Constructors Summary
Methods Summary
private static voidcapture(View root, java.io.OutputStream clientStream, java.lang.String parameter)


        final View captureView = findView(root, parameter);

        if (captureView != null) {
            final CountDownLatch latch = new CountDownLatch(1);
            final Bitmap[] cache = new Bitmap[1];

            root.post(new Runnable() {
                public void run() {
                    try {
                        cache[0] = captureView.createSnapshot(
                                Bitmap.Config.ARGB_8888, 0);
                    } catch (OutOfMemoryError e) {
                        try {
                            cache[0] = captureView.createSnapshot(
                                    Bitmap.Config.ARGB_4444, 0);
                        } catch (OutOfMemoryError e2) {
                            Log.w("View", "Out of memory for bitmap");
                        }
                    } finally {
                        latch.countDown();
                    }
                }
            });

            try {
                latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);

                if (cache[0] != null) {
                    BufferedOutputStream out = null;
                    try {
                        out = new BufferedOutputStream(clientStream, 32 * 1024);
                        cache[0].compress(Bitmap.CompressFormat.PNG, 100, out);
                        out.flush();
                    } finally {
                        if (out != null) {
                            out.close();
                        }
                        cache[0].recycle();
                    }
                } else {
                    Log.w("View", "Failed to create capture bitmap!");
                    clientStream.close();
                }
            } catch (InterruptedException e) {
                Log.w("View", "Could not complete the capture of the view " + captureView);
                Thread.currentThread().interrupt();
            }
        }
    
private static java.lang.StringcapturedViewExportFields(java.lang.Object obj, java.lang.Class klass, java.lang.String prefix)

        
        if (obj == null) {
            return "null";
        }
        
        StringBuilder sb = new StringBuilder();
        final Field[] fields = capturedViewGetPropertyFields(klass);

        int count = fields.length;
        for (int i = 0; i < count; i++) {
            final Field field = fields[i];
            try {
                Object fieldValue = field.get(obj);

                sb.append(prefix);
                sb.append(field.getName());
                sb.append("=");

                if (fieldValue != null) {
                    final String value = fieldValue.toString().replace("\n", "\\n");
                    sb.append(value);
                } else {
                    sb.append("null");
                }
                sb.append(' ");
            } catch (IllegalAccessException e) {
                //Exception IllegalAccess, it is OK here 
                //we simply ignore this field
            }
        }
        return sb.toString();
    
private static java.lang.StringcapturedViewExportMethods(java.lang.Object obj, java.lang.Class klass, java.lang.String prefix)


        if (obj == null) {
            return "null";
        }
        
        StringBuilder sb = new StringBuilder();
        final Method[] methods = capturedViewGetPropertyMethods(klass);

        int count = methods.length;
        for (int i = 0; i < count; i++) {
            final Method method = methods[i];
            try {
                Object methodValue = method.invoke(obj, (Object[]) null);
                final Class<?> returnType = method.getReturnType();
                
                CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
                if (property.retrieveReturn()) {
                    //we are interested in the second level data only
                    sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
                } else {                    
                    sb.append(prefix);
                    sb.append(method.getName());
                    sb.append("()=");
                    
                    if (methodValue != null) {
                        final String value = methodValue.toString().replace("\n", "\\n");                        
                        sb.append(value);                        
                    } else {
                        sb.append("null");
                    }
                    sb.append("; ");
                }
              } catch (IllegalAccessException e) {
                  //Exception IllegalAccess, it is OK here 
                  //we simply ignore this method
              } catch (InvocationTargetException e) {
                  //Exception InvocationTarget, it is OK here 
                  //we simply ignore this method
              }              
        }        
        return sb.toString();
    
private static java.lang.reflect.Field[]capturedViewGetPropertyFields(java.lang.Class klass)

        if (mCapturedViewFieldsForClasses == null) {
            mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
        }
        final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;

        Field[] fields = map.get(klass);
        if (fields != null) {
            return fields;
        }

        final ArrayList<Field> foundFields = new ArrayList<Field>();
        fields = klass.getFields();

        int count = fields.length;
        for (int i = 0; i < count; i++) {
            final Field field = fields[i];
            if (field.isAnnotationPresent(CapturedViewProperty.class)) {
                field.setAccessible(true);
                foundFields.add(field);
            }
        }

        fields = foundFields.toArray(new Field[foundFields.size()]);
        map.put(klass, fields);

        return fields;
    
private static java.lang.reflect.Method[]capturedViewGetPropertyMethods(java.lang.Class klass)

        if (mCapturedViewMethodsForClasses == null) {
            mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
        }
        final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;

        Method[] methods = map.get(klass);
        if (methods != null) {
            return methods;
        }

        final ArrayList<Method> foundMethods = new ArrayList<Method>();
        methods = klass.getMethods();
        
        int count = methods.length;
        for (int i = 0; i < count; i++) {
            final Method method = methods[i];            
            if (method.getParameterTypes().length == 0 &&
                    method.isAnnotationPresent(CapturedViewProperty.class) &&
                    method.getReturnType() != Void.class) {
                method.setAccessible(true);
                foundMethods.add(method);
            }
        }

        methods = foundMethods.toArray(new Method[foundMethods.size()]);
        map.put(klass, methods);

        return methods;
    
static voiddispatchCommand(View view, java.lang.String command, java.lang.String parameters, java.io.OutputStream clientStream)


        // Paranoid but safe...
        view = view.getRootView();

        if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
            dump(view, clientStream);
        } else {
            final String[] params = parameters.split(" ");
            if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
                capture(view, clientStream, params[0]);
            } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) {
                invalidate(view, params[0]);
            } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) {
                requestLayout(view, params[0]);
            } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
                profile(view, clientStream, params[0]);
            }
        }
    
private static voiddump(View root, java.io.OutputStream clientStream)

        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
            View view = root.getRootView();
            if (view instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) view;
                dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
            }
            out.write("DONE.");
            out.newLine();
        } catch (Exception e) {
            android.util.Log.w("View", "Problem dumping the view:", e);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    
public static voiddumpCapturedView(java.lang.String tag, java.lang.Object view)
Dump view info for id based instrument test generation (and possibly further data analysis). The results are dumped to the log.

param
tag for log
param
view for dump

        
        Class<?> klass = view.getClass();
        StringBuilder sb = new StringBuilder(klass.getName() + ": ");
        sb.append(capturedViewExportFields(view, klass, ""));
        sb.append(capturedViewExportMethods(view, klass, ""));        
        Log.d(tag, sb.toString());        
    
private static booleandumpView(java.lang.Object view, java.io.BufferedWriter out, int level)

        try {
            for (int i = 0; i < level; i++) {
                out.write(' ");
            }
            out.write(view.getClass().getName());
            out.write('@");
            out.write(Integer.toHexString(view.hashCode()));
            out.newLine();
        } catch (IOException e) {
            Log.w("View", "Error while dumping hierarchy tree");
            return false;
        }
        return true;
    
private static voiddumpViewHierarchy(ViewGroup group, java.io.BufferedWriter out, int level)

        if (!dumpView(group, out, level)) {
            return;
        }

        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            if (view instanceof ViewGroup) {
                dumpViewHierarchy((ViewGroup) view, out, level + 1);
            } else {
                dumpView(view, out, level + 1);
            }
        }
    
private static voiddumpViewHierarchyWithProperties(android.content.Context context, ViewGroup group, java.io.BufferedWriter out, int level)

        if (!dumpViewWithProperties(context, group, out, level)) {
            return;
        }

        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            if (view instanceof ViewGroup) {
                dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
            } else {
                dumpViewWithProperties(context, view, out, level + 1);
            }
        }
    
private static voiddumpViewProperties(android.content.Context context, java.lang.Object view, java.io.BufferedWriter out)


        dumpViewProperties(context, view, out, "");
    
private static voiddumpViewProperties(android.content.Context context, java.lang.Object view, java.io.BufferedWriter out, java.lang.String prefix)


        Class<?> klass = view.getClass();

        do {
            exportFields(context, view, out, klass, prefix);
            exportMethods(context, view, out, klass, prefix);
            klass = klass.getSuperclass();
        } while (klass != Object.class);
    
private static booleandumpViewWithProperties(android.content.Context context, View view, java.io.BufferedWriter out, int level)


        try {
            for (int i = 0; i < level; i++) {
                out.write(' ");
            }
            out.write(view.getClass().getName());
            out.write('@");
            out.write(Integer.toHexString(view.hashCode()));
            out.write(' ");
            dumpViewProperties(context, view, out);
            out.newLine();
        } catch (IOException e) {
            Log.w("View", "Error while dumping hierarchy tree");
            return false;
        }
        return true;
    
private static voidexportFields(android.content.Context context, java.lang.Object view, java.io.BufferedWriter out, java.lang.Class klass, java.lang.String prefix)


        final Field[] fields = getExportedPropertyFields(klass);

        int count = fields.length;
        for (int i = 0; i < count; i++) {
            final Field field = fields[i];

            //noinspection EmptyCatchBlock
            try {
                Object fieldValue = null;
                final Class<?> type = field.getType();

                if (type == int.class) {
                    final ExportedProperty property = sAnnotations.get(field);
                    if (property.resolveId() && context != null) {
                        final int id = field.getInt(view);
                        fieldValue = resolveId(context, id);
                    } else {
                        final IntToString[] mapping = property.mapping();
                        if (mapping.length > 0) {
                            final int intValue = field.getInt(view);
                            int mappingCount = mapping.length;
                            for (int j = 0; j < mappingCount; j++) {
                                final IntToString mapped = mapping[j];
                                if (mapped.from() == intValue) {
                                    fieldValue = mapped.to();
                                    break;
                                }
                            }

                            if (fieldValue == null) {
                                fieldValue = intValue;
                            }
                        }
                    }
                } else if (type == int[].class) {
                    final ExportedProperty property = sAnnotations.get(field);
                    final int[] array = (int[]) field.get(view);
                    final String valuePrefix = prefix + field.getName() + '_";
                    final String suffix = "";

                    exportUnrolledArray(context, out, property, array, valuePrefix, suffix);

                    // We exit here!
                    return;
                } else if (!type.isPrimitive()) {
                    final ExportedProperty property = sAnnotations.get(field);
                    if (property.deepExport()) {
                        dumpViewProperties(context, field.get(view), out,
                                prefix + property.prefix());
                        continue;
                    }
                }

                if (fieldValue == null) {
                    fieldValue = field.get(view);
                }

                writeEntry(out, prefix, field.getName(), "", fieldValue);
            } catch (IllegalAccessException e) {
            }
        }
    
private static voidexportMethods(android.content.Context context, java.lang.Object view, java.io.BufferedWriter out, java.lang.Class klass, java.lang.String prefix)


        final Method[] methods = getExportedPropertyMethods(klass);

        int count = methods.length;
        for (int i = 0; i < count; i++) {
            final Method method = methods[i];
            //noinspection EmptyCatchBlock
            try {
                // TODO: This should happen on the UI thread
                Object methodValue = method.invoke(view, (Object[]) null);
                final Class<?> returnType = method.getReturnType();

                if (returnType == int.class) {
                    final ExportedProperty property = sAnnotations.get(method);
                    if (property.resolveId() && context != null) {
                        final int id = (Integer) methodValue;
                        methodValue = resolveId(context, id);
                    } else {
                        final IntToString[] mapping = property.mapping();
                        if (mapping.length > 0) {
                            final int intValue = (Integer) methodValue;
                            boolean mapped = false;
                            int mappingCount = mapping.length;
                            for (int j = 0; j < mappingCount; j++) {
                                final IntToString mapper = mapping[j];
                                if (mapper.from() == intValue) {
                                    methodValue = mapper.to();
                                    mapped = true;
                                    break;
                                }
                            }

                            if (!mapped) {
                                methodValue = intValue;
                            }
                        }
                    }
                } else if (returnType == int[].class) {
                    final ExportedProperty property = sAnnotations.get(method);
                    final int[] array = (int[]) methodValue;
                    final String valuePrefix = prefix + method.getName() + '_";
                    final String suffix = "()";

                    exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
                } else if (!returnType.isPrimitive()) {
                    final ExportedProperty property = sAnnotations.get(method);
                    if (property.deepExport()) {
                        dumpViewProperties(context, methodValue, out, prefix + property.prefix());
                        continue;
                    }
                }

                writeEntry(out, prefix, method.getName(), "()", methodValue);
            } catch (IllegalAccessException e) {
            } catch (InvocationTargetException e) {
            }
        }
    
private static voidexportUnrolledArray(android.content.Context context, java.io.BufferedWriter out, android.view.ViewDebug$ExportedProperty property, int[] array, java.lang.String prefix, java.lang.String suffix)


        final IntToString[] indexMapping = property.indexMapping();
        final boolean hasIndexMapping = indexMapping.length > 0;

        final IntToString[] mapping = property.mapping();
        final boolean hasMapping = mapping.length > 0;

        final boolean resolveId = property.resolveId() && context != null;
        final int valuesCount = array.length;

        for (int j = 0; j < valuesCount; j++) {
            String name;
            String value;

            final int intValue = array[j];

            name = String.valueOf(j);
            if (hasIndexMapping) {
                int mappingCount = indexMapping.length;
                for (int k = 0; k < mappingCount; k++) {
                    final IntToString mapped = indexMapping[k];
                    if (mapped.from() == j) {
                        name = mapped.to();
                        break;
                    }
                }
            }

            value = String.valueOf(intValue);
            if (hasMapping) {
                int mappingCount = mapping.length;
                for (int k = 0; k < mappingCount; k++) {
                    final IntToString mapped = mapping[k];
                    if (mapped.from() == intValue) {
                        value = mapped.to();
                        break;
                    }
                }
            }

            if (resolveId) {
                value = (String) resolveId(context, intValue);
            }

            writeEntry(out, prefix, name, suffix, value);
        }
    
private static ViewfindView(View root, java.lang.String parameter)

        // Look by type/hashcode
        if (parameter.indexOf('@") != -1) {
            final String[] ids = parameter.split("@");
            final String className = ids[0];
            final int hashCode = Integer.parseInt(ids[1], 16);

            View view = root.getRootView();
            if (view instanceof ViewGroup) {
                return findView((ViewGroup) view, className, hashCode);
            }
        } else {
            // Look by id
            final int id = root.getResources().getIdentifier(parameter, null, null);
            return root.getRootView().findViewById(id);
        }

        return null;
    
private static ViewfindView(ViewGroup group, java.lang.String className, int hashCode)

        if (isRequestedView(group, className, hashCode)) {
            return group;
        }

        final int count = group.getChildCount();
        for (int i = 0; i < count; i++) {
            final View view = group.getChildAt(i);
            if (view instanceof ViewGroup) {
                final View found = findView((ViewGroup) view, className, hashCode);
                if (found != null) {
                    return found;
                }
            } else if (isRequestedView(view, className, hashCode)) {
                return view;
            }
        }

        return null;
    
private static java.lang.reflect.Field[]getExportedPropertyFields(java.lang.Class klass)

        if (sFieldsForClasses == null) {
            sFieldsForClasses = new HashMap<Class<?>, Field[]>();
        }
        if (sAnnotations == null) {
            sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
        }

        final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
        final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;

        Field[] fields = map.get(klass);
        if (fields != null) {
            return fields;
        }

        final ArrayList<Field> foundFields = new ArrayList<Field>();
        fields = klass.getDeclaredFields();

        int count = fields.length;
        for (int i = 0; i < count; i++) {
            final Field field = fields[i];
            if (field.isAnnotationPresent(ExportedProperty.class)) {
                field.setAccessible(true);
                foundFields.add(field);
                annotations.put(field, field.getAnnotation(ExportedProperty.class));
            }
        }

        fields = foundFields.toArray(new Field[foundFields.size()]);
        map.put(klass, fields);

        return fields;
    
private static java.lang.reflect.Method[]getExportedPropertyMethods(java.lang.Class klass)

        if (sMethodsForClasses == null) {
            sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
        }
        if (sAnnotations == null) {
            sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
        }

        final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
        final HashMap<AccessibleObject, ExportedProperty> annotations = sAnnotations;

        Method[] methods = map.get(klass);
        if (methods != null) {
            return methods;
        }

        final ArrayList<Method> foundMethods = new ArrayList<Method>();
        methods = klass.getDeclaredMethods();
        
        int count = methods.length;
        for (int i = 0; i < count; i++) {
            final Method method = methods[i];            
            if (method.getParameterTypes().length == 0 &&
                    method.isAnnotationPresent(ExportedProperty.class) &&
                    method.getReturnType() != Void.class) {
                method.setAccessible(true);
                foundMethods.add(method);
                annotations.put(method, method.getAnnotation(ExportedProperty.class));
            }
        }

        methods = foundMethods.toArray(new Method[foundMethods.size()]);
        map.put(klass, methods);

        return methods;
    
public static longgetViewInstanceCount()
Returns the number of instanciated Views.

return
The number of Views instanciated in the current process.
hide


                          
        
        return View.sInstanceCount;
    
public static longgetViewRootInstanceCount()
Returns the number of instanciated ViewRoots.

return
The number of ViewRoots instanciated in the current process.
hide

        return ViewRoot.getInstanceCount();
    
private static voidinvalidate(View root, java.lang.String parameter)

        final View view = findView(root, parameter);
        if (view != null) {
            view.postInvalidate();
        }
    
private static booleanisRequestedView(View view, java.lang.String className, int hashCode)

        return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
    
private static voidprofile(View root, java.io.OutputStream clientStream, java.lang.String parameter)


        final View view = findView(root, parameter);
        BufferedWriter out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);

            if (view != null) {
                final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
                    public Void[] pre() {
                        forceLayout(view);
                        return null;
                    }

                    private void forceLayout(View view) {
                        view.forceLayout();
                        if (view instanceof ViewGroup) {
                            ViewGroup group = (ViewGroup) view;
                            final int count = group.getChildCount();
                            for (int i = 0; i < count; i++) {
                                forceLayout(group.getChildAt(i));
                            }
                        }
                    }

                    public void run(Void... data) {
                        view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
                    }

                    public void post(Void... data) {
                    }
                });

                final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
                    public Void[] pre() {
                        return null;
                    }

                    public void run(Void... data) {
                        view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
                    }

                    public void post(Void... data) {
                    }
                });

                final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
                    public Object[] pre() {
                        final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
                        final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
                                metrics.heightPixels, Bitmap.Config.RGB_565);
                        final Canvas canvas = new Canvas(bitmap);
                        return new Object[] { bitmap, canvas };
                    }

                    public void run(Object... data) {
                        view.draw((Canvas) data[1]);
                    }

                    public void post(Object... data) {
                        ((Bitmap) data[0]).recycle();
                    }
                });

                out.write(String.valueOf(durationMeasure));
                out.write(' ");
                out.write(String.valueOf(durationLayout));
                out.write(' ");
                out.write(String.valueOf(durationDraw));
                out.newLine();
            } else {
                out.write("-1 -1 -1");
                out.newLine();
            }
        } catch (Exception e) {
            android.util.Log.w("View", "Problem profiling the view:", e);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    
private static longprofileViewOperation(View view, android.view.ViewDebug$ViewOperation operation)

        final CountDownLatch latch = new CountDownLatch(1);
        final long[] duration = new long[1];

        view.post(new Runnable() {
            public void run() {
                try {
                    T[] data = operation.pre();
                    long start = Debug.threadCpuTimeNanos();
                    operation.run(data);
                    duration[0] = Debug.threadCpuTimeNanos() - start;
                    operation.post(data);
                } finally {
                    latch.countDown();
                }
            }
        });

        try {
            latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Log.w("View", "Could not complete the profiling of the view " + view);
            Thread.currentThread().interrupt();
            return -1;
        }

        return duration[0];
    
private static voidrequestLayout(View root, java.lang.String parameter)

        final View view = findView(root, parameter);
        if (view != null) {
            root.post(new Runnable() {
                public void run() {
                    view.requestLayout();
                }
            });
        }
    
private static java.lang.ObjectresolveId(android.content.Context context, int id)

        Object fieldValue;
        final Resources resources = context.getResources();
        if (id >= 0) {
            try {
                fieldValue = resources.getResourceTypeName(id) + '/" +
                        resources.getResourceEntryName(id);
            } catch (Resources.NotFoundException e) {
                fieldValue = "id/0x" + Integer.toHexString(id);
            }
        } else {
            fieldValue = "NO_ID";
        }
        return fieldValue;
    
public static voidstartHierarchyTracing(java.lang.String prefix, View view)
Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix, used to build the traces files names: /EXTERNAL/view-hierarchy/PREFIX.traces and /EXTERNAL/view-hierarchy/PREFIX.tree. Only one view hierarchy can be traced at the same time. After calling this method, any other invocation will result in a IllegalStateException unless {@link #stopHierarchyTracing()} is invoked before. Calling this method creates the file /EXTERNAL/view-hierarchy/PREFIX.traces containing all the traces (or method calls) relative to the specified view's hierarchy. This method will return immediately if TRACE_HIERARCHY is false.

param
prefix the traces files name prefix
param
view the view whose hierarchy must be traced
see
#stopHierarchyTracing()
see
#trace(View, android.view.ViewDebug.HierarchyTraceType)

        //noinspection PointlessBooleanExpression,ConstantConditions
        if (!TRACE_HIERARCHY) {
            return;
        }

        if (sHierarhcyRoot != null) {
            throw new IllegalStateException("You must call stopHierarchyTracing() before running" +
                " a new trace!");
        }

        File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
        //noinspection ResultOfMethodCallIgnored
        hierarchyDump.mkdirs();

        hierarchyDump = new File(hierarchyDump, prefix + ".traces");
        sHierarchyTracePrefix = prefix;

        try {
            sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
        } catch (IOException e) {
            Log.e("View", "Could not dump view hierarchy");
            return;
        }

        sHierarhcyRoot = (ViewRoot) view.getRootView().getParent();
    
public static voidstartRecyclerTracing(java.lang.String prefix, View view)
Starts tracing the view recycler of the specified view. The trace is identified by a prefix, used to build the traces files names: /EXTERNAL/view-recycler/PREFIX.traces and /EXTERNAL/view-recycler/PREFIX.recycler. Only one view recycler can be traced at the same time. After calling this method, any other invocation will result in a IllegalStateException unless {@link #stopRecyclerTracing()} is invoked before. Traces files are created only after {@link #stopRecyclerTracing()} is invoked. This method will return immediately if TRACE_RECYCLER is false.

param
prefix the traces files name prefix
param
view the view whose recycler must be traced
see
#stopRecyclerTracing()
see
#trace(View, android.view.ViewDebug.RecyclerTraceType, int[])

        //noinspection PointlessBooleanExpression,ConstantConditions
        if (!TRACE_RECYCLER) {
            return;
        }

        if (sRecyclerOwnerView != null) {
            throw new IllegalStateException("You must call stopRecyclerTracing() before running" +
                " a new trace!");
        }

        sRecyclerTracePrefix = prefix;
        sRecyclerOwnerView = view;
        sRecyclerViews = new ArrayList<View>();
        sRecyclerTraces = new LinkedList<RecyclerTrace>();
    
public static voidstopHierarchyTracing()
Stops the current view hierarchy tracing. This method closes the file /EXTERNAL/view-hierarchy/PREFIX.traces. Calling this method creates the file /EXTERNAL/view-hierarchy/PREFIX.tree containing the view hierarchy of the view supplied to {@link #startHierarchyTracing(String, View)}. This method will return immediately if TRACE_HIERARCHY is false.

see
#startHierarchyTracing(String, View)
see
#trace(View, android.view.ViewDebug.HierarchyTraceType)

        //noinspection PointlessBooleanExpression,ConstantConditions
        if (!TRACE_HIERARCHY) {
            return;
        }

        if (sHierarhcyRoot == null || sHierarchyTraces == null) {
            throw new IllegalStateException("You must call startHierarchyTracing() before" +
                " stopHierarchyTracing()!");
        }

        try {
            sHierarchyTraces.close();
        } catch (IOException e) {
            Log.e("View", "Could not write view traces");
        }
        sHierarchyTraces = null;

        File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
        //noinspection ResultOfMethodCallIgnored
        hierarchyDump.mkdirs();
        hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");

        BufferedWriter out;
        try {
            out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024);
        } catch (IOException e) {
            Log.e("View", "Could not dump view hierarchy");
            return;
        }

        View view = sHierarhcyRoot.getView();
        if (view instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) view;
            dumpViewHierarchy(group, out, 0);
            try {
                out.close();
            } catch (IOException e) {
                Log.e("View", "Could not dump view hierarchy");
            }
        }

        sHierarhcyRoot = null;
    
public static voidstopRecyclerTracing()
Stops the current view recycer tracing. Calling this method creates the file /EXTERNAL/view-recycler/PREFIX.traces containing all the traces (or method calls) relative to the specified view's recycler. Calling this method creates the file /EXTERNAL/view-recycler/PREFIX.recycler containing all of the views used by the recycler of the view supplied to {@link #startRecyclerTracing(String, View)}. This method will return immediately if TRACE_RECYCLER is false.

see
#startRecyclerTracing(String, View)
see
#trace(View, android.view.ViewDebug.RecyclerTraceType, int[])

        //noinspection PointlessBooleanExpression,ConstantConditions
        if (!TRACE_RECYCLER) {
            return;
        }

        if (sRecyclerOwnerView == null || sRecyclerViews == null) {
            throw new IllegalStateException("You must call startRecyclerTracing() before" +
                " stopRecyclerTracing()!");
        }

        File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
        //noinspection ResultOfMethodCallIgnored
        recyclerDump.mkdirs();

        recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
        try {
            final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024);

            for (View view : sRecyclerViews) {
                final String name = view.getClass().getName();
                out.write(name);
                out.newLine();
            }

            out.close();
        } catch (IOException e) {
            Log.e("View", "Could not dump recycler content");
            return;
        }

        recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
        recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
        try {
            final FileOutputStream file = new FileOutputStream(recyclerDump);
            final DataOutputStream out = new DataOutputStream(file);

            for (RecyclerTrace trace : sRecyclerTraces) {
                out.writeInt(trace.view);
                out.writeInt(trace.type.ordinal());
                out.writeInt(trace.position);
                out.writeInt(trace.indexOnScreen);
                out.flush();
            }

            out.close();
        } catch (IOException e) {
            Log.e("View", "Could not dump recycler traces");
            return;
        }

        sRecyclerViews.clear();
        sRecyclerViews = null;

        sRecyclerTraces.clear();
        sRecyclerTraces = null;

        sRecyclerOwnerView = null;
    
public static voidtrace(View view, android.view.ViewDebug$RecyclerTraceType type, int parameters)
Outputs a trace to the currently opened recycler traces. The trace records the type of recycler action performed on the supplied view as well as a number of parameters.

param
view the view to trace
param
type the type of the trace
param
parameters parameters depending on the type of the trace

        if (sRecyclerOwnerView == null || sRecyclerViews == null) {
            return;
        }

        if (!sRecyclerViews.contains(view)) {
            sRecyclerViews.add(view);
        }

        final int index = sRecyclerViews.indexOf(view);

        RecyclerTrace trace = new RecyclerTrace();
        trace.view = index;
        trace.type = type;
        trace.position = parameters[0];
        trace.indexOnScreen = parameters[1];

        sRecyclerTraces.add(trace);
    
public static voidtrace(View view, android.view.ViewDebug$HierarchyTraceType type)
Outputs a trace to the currently opened traces file. The trace contains the class name and instance's hashcode of the specified view as well as the supplied trace type.

param
view the view to trace
param
type the type of the trace

        if (sHierarchyTraces == null) {
            return;
        }

        try {
            sHierarchyTraces.write(type.name());
            sHierarchyTraces.write(' ");
            sHierarchyTraces.write(view.getClass().getName());
            sHierarchyTraces.write('@");
            sHierarchyTraces.write(Integer.toHexString(view.hashCode()));
            sHierarchyTraces.newLine();
        } catch (IOException e) {
            Log.w("View", "Error while dumping trace of type " + type + " for view " + view);
        }
    
private static voidwriteEntry(java.io.BufferedWriter out, java.lang.String prefix, java.lang.String name, java.lang.String suffix, java.lang.Object value)


        out.write(prefix);
        out.write(name);
        out.write(suffix);
        out.write("=");
        writeValue(out, value);
        out.write(' ");
    
private static voidwriteValue(java.io.BufferedWriter out, java.lang.Object value)

        if (value != null) {
            String output = value.toString().replace("\n", "\\n");
            out.write(String.valueOf(output.length()));
            out.write(",");
            out.write(output);
        } else {
            out.write("4,null");
        }