/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

Various debugging/tracing tools related to View and the view hierarchy.
/** * Various debugging/tracing tools related to {@link View} and the view hierarchy. */
public class ViewDebug {
Deprecated:This flag is now unused
/** * @deprecated This flag is now unused */
@Deprecated public static final boolean TRACE_HIERARCHY = false;
Deprecated:This flag is now unused
/** * @deprecated This flag is now unused */
@Deprecated public static final boolean TRACE_RECYCLER = false;
Enables detailed logging of drag/drop operations.
@hide
/** * Enables detailed logging of drag/drop operations. * @hide */
public static final boolean DEBUG_DRAG = false;
Enables detailed logging of task positioning operations.
@hide
/** * Enables detailed logging of task positioning operations. * @hide */
public static final boolean DEBUG_POSITIONING = false;
This annotation can be used to mark fields and methods to be dumped by the view server. Only non-void methods with no arguments can be annotated by this annotation.
/** * This annotation can be used to mark fields and methods to be dumped by * the view server. Only non-void methods with no arguments can be annotated * by this annotation. */
@Target({ ElementType.FIELD, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface ExportedProperty {
When resolveId is true, and if the annotated field/method return value is an int, the value is converted to an Android's resource name.
Returns:true if the property's value must be transformed into an Android resource name, false otherwise
/** * When resolveId is true, and if the annotated field/method return value * is an int, the value is converted to an Android's resource name. * * @return true if the property's value must be transformed into an Android * resource name, false otherwise */
boolean resolveId() default false;
A mapping can be defined to map int values to specific strings. For instance, View.getVisibility() returns 0, 4 or 8. However, these values actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see these human readable values:
@ViewDebug.ExportedProperty(mapping = { @ViewDebug.IntToString(from = 0, to = "VISIBLE"), @ViewDebug.IntToString(from = 4, to = "INVISIBLE"), @ViewDebug.IntToString(from = 8, to = "GONE") }) public int getVisibility() { ... 
See Also:
Returns:An array of int to String mappings
/** * A mapping can be defined to map int values to specific strings. For * instance, View.getVisibility() returns 0, 4 or 8. However, these values * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see * these human readable values: * * <pre> * {@literal @}ViewDebug.ExportedProperty(mapping = { * {@literal @}ViewDebug.IntToString(from = 0, to = "VISIBLE"), * {@literal @}ViewDebug.IntToString(from = 4, to = "INVISIBLE"), * {@literal @}ViewDebug.IntToString(from = 8, to = "GONE") * }) * public int getVisibility() { ... * <pre> * * @return An array of int to String mappings * * @see android.view.ViewDebug.IntToString */
IntToString[] mapping() default { };
A mapping can be defined to map array indices to specific strings. A mapping can be used to see human readable values for the indices of an array:
@ViewDebug.ExportedProperty(indexMapping = { @ViewDebug.IntToString(from = 0, to = "INVALID"), @ViewDebug.IntToString(from = 1, to = "FIRST"), @ViewDebug.IntToString(from = 2, to = "SECOND") }) private int[] mElements; 
See Also:
Returns:An array of int to String mappings
/** * A mapping can be defined to map array indices to specific strings. * A mapping can be used to see human readable values for the indices * of an array: * * <pre> * {@literal @}ViewDebug.ExportedProperty(indexMapping = { * {@literal @}ViewDebug.IntToString(from = 0, to = "INVALID"), * {@literal @}ViewDebug.IntToString(from = 1, to = "FIRST"), * {@literal @}ViewDebug.IntToString(from = 2, to = "SECOND") * }) * private int[] mElements; * <pre> * * @return An array of int to String mappings * * @see android.view.ViewDebug.IntToString * @see #mapping() */
IntToString[] indexMapping() default { };
A flags mapping can be defined to map flags encoded in an integer to specific strings. A mapping can be used to see human readable values for the flags of an integer:
@ViewDebug.ExportedProperty(flagMapping = { @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"), @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"), }) private int mFlags; 
A specified String is output when the following is true:
Returns:An array of int to String mappings
/** * A flags mapping can be defined to map flags encoded in an integer to * specific strings. A mapping can be used to see human readable values * for the flags of an integer: * * <pre> * {@literal @}ViewDebug.ExportedProperty(flagMapping = { * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, * name = "ENABLED"), * {@literal @}ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, * name = "DISABLED"), * }) * private int mFlags; * <pre> * * A specified String is output when the following is true: * * @return An array of int to String mappings */
FlagToString[] flagMapping() default { };
When deep export is turned on, this property is not dumped. Instead, the properties contained in this property are dumped. Each child property is prefixed with the name of this property.
See Also:
Returns:true if the properties of this property should be dumped
/** * When deep export is turned on, this property is not dumped. Instead, the * properties contained in this property are dumped. Each child property * is prefixed with the name of this property. * * @return true if the properties of this property should be dumped * * @see #prefix() */
boolean deepExport() default false;
The prefix to use on child properties when deep export is enabled
See Also:
Returns:a prefix as a String
/** * The prefix to use on child properties when deep export is enabled * * @return a prefix as a String * * @see #deepExport() */
String prefix() default "";
Specifies the category the property falls into, such as measurement, layout, drawing, etc.
Returns:the category as String
/** * Specifies the category the property falls into, such as measurement, * layout, drawing, etc. * * @return the category as String */
String category() default "";
Indicates whether or not to format an int or byte value as a hex string.
Returns:true if the supported values should be formatted as a hex string.
/** * Indicates whether or not to format an {@code int} or {@code byte} value as a hex string. * * @return true if the supported values should be formatted as a hex string. */
boolean formatToHexString() default false;
Indicates whether or not the key to value mappings are held in adjacent indices. Note: Applies only to fields and methods that return String[].
Returns:true if the key to value mappings are held in adjacent indices.
/** * Indicates whether or not the key to value mappings are held in adjacent indices. * * Note: Applies only to fields and methods that return String[]. * * @return true if the key to value mappings are held in adjacent indices. */
boolean hasAdjacentMapping() default false; }
Defines a mapping from an int value to a String. Such a mapping can be used in an @ExportedProperty to provide more meaningful values to the end user.
See Also:
/** * Defines a mapping from an int value to a String. Such a mapping can be used * in an @ExportedProperty to provide more meaningful values to the end user. * * @see android.view.ViewDebug.ExportedProperty */
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface IntToString {
The original int value to map to a String.
Returns:An arbitrary int value.
/** * The original int value to map to a String. * * @return An arbitrary int value. */
int from();
The String to use in place of the original int value.
Returns:An arbitrary non-null String.
/** * The String to use in place of the original int value. * * @return An arbitrary non-null String. */
String to(); }
Defines a mapping from a flag to a String. Such a mapping can be used in an @ExportedProperty to provide more meaningful values to the end user.
See Also:
/** * Defines a mapping from a flag to a String. Such a mapping can be used * in an @ExportedProperty to provide more meaningful values to the end user. * * @see android.view.ViewDebug.ExportedProperty */
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface FlagToString {
The mask to apply to the original value.
Returns:An arbitrary int value.
/** * The mask to apply to the original value. * * @return An arbitrary int value. */
int mask();
The value to compare to the result of: original value & mask().
Returns:An arbitrary value.
/** * The value to compare to the result of: * <code>original value &amp; {@link #mask()}</code>. * * @return An arbitrary value. */
int equals();
The String to use in place of the original int value.
Returns:An arbitrary non-null String.
/** * The String to use in place of the original int value. * * @return An arbitrary non-null String. */
String name();
Indicates whether to output the flag when the test is true, or false. Defaults to true.
/** * Indicates whether to output the flag when the test is true, * or false. Defaults to true. */
boolean outputIf() default true; }
This annotation can be used to mark fields and methods to be dumped when the view is captured. Methods with this annotation must have no arguments and must return a valid type of data.
/** * This annotation can be used to mark fields and methods to be dumped when * the view is captured. Methods with this annotation must have no arguments * and must return a valid type of data. */
@Target({ ElementType.FIELD, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface CapturedViewProperty {
When retrieveReturn is true, we need to retrieve second level methods e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod() we will set retrieveReturn = true on the annotation of myView.getFirstLevelMethod()
Returns:true if we need the second level methods
/** * When retrieveReturn is true, we need to retrieve second level methods * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod() * we will set retrieveReturn = true on the annotation of * myView.getFirstLevelMethod() * @return true if we need the second level methods */
boolean retrieveReturn() default false; }
Allows a View to inject custom children into HierarchyViewer. For example, WebView uses this to add its internal layer tree as a child to itself
@hide
/** * Allows a View to inject custom children into HierarchyViewer. For example, * WebView uses this to add its internal layer tree as a child to itself * @hide */
public interface HierarchyHandler {
Dumps custom children to hierarchy viewer. See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int) for the format An empty implementation should simply do nothing
Params:
  • out – The output writer
  • level – The indentation level
/** * Dumps custom children to hierarchy viewer. * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int) * for the format * * An empty implementation should simply do nothing * * @param out The output writer * @param level The indentation level */
public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
Returns a View to enable grabbing screenshots from custom children returned in dumpViewHierarchyWithProperties.
Params:
  • className – The className of the view to find
  • hashCode – The hashCode of the view to find
Returns:the View to capture from, or null if not found
/** * Returns a View to enable grabbing screenshots from custom children * returned in dumpViewHierarchyWithProperties. * * @param className The className of the view to find * @param hashCode The hashCode of the view to find * @return the View to capture from, or null if not found */
public View findHierarchyView(String className, int hashCode); } private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null; private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null; // Maximum delay in ms after which we stop trying to capture a View's drawing private static final int CAPTURE_TIMEOUT = 4000; private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE"; private static final String REMOTE_COMMAND_DUMP = "DUMP"; private static final String REMOTE_COMMAND_DUMP_THEME = "DUMP_THEME"; private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE"; private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT"; private static final String REMOTE_PROFILE = "PROFILE"; private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS"; private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST"; private static HashMap<Class<?>, Field[]> sFieldsForClasses; private static HashMap<Class<?>, Method[]> sMethodsForClasses; private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
Deprecated:This enum is now unused
/** * @deprecated This enum is now unused */
@Deprecated public enum HierarchyTraceType { INVALIDATE, INVALIDATE_CHILD, INVALIDATE_CHILD_IN_PARENT, REQUEST_LAYOUT, ON_LAYOUT, ON_MEASURE, DRAW, BUILD_CACHE }
Deprecated:This enum is now unused
/** * @deprecated This enum is now unused */
@Deprecated public enum RecyclerTraceType { NEW_VIEW, BIND_VIEW, RECYCLE_FROM_ACTIVE_HEAP, RECYCLE_FROM_SCRAP_HEAP, MOVE_TO_SCRAP_HEAP, MOVE_FROM_ACTIVE_TO_SCRAP_HEAP }
Returns the number of instanciated Views.
Returns:The number of Views instanciated in the current process.
@hide
/** * Returns the number of instanciated Views. * * @return The number of Views instanciated in the current process. * * @hide */
public static long getViewInstanceCount() { return Debug.countInstancesOfClass(View.class); }
Returns the number of instanciated ViewAncestors.
Returns:The number of ViewAncestors instanciated in the current process.
@hide
/** * Returns the number of instanciated ViewAncestors. * * @return The number of ViewAncestors instanciated in the current process. * * @hide */
public static long getViewRootImplCount() { return Debug.countInstancesOfClass(ViewRootImpl.class); }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated @SuppressWarnings({ "UnusedParameters", "deprecation" }) public static void trace(View view, RecyclerTraceType type, int... parameters) { }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated @SuppressWarnings("UnusedParameters") public static void startRecyclerTracing(String prefix, View view) { }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated @SuppressWarnings("UnusedParameters") public static void stopRecyclerTracing() { }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated @SuppressWarnings({ "UnusedParameters", "deprecation" }) public static void trace(View view, HierarchyTraceType type) { }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated @SuppressWarnings("UnusedParameters") public static void startHierarchyTracing(String prefix, View view) { }
Deprecated:This method is now unused and invoking it is a no-op
/** * @deprecated This method is now unused and invoking it is a no-op */
@Deprecated public static void stopHierarchyTracing() { } static void dispatchCommand(View view, String command, String parameters, OutputStream clientStream) throws IOException { // Paranoid but safe... view = view.getRootView(); if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) { dump(view, false, true, clientStream); } else if (REMOTE_COMMAND_DUMP_THEME.equalsIgnoreCase(command)) { dumpTheme(view, clientStream); } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) { captureLayers(view, new DataOutputStream(clientStream)); } else { final String[] params = parameters.split(" "); if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) { capture(view, clientStream, params[0]); } else if (REMOTE_COMMAND_OUTPUT_DISPLAYLIST.equalsIgnoreCase(command)) { outputDisplayList(view, 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]); } } }
@hide
/** @hide */
public static View findView(View root, String parameter) { // Look by type/hashcode if (parameter.indexOf('@') != -1) { final String[] ids = parameter.split("@"); final String className = ids[0]; final int hashCode = (int) Long.parseLong(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 void invalidate(View root, String parameter) { final View view = findView(root, parameter); if (view != null) { view.postInvalidate(); } } private static void requestLayout(View root, String parameter) { final View view = findView(root, parameter); if (view != null) { root.post(new Runnable() { public void run() { view.requestLayout(); } }); } } private static void profile(View root, OutputStream clientStream, String parameter) throws IOException { final View view = findView(root, parameter); BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024); if (view != null) { profileViewAndChildren(view, out); } else { out.write("-1 -1 -1"); out.newLine(); } out.write("DONE."); out.newLine(); } catch (Exception e) { android.util.Log.w("View", "Problem profiling the view:", e); } finally { if (out != null) { out.close(); } } }
@hide
/** @hide */
public static void profileViewAndChildren(final View view, BufferedWriter out) throws IOException { RenderNode node = RenderNode.create("ViewDebug", null); profileViewAndChildren(view, node, out, true); node.destroy(); } private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out, boolean root) throws IOException { long durationMeasure = (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0) ? profileViewMeasure(view) : 0; long durationLayout = (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0) ? profileViewLayout(view) : 0; long durationDraw = (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0) ? profileViewDraw(view, node) : 0; out.write(String.valueOf(durationMeasure)); out.write(' '); out.write(String.valueOf(durationLayout)); out.write(' '); out.write(String.valueOf(durationDraw)); out.newLine(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); for (int i = 0; i < count; i++) { profileViewAndChildren(group.getChildAt(i), node, out, false); } } } private static long profileViewMeasure(final View view) { return profileViewOperation(view, new ViewOperation() { @Override public void pre() { forceLayout(view); } 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)); } } } @Override public void run() { view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); } }); } private static long profileViewLayout(View view) { return profileViewOperation(view, () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom)); } private static long profileViewDraw(View view, RenderNode node) { DisplayMetrics dm = view.getResources().getDisplayMetrics(); if (dm == null) { return 0; } if (view.isHardwareAccelerated()) { DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels); try { return profileViewOperation(view, () -> view.draw(canvas)); } finally { node.end(canvas); } } else { Bitmap bitmap = Bitmap.createBitmap( dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); try { return profileViewOperation(view, () -> view.draw(canvas)); } finally { canvas.setBitmap(null); bitmap.recycle(); } } } interface ViewOperation { default void pre() {} void run(); } private static long profileViewOperation(View view, final ViewOperation operation) { final CountDownLatch latch = new CountDownLatch(1); final long[] duration = new long[1]; view.post(() -> { try { operation.pre(); long start = Debug.threadCpuTimeNanos(); //noinspection unchecked operation.run(); duration[0] = Debug.threadCpuTimeNanos() - start; } finally { latch.countDown(); } }); try { if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) { Log.w("View", "Could not complete the profiling of the view " + view); return -1; } } catch (InterruptedException e) { Log.w("View", "Could not complete the profiling of the view " + view); Thread.currentThread().interrupt(); return -1; } return duration[0]; }
@hide
/** @hide */
public static void captureLayers(View root, final DataOutputStream clientStream) throws IOException { try { Rect outRect = new Rect(); try { root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect); } catch (RemoteException e) { // Ignore } clientStream.writeInt(outRect.width()); clientStream.writeInt(outRect.height()); captureViewLayer(root, clientStream, true); clientStream.write(2); } finally { clientStream.close(); } } private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible) throws IOException { final boolean localVisible = view.getVisibility() == View.VISIBLE && visible; if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) { final int id = view.getId(); String name = view.getClass().getSimpleName(); if (id != View.NO_ID) { name = resolveId(view.getContext(), id).toString(); } clientStream.write(1); clientStream.writeUTF(name); clientStream.writeByte(localVisible ? 1 : 0); int[] position = new int[2]; // XXX: Should happen on the UI thread view.getLocationInWindow(position); clientStream.writeInt(position[0]); clientStream.writeInt(position[1]); clientStream.flush(); Bitmap b = performViewCapture(view, true); if (b != null) { ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() * b.getHeight() * 2); b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut); clientStream.writeInt(arrayOut.size()); arrayOut.writeTo(clientStream); } clientStream.flush(); } if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; int count = group.getChildCount(); for (int i = 0; i < count; i++) { captureViewLayer(group.getChildAt(i), clientStream, localVisible); } } if (view.mOverlay != null) { ViewGroup overlayContainer = view.getOverlay().mOverlayViewGroup; captureViewLayer(overlayContainer, clientStream, localVisible); } } private static void outputDisplayList(View root, String parameter) throws IOException { final View view = findView(root, parameter); view.getViewRootImpl().outputDisplayList(view); }
@hide
/** @hide */
public static void outputDisplayList(View root, View target) { root.getViewRootImpl().outputDisplayList(target); } private static void capture(View root, final OutputStream clientStream, String parameter) throws IOException { final View captureView = findView(root, parameter); capture(root, clientStream, captureView); }
@hide
/** @hide */
public static void capture(View root, final OutputStream clientStream, View captureView) throws IOException { Bitmap b = performViewCapture(captureView, false); if (b == null) { Log.w("View", "Failed to create capture bitmap!"); // Send an empty one so that it doesn't get stuck waiting for // something. b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(), 1, 1, Bitmap.Config.ARGB_8888); } BufferedOutputStream out = null; try { out = new BufferedOutputStream(clientStream, 32 * 1024); b.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); } finally { if (out != null) { out.close(); } b.recycle(); } } private static Bitmap performViewCapture(final View captureView, final boolean skipChildren) { if (captureView != null) { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; captureView.post(() -> { try { CanvasProvider provider = captureView.isHardwareAccelerated() ? new HardwareCanvasProvider() : new SoftwareCanvasProvider(); cache[0] = captureView.createSnapshot(provider, skipChildren); } catch (OutOfMemoryError e) { Log.w("View", "Out of memory for bitmap"); } finally { latch.countDown(); } }); try { latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS); return cache[0]; } catch (InterruptedException e) { Log.w("View", "Could not complete the capture of the view " + captureView); Thread.currentThread().interrupt(); } } return null; }
Dumps the view hierarchy starting from the given view.
Deprecated:See dumpv2(View, ByteArrayOutputStream) below.
@hide
/** * Dumps the view hierarchy starting from the given view. * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below. * @hide */
@Deprecated public static void dump(View root, boolean skipChildren, boolean includeProperties, OutputStream clientStream) throws IOException { BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024); View view = root.getRootView(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; dumpViewHierarchy(group.getContext(), group, out, 0, skipChildren, includeProperties); } out.write("DONE."); out.newLine(); } catch (Exception e) { android.util.Log.w("View", "Problem dumping the view:", e); } finally { if (out != null) { out.close(); } } }
Dumps the view hierarchy starting from the given view. Rather than using reflection, it uses View's encode method to obtain all the properties.
@hide
/** * Dumps the view hierarchy starting from the given view. * Rather than using reflection, it uses View's encode method to obtain all the properties. * @hide */
public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out) throws InterruptedException { final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out); final CountDownLatch latch = new CountDownLatch(1); view.post(new Runnable() { @Override public void run() { encoder.addProperty("window:left", view.mAttachInfo.mWindowLeft); encoder.addProperty("window:top", view.mAttachInfo.mWindowTop); view.encode(encoder); latch.countDown(); } }); latch.await(2, TimeUnit.SECONDS); encoder.endStream(); }
Dumps the theme attributes from the given View.
@hide
/** * Dumps the theme attributes from the given View. * @hide */
public static void dumpTheme(View view, OutputStream clientStream) throws IOException { BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024); String[] attributes = getStyleAttributesDump(view.getContext().getResources(), view.getContext().getTheme()); if (attributes != null) { for (int i = 0; i < attributes.length; i += 2) { if (attributes[i] != null) { out.write(attributes[i] + "\n"); out.write(attributes[i + 1] + "\n"); } } } out.write("DONE."); out.newLine(); } catch (Exception e) { android.util.Log.w("View", "Problem dumping View Theme:", e); } finally { if (out != null) { out.close(); } } }
Gets the style attributes from the Theme. For debugging only.
Params:
  • resources – Resources to resolve attributes from.
  • theme – Theme to dump.
Returns:a String array containing pairs of adjacent Theme attribute data: name followed by its value.
@hide
/** * Gets the style attributes from the {@link Resources.Theme}. For debugging only. * * @param resources Resources to resolve attributes from. * @param theme Theme to dump. * @return a String array containing pairs of adjacent Theme attribute data: name followed by * its value. * * @hide */
private static String[] getStyleAttributesDump(Resources resources, Resources.Theme theme) { TypedValue outValue = new TypedValue(); String nullString = "null"; int i = 0; int[] attributes = theme.getAllAttributes(); String[] data = new String[attributes.length * 2]; for (int attributeId : attributes) { try { data[i] = resources.getResourceName(attributeId); data[i + 1] = theme.resolveAttribute(attributeId, outValue, true) ? outValue.coerceToString().toString() : nullString; i += 2; // attempt to replace reference data with its name if (outValue.type == TypedValue.TYPE_REFERENCE) { data[i - 1] = resources.getResourceName(outValue.resourceId); } } catch (Resources.NotFoundException e) { // ignore resources we can't resolve } } return data; } private static View findView(ViewGroup group, 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; } if (view.mOverlay != null) { final View found = findView((ViewGroup) view.mOverlay.mOverlayViewGroup, className, hashCode); if (found != null) { return found; } } if (view instanceof HierarchyHandler) { final View found = ((HierarchyHandler)view) .findHierarchyView(className, hashCode); if (found != null) { return found; } } } return null; } private static boolean isRequestedView(View view, String className, int hashCode) { if (view.hashCode() == hashCode) { String viewClassName = view.getClass().getName(); if (className.equals("ViewOverlay")) { return viewClassName.equals("android.view.ViewOverlay$OverlayViewGroup"); } else { return className.equals(viewClassName); } } return false; } private static void dumpViewHierarchy(Context context, ViewGroup group, BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) { if (!dumpView(context, group, out, level, includeProperties)) { return; } if (skipChildren) { return; } final int count = group.getChildCount(); for (int i = 0; i < count; i++) { final View view = group.getChildAt(i); if (view instanceof ViewGroup) { dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren, includeProperties); } else { dumpView(context, view, out, level + 1, includeProperties); } if (view.mOverlay != null) { ViewOverlay overlay = view.getOverlay(); ViewGroup overlayContainer = overlay.mOverlayViewGroup; dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren, includeProperties); } } if (group instanceof HierarchyHandler) { ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1); } } private static boolean dumpView(Context context, View view, BufferedWriter out, int level, boolean includeProperties) { try { for (int i = 0; i < level; i++) { out.write(' '); } String className = view.getClass().getName(); if (className.equals("android.view.ViewOverlay$OverlayViewGroup")) { className = "ViewOverlay"; } out.write(className); out.write('@'); out.write(Integer.toHexString(view.hashCode())); out.write(' '); if (includeProperties) { dumpViewProperties(context, view, out); } out.newLine(); } catch (IOException e) { Log.w("View", "Error while dumping hierarchy tree"); return false; } return true; } private static Field[] getExportedPropertyFields(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; Field[] fields = map.get(klass); if (fields != null) { return fields; } try { final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false); final ArrayList<Field> foundFields = new ArrayList<Field>(); for (final Field field : declaredFields) { // Fields which can't be resolved have a null type. if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) { field.setAccessible(true); foundFields.add(field); sAnnotations.put(field, field.getAnnotation(ExportedProperty.class)); } } fields = foundFields.toArray(new Field[foundFields.size()]); map.put(klass, fields); } catch (NoClassDefFoundError e) { throw new AssertionError(e); } return fields; } private static Method[] getExportedPropertyMethods(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; Method[] methods = map.get(klass); if (methods != null) { return methods; } methods = klass.getDeclaredMethodsUnchecked(false); final ArrayList<Method> foundMethods = new ArrayList<Method>(); for (final Method method : methods) { // Ensure the method return and parameter types can be resolved. try { method.getReturnType(); method.getParameterTypes(); } catch (NoClassDefFoundError e) { continue; } if (method.getParameterTypes().length == 0 && method.isAnnotationPresent(ExportedProperty.class) && method.getReturnType() != Void.class) { method.setAccessible(true); foundMethods.add(method); sAnnotations.put(method, method.getAnnotation(ExportedProperty.class)); } } methods = foundMethods.toArray(new Method[foundMethods.size()]); map.put(klass, methods); return methods; } private static void dumpViewProperties(Context context, Object view, BufferedWriter out) throws IOException { dumpViewProperties(context, view, out, ""); } private static void dumpViewProperties(Context context, Object view, BufferedWriter out, String prefix) throws IOException { if (view == null) { out.write(prefix + "=4,null "); return; } 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 Object callMethodOnAppropriateTheadBlocking(final Method method, final Object object) throws IllegalAccessException, InvocationTargetException, TimeoutException { if (!(object instanceof View)) { return method.invoke(object, (Object[]) null); } final View view = (View) object; Callable<Object> callable = new Callable<Object>() { @Override public Object call() throws IllegalAccessException, InvocationTargetException { return method.invoke(view, (Object[]) null); } }; FutureTask<Object> future = new FutureTask<Object>(callable); // Try to use the handler provided by the view Handler handler = view.getHandler(); // Fall back on using the main thread if (handler == null) { handler = new Handler(android.os.Looper.getMainLooper()); } handler.post(future); while (true) { try { return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS); } catch (ExecutionException e) { Throwable t = e.getCause(); if (t instanceof IllegalAccessException) { throw (IllegalAccessException)t; } if (t instanceof InvocationTargetException) { throw (InvocationTargetException)t; } throw new RuntimeException("Unexpected exception", t); } catch (InterruptedException e) { // Call get again } catch (CancellationException e) { throw new RuntimeException("Unexpected cancellation exception", e); } } } private static String formatIntToHexString(int value) { return "0x" + Integer.toHexString(value).toUpperCase(); } private static void exportMethods(Context context, Object view, BufferedWriter out, Class<?> klass, String prefix) throws IOException { final Method[] methods = getExportedPropertyMethods(klass); int count = methods.length; for (int i = 0; i < count; i++) { final Method method = methods[i]; //noinspection EmptyCatchBlock try { Object methodValue = callMethodOnAppropriateTheadBlocking(method, view); final Class<?> returnType = method.getReturnType(); final ExportedProperty property = sAnnotations.get(method); String categoryPrefix = property.category().length() != 0 ? property.category() + ":" : ""; if (returnType == int.class) { if (property.resolveId() && context != null) { final int id = (Integer) methodValue; methodValue = resolveId(context, id); } else { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = (Integer) methodValue; final String valuePrefix = categoryPrefix + prefix + method.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } 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 int[] array = (int[]) methodValue; final String valuePrefix = categoryPrefix + prefix + method.getName() + '_'; final String suffix = "()"; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); continue; } else if (returnType == String[].class) { final String[] array = (String[]) methodValue; if (property.hasAdjacentMapping() && array != null) { for (int j = 0; j < array.length; j += 2) { if (array[j] != null) { writeEntry(out, categoryPrefix + prefix, array[j], "()", array[j + 1] == null ? "null" : array[j + 1]); } } } continue; } else if (!returnType.isPrimitive()) { if (property.deepExport()) { dumpViewProperties(context, methodValue, out, prefix + property.prefix()); continue; } } writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue); } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } catch (TimeoutException e) { } } } private static void exportFields(Context context, Object view, BufferedWriter out, Class<?> klass, String prefix) throws IOException { 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(); final ExportedProperty property = sAnnotations.get(field); String categoryPrefix = property.category().length() != 0 ? property.category() + ":" : ""; if (type == int.class || type == byte.class) { if (property.resolveId() && context != null) { final int id = field.getInt(view); fieldValue = resolveId(context, id); } else { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = field.getInt(view); final String valuePrefix = categoryPrefix + prefix + field.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } 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; } } if (property.formatToHexString()) { fieldValue = field.get(view); if (type == int.class) { fieldValue = formatIntToHexString((Integer) fieldValue); } else if (type == byte.class) { fieldValue = "0x" + Byte.toHexString((Byte) fieldValue, true); } } } } else if (type == int[].class) { final int[] array = (int[]) field.get(view); final String valuePrefix = categoryPrefix + prefix + field.getName() + '_'; final String suffix = ""; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); continue; } else if (type == String[].class) { final String[] array = (String[]) field.get(view); if (property.hasAdjacentMapping() && array != null) { for (int j = 0; j < array.length; j += 2) { if (array[j] != null) { writeEntry(out, categoryPrefix + prefix, array[j], "", array[j + 1] == null ? "null" : array[j + 1]); } } } continue; } else if (!type.isPrimitive()) { if (property.deepExport()) { dumpViewProperties(context, field.get(view), out, prefix + property.prefix()); continue; } } if (fieldValue == null) { fieldValue = field.get(view); } writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue); } catch (IllegalAccessException e) { } } } private static void writeEntry(BufferedWriter out, String prefix, String name, String suffix, Object value) throws IOException { out.write(prefix); out.write(name); out.write(suffix); out.write("="); writeValue(out, value); out.write(' '); } private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping, int intValue, String prefix) throws IOException { final int count = mapping.length; for (int j = 0; j < count; j++) { final FlagToString flagMapping = mapping[j]; final boolean ifTrue = flagMapping.outputIf(); final int maskResult = intValue & flagMapping.mask(); final boolean test = maskResult == flagMapping.equals(); if ((test && ifTrue) || (!test && !ifTrue)) { final String name = flagMapping.name(); final String value = formatIntToHexString(maskResult); writeEntry(out, prefix, name, "", value); } } }
Converts an integer from a field that is mapped with IntToString to its string representation.
Params:
  • clazz – The class the field is defined on.
  • field – The field on which the ExportedProperty is defined on.
  • integer – The value to convert.
Returns:The value converted into its string representation.
@hide
/** * Converts an integer from a field that is mapped with {@link IntToString} to its string * representation. * * @param clazz The class the field is defined on. * @param field The field on which the {@link ExportedProperty} is defined on. * @param integer The value to convert. * @return The value converted into its string representation. * @hide */
public static String intToString(Class<?> clazz, String field, int integer) { final IntToString[] mapping = getMapping(clazz, field); if (mapping == null) { return Integer.toString(integer); } final int count = mapping.length; for (int j = 0; j < count; j++) { final IntToString map = mapping[j]; if (map.from() == integer) { return map.to(); } } return Integer.toString(integer); }
Converts a set of flags from a field that is mapped with FlagToString to its string representation.
Params:
  • clazz – The class the field is defined on.
  • field – The field on which the ExportedProperty is defined on.
  • flags – The flags to convert.
Returns:The flags converted into their string representations.
@hide
/** * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string * representation. * * @param clazz The class the field is defined on. * @param field The field on which the {@link ExportedProperty} is defined on. * @param flags The flags to convert. * @return The flags converted into their string representations. * @hide */
public static String flagsToString(Class<?> clazz, String field, int flags) { final FlagToString[] mapping = getFlagMapping(clazz, field); if (mapping == null) { return Integer.toHexString(flags); } final StringBuilder result = new StringBuilder(); final int count = mapping.length; for (int j = 0; j < count; j++) { final FlagToString flagMapping = mapping[j]; final boolean ifTrue = flagMapping.outputIf(); final int maskResult = flags & flagMapping.mask(); final boolean test = maskResult == flagMapping.equals(); if (test && ifTrue) { final String name = flagMapping.name(); result.append(name).append(' '); } } if (result.length() > 0) { result.deleteCharAt(result.length() - 1); } return result.toString(); } private static FlagToString[] getFlagMapping(Class<?> clazz, String field) { try { return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class) .flagMapping(); } catch (NoSuchFieldException e) { return null; } } private static IntToString[] getMapping(Class<?> clazz, String field) { try { return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping(); } catch (NoSuchFieldException e) { return null; } } private static void exportUnrolledArray(Context context, BufferedWriter out, ExportedProperty property, int[] array, String prefix, String suffix) throws IOException { 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 = null; 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; } } } 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) { if (value == null) value = (String) resolveId(context, intValue); } else { value = String.valueOf(intValue); } writeEntry(out, prefix, name, suffix, value); } } static Object resolveId(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/" + formatIntToHexString(id); } } else { fieldValue = "NO_ID"; } return fieldValue; } private static void writeValue(BufferedWriter out, Object value) throws IOException { if (value != null) { String output = "[EXCEPTION]"; try { output = value.toString().replace("\n", "\\n"); } finally { out.write(String.valueOf(output.length())); out.write(","); out.write(output); } } else { out.write("4,null"); } } private static Field[] capturedViewGetPropertyFields(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 Method[] capturedViewGetPropertyMethods(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; } private static String capturedViewExportMethods(Object obj, Class<?> klass, 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 String capturedViewExportFields(Object obj, Class<?> klass, 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(); }
Dump view info for id based instrument test generation (and possibly further data analysis). The results are dumped to the log.
Params:
  • tag – for log
  • view – for dump
/** * 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 */
public static void dumpCapturedView(String tag, Object view) { 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()); }
Invoke a particular method on given view. The given method is always invoked on the UI thread. The caller thread will stall until the method invocation is complete. Returns an object equal to the result of the method invocation, null if the method is declared to return void
Throws:
  • Exception – if the method invocation caused any exception
@hide
/** * Invoke a particular method on given view. * The given method is always invoked on the UI thread. The caller thread will stall until the * method invocation is complete. Returns an object equal to the result of the method * invocation, null if the method is declared to return void * @throws Exception if the method invocation caused any exception * @hide */
public static Object invokeViewMethod(final View view, final Method method, final Object[] args) { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Object> result = new AtomicReference<Object>(); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); view.post(new Runnable() { @Override public void run() { try { result.set(method.invoke(view, args)); } catch (InvocationTargetException e) { exception.set(e.getCause()); } catch (Exception e) { exception.set(e); } latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } if (exception.get() != null) { throw new RuntimeException(exception.get()); } return result.get(); }
@hide
/** * @hide */
public static void setLayoutParameter(final View view, final String param, final int value) throws NoSuchFieldException, IllegalAccessException { final ViewGroup.LayoutParams p = view.getLayoutParams(); final Field f = p.getClass().getField(param); if (f.getType() != int.class) { throw new RuntimeException("Only integer layout parameters can be set. Field " + param + " is of type " + f.getType().getSimpleName()); } f.set(p, Integer.valueOf(value)); view.post(new Runnable() { @Override public void run() { view.setLayoutParams(p); } }); }
@hide
/** * @hide */
public static class SoftwareCanvasProvider implements CanvasProvider { private Canvas mCanvas; private Bitmap mBitmap; private boolean mEnabledHwBitmapsInSwMode; @Override public Canvas getCanvas(View view, int width, int height) { mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(), width, height, Bitmap.Config.ARGB_8888); if (mBitmap == null) { throw new OutOfMemoryError(); } mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi); if (view.mAttachInfo != null) { mCanvas = view.mAttachInfo.mCanvas; } if (mCanvas == null) { mCanvas = new Canvas(); } mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); mCanvas.setBitmap(mBitmap); return mCanvas; } @Override public Bitmap createBitmap() { mCanvas.setBitmap(null); mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); return mBitmap; } }
@hide
/** * @hide */
public static class HardwareCanvasProvider implements CanvasProvider { private Picture mPicture; @Override public Canvas getCanvas(View view, int width, int height) { mPicture = new Picture(); return mPicture.beginRecording(width, height); } @Override public Bitmap createBitmap() { mPicture.endRecording(); return Bitmap.createBitmap(mPicture); } }
@hide
/** * @hide */
public interface CanvasProvider {
Returns a canvas which can be used to draw {@param view}
/** * Returns a canvas which can be used to draw {@param view} */
Canvas getCanvas(View view, int width, int height);
Creates a bitmap from previously returned canvas
Returns:
/** * Creates a bitmap from previously returned canvas * @return */
Bitmap createBitmap(); } }