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