10d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamypackage android.view;
20d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
30d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport android.annotation.NonNull;
40d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport android.annotation.Nullable;
50d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
60d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.io.ByteArrayOutputStream;
70d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.io.DataOutputStream;
80d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.io.IOException;
90d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.nio.charset.Charset;
100d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.util.HashMap;
110d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamyimport java.util.Map;
120d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
130d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy/**
140d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
150d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * view hierarchies (the view tree, along with the properties for each view) to a stream.
160d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
170d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * It is typically used as follows:
180d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * <pre>
190d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *   ViewHierarchyEncoder e = new ViewHierarchyEncoder();
200d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
210d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *   for (View view : views) {
220d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *      e.beginObject(view);
230d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *      e.addProperty("prop1", value);
240d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *      ...
250d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *      e.endObject();
260d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *   }
270d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
280d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *   // repeat above snippet for each view, finally end with:
290d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *   e.endStream();
300d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * </pre>
310d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
320d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
330d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * corresponding to each view) with the property name as the key and the property value
340d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * as the value.
350d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
360d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * <p>Since the property names are practically the same across all views, rather than using
370d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * the property name directly as the key, we use a short integer id corresponding to each
380d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * property name as the key. A final map is added at the end which contains the mapping
390d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * from the integer to its property name.
400d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
410d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * <p>A value is encoded as a single byte type identifier followed by the encoding of the
420d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * value. Only primitive types are supported as values, in addition to the Map type.
430d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy *
440d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy * @hide
450d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy */
460d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamypublic class ViewHierarchyEncoder {
470d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    // Prefixes for simple primitives. These match the JNI definitions.
480d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_BOOLEAN = 'Z';
490d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_BYTE = 'B';
500d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_SHORT = 'S';
510d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_INT = 'I';
520d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_LONG = 'J';
530d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_FLOAT = 'F';
540d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_DOUBLE = 'D';
550d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
560d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    // Prefixes for some commonly used objects
570d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_STRING = 'R';
580d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
590d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final byte SIG_MAP = 'M'; // a map with an short key
600d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private static final short SIG_END_MAP = 0;
610d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
620d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private final DataOutputStream mStream;
630d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
640d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
650d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private short mPropertyId = 1;
660d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private Charset mCharset = Charset.forName("utf-8");
670d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
680d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
690d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        mStream = new DataOutputStream(stream);
700d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
710d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
720d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void beginObject(@NonNull Object o) {
730d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        startPropertyMap();
740d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        addProperty("meta:__name__", o.getClass().getName());
750d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        addProperty("meta:__hash__", o.hashCode());
760d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
770d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
780d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void endObject() {
790d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        endPropertyMap();
800d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
810d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
820d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void endStream() {
830d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        // write out the string table
840d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        startPropertyMap();
850d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        addProperty("__name__", "propertyIndex");
860d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
870d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            writeShort(entry.getValue());
880d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            writeString(entry.getKey());
890d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
900d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        endPropertyMap();
910d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
920d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
930d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addProperty(@NonNull String name, boolean v) {
940d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
950d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeBoolean(v);
960d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
970d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
980d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addProperty(@NonNull String name, short s) {
990d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
1000d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(s);
1010d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1020d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1030d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addProperty(@NonNull String name, int v) {
1040d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
1050d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeInt(v);
1060d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1070d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1080d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addProperty(@NonNull String name, float v) {
1090d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
1100d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeFloat(v);
1110d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1120d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1130d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addProperty(@NonNull String name, @Nullable String s) {
1140d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
1150d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeString(s);
1160d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1170d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1180d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    /**
1190d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy     * Writes the given name as the property name, and leaves it to the callee
1200d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy     * to fill in value for this property.
1210d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy     */
1220d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    public void addPropertyKey(@NonNull String name) {
1230d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(createPropertyIndex(name));
1240d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1250d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1260d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private short createPropertyIndex(@NonNull String name) {
1270d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        Short index = mPropertyNames.get(name);
1280d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        if (index == null) {
1290d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            index = mPropertyId++;
1300d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mPropertyNames.put(name, index);
1310d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1320d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1330d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        return index;
1340d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1350d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1360d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void startPropertyMap() {
1370d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1380d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_MAP);
1390d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1400d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1410d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1420d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1430d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1440d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void endPropertyMap() {
1450d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        writeShort(SIG_END_MAP);
1460d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1470d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1480d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void writeBoolean(boolean v) {
1490d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1500d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_BOOLEAN);
1510d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(v ? 1 : 0);
1520d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1530d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1540d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1550d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1560d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1570d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void writeShort(short s) {
1580d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1590d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_SHORT);
1600d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.writeShort(s);
1610d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1620d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1630d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1640d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1650d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1660d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void writeInt(int i) {
1670d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1680d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_INT);
1690d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.writeInt(i);
1700d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1710d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1720d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1730d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1740d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1750d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void writeFloat(float v) {
1760d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1770d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_FLOAT);
1780d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.writeFloat(v);
1790d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1800d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1810d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1820d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
1830d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1840d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    private void writeString(@Nullable String s) {
1850d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        if (s == null) {
1860d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            s = "";
1870d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
1880d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1890d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        try {
1900d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(SIG_STRING);
1910d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            byte[] bytes = s.getBytes(mCharset);
1920d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1930d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
1940d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.writeShort(len);
1950d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy
1960d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            mStream.write(bytes, 0, len);
1970d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        } catch (IOException e) {
1980d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy            // does not happen since the stream simply wraps a ByteArrayOutputStream
1990d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy        }
2000d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy    }
2010d857b9028f2702ce439e13feccde8182d40e1e5Siva Velusamy}
202