diff options
Diffstat (limited to 'tests/HierarchyViewerTest/src/com/android')
4 files changed, 299 insertions, 0 deletions
diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java new file mode 100644 index 0000000..c6f1470 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java @@ -0,0 +1,101 @@ +package com.android.test.hierarchyviewer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +public class Decoder { + // Prefixes for simple primitives. These match the JNI definitions. + public static final byte SIG_BOOLEAN = 'Z'; + public static final byte SIG_BYTE = 'B'; + public static final byte SIG_SHORT = 'S'; + public static final byte SIG_INT = 'I'; + public static final byte SIG_LONG = 'J'; + public static final byte SIG_FLOAT = 'F'; + public static final byte SIG_DOUBLE = 'D'; + + // Prefixes for some commonly used objects + public static final byte SIG_STRING = 'R'; + + public static final byte SIG_MAP = 'M'; // a map with an short key + public static final short SIG_END_MAP = 0; + + private final ByteBuffer mBuf; + + public Decoder(byte[] buf) { + this(ByteBuffer.wrap(buf)); + } + + public Decoder(ByteBuffer buf) { + mBuf = buf; + } + + public boolean hasRemaining() { + return mBuf.hasRemaining(); + } + + public Object readObject() { + byte sig = mBuf.get(); + + switch (sig) { + case SIG_BOOLEAN: + return mBuf.get() == 0 ? Boolean.FALSE : Boolean.TRUE; + case SIG_BYTE: + return mBuf.get(); + case SIG_SHORT: + return mBuf.getShort(); + case SIG_INT: + return mBuf.getInt(); + case SIG_LONG: + return mBuf.getLong(); + case SIG_FLOAT: + return mBuf.getFloat(); + case SIG_DOUBLE: + return mBuf.getDouble(); + case SIG_STRING: + return readString(); + case SIG_MAP: + return readMap(); + default: + throw new DecoderException(sig, mBuf.position() - 1); + } + } + + private String readString() { + short len = mBuf.getShort(); + byte[] b = new byte[len]; + mBuf.get(b, 0, len); + return new String(b, Charset.forName("utf-8")); + } + + private Map<Short, Object> readMap() { + Map<Short, Object> m = new HashMap<Short, Object>(); + + while (true) { + Object o = readObject(); + if (!(o instanceof Short)) { + throw new DecoderException("Expected short key, got " + o.getClass()); + } + + Short key = (Short)o; + if (key == SIG_END_MAP) { + break; + } + + m.put(key, readObject()); + } + + return m; + } + + public static class DecoderException extends RuntimeException { + public DecoderException(byte seen, int pos) { + super(String.format("Unexpected byte %c seen at position %d", (char)seen, pos)); + } + + public DecoderException(String msg) { + super(msg); + } + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java new file mode 100644 index 0000000..3a67273 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java @@ -0,0 +1,44 @@ +package com.android.test.hierarchyviewer; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + + +public class MainActivity extends Activity { + private static final String TAG = "Main"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + View textView = findViewById(R.id.textView); + Log.d(TAG, "x, y = " + textView.getX() + ", " + textView.getY()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java new file mode 100644 index 0000000..aae0ff5 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java @@ -0,0 +1,81 @@ +package com.android.test.hierarchyviewer; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; + +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> { + private MainActivity mActivity; + private View mTextView; + + + public MainActivityTest() { + super(MainActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mActivity = getActivity(); + mTextView = mActivity.findViewById(R.id.textView); + } + + private byte[] encode(View view) throws ClassNotFoundException, NoSuchMethodException, + IllegalAccessException, InstantiationException, InvocationTargetException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 1024); + + Object encoder = createEncoder(baos); + invokeMethod(View.class, view, "encode", encoder); + invokeMethod(encoder.getClass(), encoder, "endStream"); + + return baos.toByteArray(); + } + + private Object invokeMethod(Class targetClass, Object target, String methodName, Object... params) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class[] paramClasses = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + paramClasses[i] = params[i].getClass(); + } + Method method = targetClass.getDeclaredMethod(methodName, paramClasses); + method.setAccessible(true); + return method.invoke(target, params); + } + + private Object createEncoder(ByteArrayOutputStream baos) throws ClassNotFoundException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + Class clazz = Class.forName("android.view.ViewHierarchyEncoder"); + Constructor constructor = clazz.getConstructor(ByteArrayOutputStream.class); + return constructor.newInstance(baos); + } + + public void testTextView() throws Exception { + byte[] data = encode(mTextView); + assertNotNull(data); + assertTrue(data.length > 0); + + ViewDumpParser parser = new ViewDumpParser(); + parser.parse(data); + + List<Map<Short, Object>> views = parser.getViews(); + Map<String, Short> propertyNameTable = parser.getIds(); + + assertEquals(1, views.size()); + assertNotNull(propertyNameTable); + + Map<Short, Object> textViewProperties = views.get(0); + assertEquals("android.widget.TextView", + textViewProperties.get(propertyNameTable.get("meta:__name__"))); + +// assertEquals(mActivity.getString(R.string.test), +// textViewProperties.get(propertyNameTable.get("text"))); + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java new file mode 100644 index 0000000..0111bc6 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java @@ -0,0 +1,73 @@ +package com.android.test.hierarchyviewer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ViewDumpParser { + private Map<String, Short> mIds; + private List<Map<Short,Object>> mViews; + + public void parse(byte[] data) { + Decoder d = new Decoder(ByteBuffer.wrap(data)); + + mViews = new ArrayList<>(100); + while (d.hasRemaining()) { + Object o = d.readObject(); + if (o instanceof Map) { + //noinspection unchecked + mViews.add((Map<Short, Object>) o); + } + } + + if (mViews.isEmpty()) { + return; + } + + // the last one is the property map + Map<Short,Object> idMap = mViews.remove(mViews.size() - 1); + mIds = reverse(idMap); + } + + public String getFirstView() { + if (mViews.isEmpty()) { + return null; + } + + Map<Short, Object> props = mViews.get(0); + Object name = getProperty(props, "__name__"); + Object hash = getProperty(props, "__hash__"); + + if (name instanceof String && hash instanceof Integer) { + return String.format(Locale.US, "%s@%x", name, hash); + } else { + return null; + } + } + + private Object getProperty(Map<Short, Object> props, String key) { + return props.get(mIds.get(key)); + } + + private static Map<String, Short> reverse(Map<Short, Object> m) { + Map<String, Short> r = new HashMap<String, Short>(m.size()); + + for (Map.Entry<Short, Object> e : m.entrySet()) { + r.put((String)e.getValue(), e.getKey()); + } + + return r; + } + + public List<Map<Short, Object>> getViews() { + return mViews; + } + + public Map<String, Short> getIds() { + return mIds; + } + +} |