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