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

Commit bc0ed2ce authored by Siva Velusamy's avatar Siva Velusamy Committed by Android Git Automerger
Browse files

am a6a5c8f6: Merge "Support invoking view methods from hierarchy viewer"

# Via Android (Google) Code Review (1) and Siva Velusamy (1)
* commit 'a6a5c8f6':
  Support invoking view methods from hierarchy viewer
parents c83a7954 a6a5c8f6
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);
            }
        });
    }
}