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

Commit e697191f authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Adding support for invoking view commands from View server" into main

parents 389473d5 64e7f827
Loading
Loading
Loading
Loading
+5 −213
Original line number Diff line number Diff line
@@ -16,16 +16,12 @@

package android.ddm;

import static com.android.internal.util.Preconditions.checkArgument;

import android.util.Log;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;

import com.android.internal.annotations.VisibleForTesting;

import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
@@ -35,10 +31,8 @@ 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;
import java.nio.charset.StandardCharsets;

/**
 * Handle various requests related to profiling / debugging of the view system.
@@ -352,48 +346,17 @@ public class DdmHandleViewDebug extends DdmHandle {
     *
     * The return value is encoded the same way as a single parameter (type + value)
     */
    private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
    private Chunk invokeViewMethod(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];

            try {
                deserializeMethodParameters(args, argTypes, in);
            } catch (ViewMethodInvocationSerializationException e) {
                return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
            }
        }

        Method method;
        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 {
            Object result = ViewDebug.invokeViewMethod(targetView, method, args);
            Class<?> returnType = method.getReturnType();
            byte[] returnValue = serializeReturnValue(returnType, returnType.cast(result));
            byte[] returnValue =  ViewDebug.invokeViewMethod(targetView, methodName, in);
            return new Chunk(CHUNK_VUOP, returnValue, 0, returnValue.length);
        } catch (ViewDebug.ViewMethodInvocationSerializationException e) {
            return createFailChunk(ERR_INVALID_PARAM, e.getMessage());
        } 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 createFailChunk(ERR_EXCEPTION, e.getMessage());
        }
    }

@@ -431,175 +394,4 @@ public class DdmHandleViewDebug extends DdmHandle {
        byte[] data = b.toByteArray();
        return new Chunk(CHUNK_VUOP, data, 0, data.length);
    }

    /**
     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
     * buffer.
     *
     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
     * be the same length, and will be set to the argument types of the data read.
     *
     * @hide
     */
    @VisibleForTesting
    public static void deserializeMethodParameters(
            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
            ViewMethodInvocationSerializationException {
        checkArgument(args.length == argTypes.length);

        for (int i = 0; i < args.length; i++) {
            char typeSignature = in.getChar();
            boolean isArray = typeSignature == SIG_ARRAY;
            if (isArray) {
                char arrayType = in.getChar();
                if (arrayType != SIG_BYTE) {
                    // This implementation only supports byte-arrays for now.
                    throw new ViewMethodInvocationSerializationException(
                            "Unsupported array parameter type (" + typeSignature
                                    + ") to invoke view method @argument " + i);
                }

                int arrayLength = in.getInt();
                if (arrayLength > in.remaining()) {
                    // The sender did not actually sent the specified amount of bytes. This
                    // avoids a malformed packet to trigger an out-of-memory error.
                    throw new BufferUnderflowException();
                }

                byte[] byteArray = new byte[arrayLength];
                in.get(byteArray);

                argTypes[i] = byte[].class;
                args[i] = byteArray;
            } else {
                switch (typeSignature) {
                    case SIG_BOOLEAN:
                        argTypes[i] = boolean.class;
                        args[i] = in.get() != 0;
                        break;
                    case SIG_BYTE:
                        argTypes[i] = byte.class;
                        args[i] = in.get();
                        break;
                    case SIG_CHAR:
                        argTypes[i] = char.class;
                        args[i] = in.getChar();
                        break;
                    case SIG_SHORT:
                        argTypes[i] = short.class;
                        args[i] = in.getShort();
                        break;
                    case SIG_INT:
                        argTypes[i] = int.class;
                        args[i] = in.getInt();
                        break;
                    case SIG_LONG:
                        argTypes[i] = long.class;
                        args[i] = in.getLong();
                        break;
                    case SIG_FLOAT:
                        argTypes[i] = float.class;
                        args[i] = in.getFloat();
                        break;
                    case SIG_DOUBLE:
                        argTypes[i] = double.class;
                        args[i] = in.getDouble();
                        break;
                    case SIG_STRING: {
                        argTypes[i] = String.class;
                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
                        in.get(rawStringBuffer);
                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
                        break;
                    }
                    default:
                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
                        throw new ViewMethodInvocationSerializationException(
                                "Unsupported parameter type (" + typeSignature
                                        + ") to invoke view method.");
                }
            }

        }
    }

    /**
     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
     * @hide
     */
    @VisibleForTesting
    public static byte[] serializeReturnValue(Class<?> type, Object value)
            throws ViewMethodInvocationSerializationException, IOException {
        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
        DataOutputStream dos = new DataOutputStream(byteOutStream);

        if (type.isArray()) {
            if (!type.equals(byte[].class)) {
                // Only byte arrays are supported currently.
                throw new ViewMethodInvocationSerializationException(
                        "Unsupported array return type (" + type + ")");
            }
            byte[] byteArray = (byte[]) value;
            dos.writeChar(SIG_ARRAY);
            dos.writeChar(SIG_BYTE);
            dos.writeInt(byteArray.length);
            dos.write(byteArray);
        } else if (boolean.class.equals(type)) {
            dos.writeChar(SIG_BOOLEAN);
            dos.write((boolean) value ? 1 : 0);
        } else if (byte.class.equals(type)) {
            dos.writeChar(SIG_BYTE);
            dos.writeByte((byte) value);
        } else if (char.class.equals(type)) {
            dos.writeChar(SIG_CHAR);
            dos.writeChar((char) value);
        } else if (short.class.equals(type)) {
            dos.writeChar(SIG_SHORT);
            dos.writeShort((short) value);
        } else if (int.class.equals(type)) {
            dos.writeChar(SIG_INT);
            dos.writeInt((int) value);
        } else if (long.class.equals(type)) {
            dos.writeChar(SIG_LONG);
            dos.writeLong((long) value);
        } else if (double.class.equals(type)) {
            dos.writeChar(SIG_DOUBLE);
            dos.writeDouble((double) value);
        } else if (float.class.equals(type)) {
            dos.writeChar(SIG_FLOAT);
            dos.writeFloat((float) value);
        } else if (String.class.equals(type)) {
            dos.writeChar(SIG_STRING);
            dos.writeUTF(value != null ? (String) value : "");
        } else {
            dos.writeChar(SIG_VOID);
        }

        return byteOutStream.toByteArray();
    }

    // Prefixes for simple primitives. These match the JNI definitions.
    private static final char SIG_ARRAY = '[';
    private static final char SIG_BOOLEAN = 'Z';
    private static final char SIG_BYTE = 'B';
    private static final char SIG_SHORT = 'S';
    private static final char SIG_CHAR = 'C';
    private static final char SIG_INT = 'I';
    private static final char SIG_LONG = 'J';
    private static final char SIG_FLOAT = 'F';
    private static final char SIG_DOUBLE = 'D';
    private static final char SIG_VOID = 'V';
    // Prefixes for some commonly used objects
    private static final char SIG_STRING = 'R';

    /**
     * @hide
     */
    @VisibleForTesting
    public static class ViewMethodInvocationSerializationException extends Exception {
        ViewMethodInvocationSerializationException(String message) {
            super(message);
        }
    }
}
+250 −29
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * Unless required by applicable law or agreed to in writing, softwareViewDebug
 * 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
@@ -16,6 +16,8 @@

package android.view;

import static com.android.internal.util.Preconditions.checkArgument;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
@@ -34,10 +36,13 @@ import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;

import com.android.internal.annotations.VisibleForTesting;

import libcore.util.HexEncoding;

import java.io.BufferedOutputStream;
@@ -54,9 +59,11 @@ 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.Member;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
@@ -67,7 +74,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -76,6 +82,9 @@ import java.util.stream.Stream;
 * Various debugging/tracing tools related to {@link View} and the view hierarchy.
 */
public class ViewDebug {

    private static final String TAG = "ViewDebug";

    /**
     * @deprecated This flag is now unused
     */
@@ -425,6 +434,7 @@ public class ViewDebug {
    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 final String REMOTE_COMMAND_INVOKE_METHOD = "INVOKE_METHOD";

    private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
    private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
@@ -555,6 +565,8 @@ public class ViewDebug {
                requestLayout(view, params[0]);
            } else if (REMOTE_PROFILE.equalsIgnoreCase(command)) {
                profile(view, clientStream, params[0]);
            } else if (REMOTE_COMMAND_INVOKE_METHOD.equals(command)) {
                invokeViewMethod(view, clientStream, params);
            }
        }
    }
@@ -1825,46 +1837,84 @@ public class ViewDebug {
        Log.d(tag, sb.toString());
    }

    private static void invokeViewMethod(View root, OutputStream clientStream, String[] params)
            throws IOException {
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
        try {
            if (params.length < 2) {
                throw new IllegalArgumentException("Missing parameter");
            }
            View targetView = findView(root, params[0]);
            if (targetView == null) {
                throw new IllegalArgumentException("View not found: " + params[0]);
            }
            String method = params[1];
            ByteBuffer args = ByteBuffer.wrap(params.length < 2
                    ? new byte[0]
                    : Base64.decode(params[2], Base64.NO_WRAP));
            byte[] result = invokeViewMethod(targetView, method, args);
            out.write("1");
            out.newLine();
            out.write(Base64.encodeToString(result, Base64.NO_WRAP));
            out.newLine();
        } catch (Exception e) {
            out.write("-1");
            out.newLine();
            out.write(e.getMessage());
            out.newLine();
        } finally {
            out.close();
        }
    }

    /**
     * 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
     * @param params all the method parameters encoded in a byteArray
     * @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>();
    public static byte[] invokeViewMethod(View targetView, String methodName, ByteBuffer params)
            throws ViewMethodInvocationSerializationException {
        Class<?>[] argTypes;
        Object[] args;
        if (!params.hasRemaining()) {
            argTypes = new Class<?>[0];
            args = new Object[0];
        } else {
            int nArgs = params.getInt();
            argTypes = new Class<?>[nArgs];
            args = new Object[nArgs];

        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);
            deserializeMethodParameters(args, argTypes, params);
        }

                latch.countDown();
        Method method;
        try {
            method = targetView.getClass().getMethod(methodName, argTypes);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "No such method: " + e.getMessage());
            throw new ViewMethodInvocationSerializationException(
                    "No such method: " + e.getMessage());
        }
        });

        try {
            latch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
            // Invoke the method on Views handler
            FutureTask<Object> task = new FutureTask<>(() -> method.invoke(targetView, args));
            targetView.post(task);
            Object result = task.get();
            Class<?> returnType = method.getReturnType();
            return serializeReturnValue(returnType, returnType.cast(result));
        } 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();
            }

        if (exception.get() != null) {
            throw new RuntimeException(exception.get());
            throw new RuntimeException(msg);
        }

        return result.get();
    }

    /**
@@ -1961,4 +2011,175 @@ public class ViewDebug {
         */
        Bitmap createBitmap();
    }

    /**
     * Deserializes parameters according to the VUOP_INVOKE_VIEW_METHOD protocol the {@code in}
     * buffer.
     *
     * The length of {@code args} determines how many arguments are read. The {@code argTypes} must
     * be the same length, and will be set to the argument types of the data read.
     *
     * @hide
     */
    @VisibleForTesting
    public static void deserializeMethodParameters(
            Object[] args, Class<?>[] argTypes, ByteBuffer in) throws
            ViewMethodInvocationSerializationException {
        checkArgument(args.length == argTypes.length);

        for (int i = 0; i < args.length; i++) {
            char typeSignature = in.getChar();
            boolean isArray = typeSignature == SIG_ARRAY;
            if (isArray) {
                char arrayType = in.getChar();
                if (arrayType != SIG_BYTE) {
                    // This implementation only supports byte-arrays for now.
                    throw new ViewMethodInvocationSerializationException(
                            "Unsupported array parameter type (" + typeSignature
                                    + ") to invoke view method @argument " + i);
                }

                int arrayLength = in.getInt();
                if (arrayLength > in.remaining()) {
                    // The sender did not actually sent the specified amount of bytes. This
                    // avoids a malformed packet to trigger an out-of-memory error.
                    throw new BufferUnderflowException();
                }

                byte[] byteArray = new byte[arrayLength];
                in.get(byteArray);

                argTypes[i] = byte[].class;
                args[i] = byteArray;
            } else {
                switch (typeSignature) {
                    case SIG_BOOLEAN:
                        argTypes[i] = boolean.class;
                        args[i] = in.get() != 0;
                        break;
                    case SIG_BYTE:
                        argTypes[i] = byte.class;
                        args[i] = in.get();
                        break;
                    case SIG_CHAR:
                        argTypes[i] = char.class;
                        args[i] = in.getChar();
                        break;
                    case SIG_SHORT:
                        argTypes[i] = short.class;
                        args[i] = in.getShort();
                        break;
                    case SIG_INT:
                        argTypes[i] = int.class;
                        args[i] = in.getInt();
                        break;
                    case SIG_LONG:
                        argTypes[i] = long.class;
                        args[i] = in.getLong();
                        break;
                    case SIG_FLOAT:
                        argTypes[i] = float.class;
                        args[i] = in.getFloat();
                        break;
                    case SIG_DOUBLE:
                        argTypes[i] = double.class;
                        args[i] = in.getDouble();
                        break;
                    case SIG_STRING: {
                        argTypes[i] = String.class;
                        int stringUtf8ByteCount = Short.toUnsignedInt(in.getShort());
                        byte[] rawStringBuffer = new byte[stringUtf8ByteCount];
                        in.get(rawStringBuffer);
                        args[i] = new String(rawStringBuffer, StandardCharsets.UTF_8);
                        break;
                    }
                    default:
                        Log.e(TAG, "arg " + i + ", unrecognized type: " + typeSignature);
                        throw new ViewMethodInvocationSerializationException(
                                "Unsupported parameter type (" + typeSignature
                                        + ") to invoke view method.");
                }
            }

        }
    }

    /**
     * Serializes {@code value} to the wire protocol of VUOP_INVOKE_VIEW_METHOD.
     * @hide
     */
    @VisibleForTesting
    public static byte[] serializeReturnValue(Class<?> type, Object value)
            throws ViewMethodInvocationSerializationException, IOException {
        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(1024);
        DataOutputStream dos = new DataOutputStream(byteOutStream);

        if (type.isArray()) {
            if (!type.equals(byte[].class)) {
                // Only byte arrays are supported currently.
                throw new ViewMethodInvocationSerializationException(
                        "Unsupported array return type (" + type + ")");
            }
            byte[] byteArray = (byte[]) value;
            dos.writeChar(SIG_ARRAY);
            dos.writeChar(SIG_BYTE);
            dos.writeInt(byteArray.length);
            dos.write(byteArray);
        } else if (boolean.class.equals(type)) {
            dos.writeChar(SIG_BOOLEAN);
            dos.write((boolean) value ? 1 : 0);
        } else if (byte.class.equals(type)) {
            dos.writeChar(SIG_BYTE);
            dos.writeByte((byte) value);
        } else if (char.class.equals(type)) {
            dos.writeChar(SIG_CHAR);
            dos.writeChar((char) value);
        } else if (short.class.equals(type)) {
            dos.writeChar(SIG_SHORT);
            dos.writeShort((short) value);
        } else if (int.class.equals(type)) {
            dos.writeChar(SIG_INT);
            dos.writeInt((int) value);
        } else if (long.class.equals(type)) {
            dos.writeChar(SIG_LONG);
            dos.writeLong((long) value);
        } else if (double.class.equals(type)) {
            dos.writeChar(SIG_DOUBLE);
            dos.writeDouble((double) value);
        } else if (float.class.equals(type)) {
            dos.writeChar(SIG_FLOAT);
            dos.writeFloat((float) value);
        } else if (String.class.equals(type)) {
            dos.writeChar(SIG_STRING);
            dos.writeUTF(value != null ? (String) value : "");
        } else {
            dos.writeChar(SIG_VOID);
        }

        return byteOutStream.toByteArray();
    }

    // Prefixes for simple primitives. These match the JNI definitions.
    private static final char SIG_ARRAY = '[';
    private static final char SIG_BOOLEAN = 'Z';
    private static final char SIG_BYTE = 'B';
    private static final char SIG_SHORT = 'S';
    private static final char SIG_CHAR = 'C';
    private static final char SIG_INT = 'I';
    private static final char SIG_LONG = 'J';
    private static final char SIG_FLOAT = 'F';
    private static final char SIG_DOUBLE = 'D';
    private static final char SIG_VOID = 'V';
    // Prefixes for some commonly used objects
    private static final char SIG_STRING = 'R';

    /**
     * @hide
     */
    @VisibleForTesting
    public static class ViewMethodInvocationSerializationException extends Exception {
        ViewMethodInvocationSerializationException(String message) {
            super(message);
        }
    }
}
+0 −1
Original line number Diff line number Diff line
michschn@google.com