Merge "Support invoking view methods from hierarchy viewer"

This commit is contained in:
Siva Velusamy
2013-01-28 18:02:49 +00:00
committed by Android (Google) Code Review
2 changed files with 188 additions and 22 deletions

View File

@@ -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;
/** Re-layout given view. */
private static final int VUOP_LAYOUT_VIEW = 4;
/** Profile a view. */
private static final int VUOP_PROFILE_VIEW = 5;
private static final int VUOP_PROFILE_VIEW = 3;
/** Invoke a method on the view. */
private static final int VUOP_INVOKE_VIEW_METHOD = 4;
/** 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;
}

View File

@@ -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);
}
});
}
}