Home | History | Annotate | Download | only in view
      1 package android.view;
      2 
      3 import android.annotation.NonNull;
      4 import android.annotation.Nullable;
      5 
      6 import java.io.ByteArrayOutputStream;
      7 import java.io.DataOutputStream;
      8 import java.io.IOException;
      9 import java.nio.charset.Charset;
     10 import java.util.HashMap;
     11 import java.util.Map;
     12 
     13 /**
     14  * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
     15  * view hierarchies (the view tree, along with the properties for each view) to a stream.
     16  *
     17  * It is typically used as follows:
     18  * <pre>
     19  *   ViewHierarchyEncoder e = new ViewHierarchyEncoder();
     20  *
     21  *   for (View view : views) {
     22  *      e.beginObject(view);
     23  *      e.addProperty("prop1", value);
     24  *      ...
     25  *      e.endObject();
     26  *   }
     27  *
     28  *   // repeat above snippet for each view, finally end with:
     29  *   e.endStream();
     30  * </pre>
     31  *
     32  * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
     33  * corresponding to each view) with the property name as the key and the property value
     34  * as the value.
     35  *
     36  * <p>Since the property names are practically the same across all views, rather than using
     37  * the property name directly as the key, we use a short integer id corresponding to each
     38  * property name as the key. A final map is added at the end which contains the mapping
     39  * from the integer to its property name.
     40  *
     41  * <p>A value is encoded as a single byte type identifier followed by the encoding of the
     42  * value. Only primitive types are supported as values, in addition to the Map type.
     43  *
     44  * @hide
     45  */
     46 public class ViewHierarchyEncoder {
     47     // Prefixes for simple primitives. These match the JNI definitions.
     48     private static final byte SIG_BOOLEAN = 'Z';
     49     private static final byte SIG_BYTE = 'B';
     50     private static final byte SIG_SHORT = 'S';
     51     private static final byte SIG_INT = 'I';
     52     private static final byte SIG_LONG = 'J';
     53     private static final byte SIG_FLOAT = 'F';
     54     private static final byte SIG_DOUBLE = 'D';
     55 
     56     // Prefixes for some commonly used objects
     57     private static final byte SIG_STRING = 'R';
     58 
     59     private static final byte SIG_MAP = 'M'; // a map with an short key
     60     private static final short SIG_END_MAP = 0;
     61 
     62     private final DataOutputStream mStream;
     63 
     64     private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
     65     private short mPropertyId = 1;
     66     private Charset mCharset = Charset.forName("utf-8");
     67 
     68     public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
     69         mStream = new DataOutputStream(stream);
     70     }
     71 
     72     public void beginObject(@NonNull Object o) {
     73         startPropertyMap();
     74         addProperty("meta:__name__", o.getClass().getName());
     75         addProperty("meta:__hash__", o.hashCode());
     76     }
     77 
     78     public void endObject() {
     79         endPropertyMap();
     80     }
     81 
     82     public void endStream() {
     83         // write out the string table
     84         startPropertyMap();
     85         addProperty("__name__", "propertyIndex");
     86         for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
     87             writeShort(entry.getValue());
     88             writeString(entry.getKey());
     89         }
     90         endPropertyMap();
     91     }
     92 
     93     public void addProperty(@NonNull String name, boolean v) {
     94         writeShort(createPropertyIndex(name));
     95         writeBoolean(v);
     96     }
     97 
     98     public void addProperty(@NonNull String name, short s) {
     99         writeShort(createPropertyIndex(name));
    100         writeShort(s);
    101     }
    102 
    103     public void addProperty(@NonNull String name, int v) {
    104         writeShort(createPropertyIndex(name));
    105         writeInt(v);
    106     }
    107 
    108     public void addProperty(@NonNull String name, float v) {
    109         writeShort(createPropertyIndex(name));
    110         writeFloat(v);
    111     }
    112 
    113     public void addProperty(@NonNull String name, @Nullable String s) {
    114         writeShort(createPropertyIndex(name));
    115         writeString(s);
    116     }
    117 
    118     /**
    119      * Writes the given name as the property name, and leaves it to the callee
    120      * to fill in value for this property.
    121      */
    122     public void addPropertyKey(@NonNull String name) {
    123         writeShort(createPropertyIndex(name));
    124     }
    125 
    126     private short createPropertyIndex(@NonNull String name) {
    127         Short index = mPropertyNames.get(name);
    128         if (index == null) {
    129             index = mPropertyId++;
    130             mPropertyNames.put(name, index);
    131         }
    132 
    133         return index;
    134     }
    135 
    136     private void startPropertyMap() {
    137         try {
    138             mStream.write(SIG_MAP);
    139         } catch (IOException e) {
    140             // does not happen since the stream simply wraps a ByteArrayOutputStream
    141         }
    142     }
    143 
    144     private void endPropertyMap() {
    145         writeShort(SIG_END_MAP);
    146     }
    147 
    148     private void writeBoolean(boolean v) {
    149         try {
    150             mStream.write(SIG_BOOLEAN);
    151             mStream.write(v ? 1 : 0);
    152         } catch (IOException e) {
    153             // does not happen since the stream simply wraps a ByteArrayOutputStream
    154         }
    155     }
    156 
    157     private void writeShort(short s) {
    158         try {
    159             mStream.write(SIG_SHORT);
    160             mStream.writeShort(s);
    161         } catch (IOException e) {
    162             // does not happen since the stream simply wraps a ByteArrayOutputStream
    163         }
    164     }
    165 
    166     private void writeInt(int i) {
    167         try {
    168             mStream.write(SIG_INT);
    169             mStream.writeInt(i);
    170         } catch (IOException e) {
    171             // does not happen since the stream simply wraps a ByteArrayOutputStream
    172         }
    173     }
    174 
    175     private void writeFloat(float v) {
    176         try {
    177             mStream.write(SIG_FLOAT);
    178             mStream.writeFloat(v);
    179         } catch (IOException e) {
    180             // does not happen since the stream simply wraps a ByteArrayOutputStream
    181         }
    182     }
    183 
    184     private void writeString(@Nullable String s) {
    185         if (s == null) {
    186             s = "";
    187         }
    188 
    189         try {
    190             mStream.write(SIG_STRING);
    191             byte[] bytes = s.getBytes(mCharset);
    192 
    193             short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
    194             mStream.writeShort(len);
    195 
    196             mStream.write(bytes, 0, len);
    197         } catch (IOException e) {
    198             // does not happen since the stream simply wraps a ByteArrayOutputStream
    199         }
    200     }
    201 }
    202