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 69 private boolean mUserPropertiesEnabled = true; 70 ViewHierarchyEncoder(@onNull ByteArrayOutputStream stream)71 public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) { 72 mStream = new DataOutputStream(stream); 73 } 74 setUserPropertiesEnabled(boolean enabled)75 public void setUserPropertiesEnabled(boolean enabled) { 76 mUserPropertiesEnabled = enabled; 77 } 78 beginObject(@onNull Object o)79 public void beginObject(@NonNull Object o) { 80 startPropertyMap(); 81 addProperty("meta:__name__", o.getClass().getName()); 82 addProperty("meta:__hash__", o.hashCode()); 83 } 84 endObject()85 public void endObject() { 86 endPropertyMap(); 87 } 88 endStream()89 public void endStream() { 90 // write out the string table 91 startPropertyMap(); 92 addProperty("__name__", "propertyIndex"); 93 for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) { 94 writeShort(entry.getValue()); 95 writeString(entry.getKey()); 96 } 97 endPropertyMap(); 98 } 99 100 @UnsupportedAppUsage addProperty(@onNull String name, boolean v)101 public void addProperty(@NonNull String name, boolean v) { 102 writeShort(createPropertyIndex(name)); 103 writeBoolean(v); 104 } 105 addProperty(@onNull String name, short s)106 public void addProperty(@NonNull String name, short s) { 107 writeShort(createPropertyIndex(name)); 108 writeShort(s); 109 } 110 111 @UnsupportedAppUsage addProperty(@onNull String name, int v)112 public void addProperty(@NonNull String name, int v) { 113 writeShort(createPropertyIndex(name)); 114 writeInt(v); 115 } 116 117 @UnsupportedAppUsage addProperty(@onNull String name, float v)118 public void addProperty(@NonNull String name, float v) { 119 writeShort(createPropertyIndex(name)); 120 writeFloat(v); 121 } 122 123 @UnsupportedAppUsage addProperty(@onNull String name, @Nullable String s)124 public void addProperty(@NonNull String name, @Nullable String s) { 125 writeShort(createPropertyIndex(name)); 126 writeString(s); 127 } 128 129 /** 130 * Encodes a user defined property if they are allowed to be encoded 131 * 132 * @see #setUserPropertiesEnabled(boolean) 133 */ addUserProperty(@onNull String name, @Nullable String s)134 public void addUserProperty(@NonNull String name, @Nullable String s) { 135 if (mUserPropertiesEnabled) { 136 addProperty(name, s); 137 } 138 } 139 140 /** 141 * Writes the given name as the property name, and leaves it to the callee 142 * to fill in value for this property. 143 */ addPropertyKey(@onNull String name)144 public void addPropertyKey(@NonNull String name) { 145 writeShort(createPropertyIndex(name)); 146 } 147 createPropertyIndex(@onNull String name)148 private short createPropertyIndex(@NonNull String name) { 149 Short index = mPropertyNames.get(name); 150 if (index == null) { 151 index = mPropertyId++; 152 mPropertyNames.put(name, index); 153 } 154 155 return index; 156 } 157 startPropertyMap()158 private void startPropertyMap() { 159 try { 160 mStream.write(SIG_MAP); 161 } catch (IOException e) { 162 // does not happen since the stream simply wraps a ByteArrayOutputStream 163 } 164 } 165 endPropertyMap()166 private void endPropertyMap() { 167 writeShort(SIG_END_MAP); 168 } 169 writeBoolean(boolean v)170 private void writeBoolean(boolean v) { 171 try { 172 mStream.write(SIG_BOOLEAN); 173 mStream.write(v ? 1 : 0); 174 } catch (IOException e) { 175 // does not happen since the stream simply wraps a ByteArrayOutputStream 176 } 177 } 178 writeShort(short s)179 private void writeShort(short s) { 180 try { 181 mStream.write(SIG_SHORT); 182 mStream.writeShort(s); 183 } catch (IOException e) { 184 // does not happen since the stream simply wraps a ByteArrayOutputStream 185 } 186 } 187 writeInt(int i)188 private void writeInt(int i) { 189 try { 190 mStream.write(SIG_INT); 191 mStream.writeInt(i); 192 } catch (IOException e) { 193 // does not happen since the stream simply wraps a ByteArrayOutputStream 194 } 195 } 196 writeFloat(float v)197 private void writeFloat(float v) { 198 try { 199 mStream.write(SIG_FLOAT); 200 mStream.writeFloat(v); 201 } catch (IOException e) { 202 // does not happen since the stream simply wraps a ByteArrayOutputStream 203 } 204 } 205 writeString(@ullable String s)206 private void writeString(@Nullable String s) { 207 if (s == null) { 208 s = ""; 209 } 210 211 try { 212 mStream.write(SIG_STRING); 213 byte[] bytes = s.getBytes(mCharset); 214 215 short len = (short)Math.min(bytes.length, Short.MAX_VALUE); 216 mStream.writeShort(len); 217 218 mStream.write(bytes, 0, len); 219 } catch (IOException e) { 220 // does not happen since the stream simply wraps a ByteArrayOutputStream 221 } 222 } 223 } 224