1 package android.view; 2 3 import android.annotation.NonNull; 4 import android.annotation.Nullable; 5 import android.compat.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 ViewHierarchyEncoder(@onNull ByteArrayOutputStream stream)69 public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) { 70 mStream = new DataOutputStream(stream); 71 } 72 beginObject(@onNull Object o)73 public void beginObject(@NonNull Object o) { 74 startPropertyMap(); 75 addProperty("meta:__name__", o.getClass().getName()); 76 addProperty("meta:__hash__", o.hashCode()); 77 } 78 endObject()79 public void endObject() { 80 endPropertyMap(); 81 } 82 endStream()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 addProperty(@onNull String name, boolean v)95 public void addProperty(@NonNull String name, boolean v) { 96 writeShort(createPropertyIndex(name)); 97 writeBoolean(v); 98 } 99 addProperty(@onNull String name, short s)100 public void addProperty(@NonNull String name, short s) { 101 writeShort(createPropertyIndex(name)); 102 writeShort(s); 103 } 104 105 @UnsupportedAppUsage addProperty(@onNull String name, int v)106 public void addProperty(@NonNull String name, int v) { 107 writeShort(createPropertyIndex(name)); 108 writeInt(v); 109 } 110 111 @UnsupportedAppUsage addProperty(@onNull String name, float v)112 public void addProperty(@NonNull String name, float v) { 113 writeShort(createPropertyIndex(name)); 114 writeFloat(v); 115 } 116 117 @UnsupportedAppUsage addProperty(@onNull String name, @Nullable String s)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 */ addPropertyKey(@onNull String name)127 public void addPropertyKey(@NonNull String name) { 128 writeShort(createPropertyIndex(name)); 129 } 130 createPropertyIndex(@onNull String name)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 startPropertyMap()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 endPropertyMap()149 private void endPropertyMap() { 150 writeShort(SIG_END_MAP); 151 } 152 writeBoolean(boolean v)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 writeShort(short s)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 writeInt(int i)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 writeFloat(float v)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 writeString(@ullable String s)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