Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a6a5c8f6 authored by Siva Velusamy's avatar Siva Velusamy Committed by Android (Google) Code Review
Browse files

Merge "Support invoking view methods from hierarchy viewer"

parents d7368563 f9455faf
Loading
Loading
Loading
Loading
+122 −21
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;

@@ -67,14 +68,14 @@ public class DdmHandleViewDebug extends ChunkHandler {
    /** Obtain the Display List corresponding to the view. */
    private static final int VUOP_DUMP_DISPLAYLIST = 2;

    /** Invalidate View. */
    private static final int VUOP_INVALIDATE_VIEW = 3;
    /** Profile a view. */
    private static final int VUOP_PROFILE_VIEW = 3;

    /** Re-layout given view. */
    private static final int VUOP_LAYOUT_VIEW = 4;
    /** Invoke a method on the view. */
    private static final int VUOP_INVOKE_VIEW_METHOD = 4;

    /** Profile a view. */
    private static final int VUOP_PROFILE_VIEW = 5;
    /** Set layout parameter. */
    private static final int VUOP_SET_LAYOUT_PARAMETER = 5;

    /** Error code indicating operation specified in chunk is invalid. */
    private static final int ERR_INVALID_OP = -1;
@@ -82,6 +83,11 @@ public class DdmHandleViewDebug extends ChunkHandler {
    /** Error code indicating that the parameters are invalid. */
    private static final int ERR_INVALID_PARAM = -2;

    /** Error code indicating an exception while performing operation. */
    private static final int ERR_EXCEPTION = -3;

    private static final String TAG = "DdmViewDebug";

    private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();

    /** singleton, do not instantiate. */
@@ -140,12 +146,12 @@ public class DdmHandleViewDebug extends ChunkHandler {
                    return captureView(rootView, targetView);
                case VUOP_DUMP_DISPLAYLIST:
                    return dumpDisplayLists(rootView, targetView);
                case VUOP_INVALIDATE_VIEW:
                    return invalidateView(rootView, targetView);
                case VUOP_LAYOUT_VIEW:
                    return layoutView(rootView, targetView);
                case VUOP_PROFILE_VIEW:
                    return profileView(rootView, targetView);
                case VUOP_INVOKE_VIEW_METHOD:
                    return invokeViewMethod(rootView, targetView, in);
                case VUOP_SET_LAYOUT_PARAMETER:
                    return setLayoutParameter(rootView, targetView, in);
                default:
                    return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op);
            }
@@ -276,20 +282,115 @@ public class DdmHandleViewDebug extends ChunkHandler {
        return null;
    }

    /** Invalidates provided view. */
    private Chunk invalidateView(final View rootView, final View targetView) {
        targetView.postInvalidate();
    /**
     * Invokes provided method on the view.
     * The method name and its arguments are passed in as inputs via the byte buffer.
     * The buffer contains:<ol>
     *  <li> len(method name) </li>
     *  <li> method name </li>
     *  <li> # of args </li>
     *  <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
     *          The type specifier is a single character as used in JNI:
     *          (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
     *          F - float, D - double). <p>
     *          The type specifier is followed by the actual value of argument.
     *          Booleans are encoded via bytes with 0 indicating false.</li>
     * </ol>
     * Methods that take no arguments need only specify the method name.
     */
    private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
        int l = in.getInt();
        String methodName = getString(in, l);

        Class<?>[] argTypes;
        Object[] args;
        if (!in.hasRemaining()) {
            argTypes = new Class<?>[0];
            args = new Object[0];
        } else {
            int nArgs = in.getInt();

            argTypes = new Class<?>[nArgs];
            args = new Object[nArgs];

            for (int i = 0; i < nArgs; i++) {
                char c = in.getChar();
                switch (c) {
                    case 'Z':
                        argTypes[i] = boolean.class;
                        args[i] = in.get() == 0 ? false : true;
                        break;
                    case 'B':
                        argTypes[i] = byte.class;
                        args[i] = in.get();
                        break;
                    case 'C':
                        argTypes[i] = char.class;
                        args[i] = in.getChar();
                        break;
                    case 'S':
                        argTypes[i] = short.class;
                        args[i] = in.getShort();
                        break;
                    case 'I':
                        argTypes[i] = int.class;
                        args[i] = in.getInt();
                        break;
                    case 'J':
                        argTypes[i] = long.class;
                        args[i] = in.getLong();
                        break;
                    case 'F':
                        argTypes[i] = float.class;
                        args[i] = in.getFloat();
                        break;
                    case 'D':
                        argTypes[i] = double.class;
                        args[i] = in.getDouble();
                        break;
                    default:
                        Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
                        return createFailChunk(ERR_INVALID_PARAM,
                                "Unsupported parameter type (" + c + ") to invoke view method.");
                }
            }
        }

        Method method = null;
        try {
            method = targetView.getClass().getMethod(methodName, argTypes);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "No such method: " + e.getMessage());
            return createFailChunk(ERR_INVALID_PARAM,
                    "No such method: " + e.getMessage());
        }

        try {
            ViewDebug.invokeViewMethod(targetView, method, args);
        } catch (Exception e) {
            Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
            String msg = e.getCause().getMessage();
            if (msg == null) {
                msg = e.getCause().toString();
            }
            return createFailChunk(ERR_EXCEPTION, msg);
        }

        return null;
    }

    /** Lays out provided view. */
    private Chunk layoutView(View rootView, final View targetView) {
        rootView.post(new Runnable() {
            @Override
            public void run() {
                targetView.requestLayout();
    private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
        int l = in.getInt();
        String param = getString(in, l);
        int value = in.getInt();
        try {
            ViewDebug.setLayoutParameter(targetView, param, value);
        } catch (Exception e) {
            Log.e(TAG, "Exception setting layout parameter: " + e);
            return createFailChunk(ERR_EXCEPTION, "Error accessing field "
                        + param + ":" + e.getMessage());
        }
        });

        return null;
    }

+65 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -1374,4 +1375,68 @@ public class ViewDebug {
        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
     */
    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
     */
    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);
            }
        });
    }
}