blob: ace05a6b6bf9e812bacaa70b104f4c6de84928f9 [file] [log] [blame]
Siva Velusamy0d857b92015-04-22 10:23:56 -07001package android.view;
2
3import android.annotation.NonNull;
4import android.annotation.Nullable;
Artur Satayevad9254c2019-12-10 17:47:54 +00005import android.compat.annotation.UnsupportedAppUsage;
Siva Velusamy0d857b92015-04-22 10:23:56 -07006
7import java.io.ByteArrayOutputStream;
8import java.io.DataOutputStream;
9import java.io.IOException;
10import java.nio.charset.Charset;
11import java.util.HashMap;
12import 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 */
47public 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
Sunny Goyalc444b512020-02-25 09:57:50 -080069 private boolean mUserPropertiesEnabled = true;
70
Siva Velusamy0d857b92015-04-22 10:23:56 -070071 public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
72 mStream = new DataOutputStream(stream);
73 }
74
Sunny Goyalc444b512020-02-25 09:57:50 -080075 public void setUserPropertiesEnabled(boolean enabled) {
76 mUserPropertiesEnabled = enabled;
77 }
78
Siva Velusamy0d857b92015-04-22 10:23:56 -070079 public void beginObject(@NonNull Object o) {
80 startPropertyMap();
81 addProperty("meta:__name__", o.getClass().getName());
82 addProperty("meta:__hash__", o.hashCode());
83 }
84
85 public void endObject() {
86 endPropertyMap();
87 }
88
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
Mathew Inwooda570dee2018-08-17 14:56:00 +0100100 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -0700101 public void addProperty(@NonNull String name, boolean v) {
102 writeShort(createPropertyIndex(name));
103 writeBoolean(v);
104 }
105
106 public void addProperty(@NonNull String name, short s) {
107 writeShort(createPropertyIndex(name));
108 writeShort(s);
109 }
110
Mathew Inwooda570dee2018-08-17 14:56:00 +0100111 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -0700112 public void addProperty(@NonNull String name, int v) {
113 writeShort(createPropertyIndex(name));
114 writeInt(v);
115 }
116
Mathew Inwooda570dee2018-08-17 14:56:00 +0100117 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -0700118 public void addProperty(@NonNull String name, float v) {
119 writeShort(createPropertyIndex(name));
120 writeFloat(v);
121 }
122
Mathew Inwooda570dee2018-08-17 14:56:00 +0100123 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -0700124 public void addProperty(@NonNull String name, @Nullable String s) {
125 writeShort(createPropertyIndex(name));
126 writeString(s);
127 }
128
129 /**
Sunny Goyalc444b512020-02-25 09:57:50 -0800130 * Encodes a user defined property if they are allowed to be encoded
131 *
132 * @see #setUserPropertiesEnabled(boolean)
133 */
134 public void addUserProperty(@NonNull String name, @Nullable String s) {
135 if (mUserPropertiesEnabled) {
136 addProperty(name, s);
137 }
138 }
139
140 /**
Siva Velusamy0d857b92015-04-22 10:23:56 -0700141 * Writes the given name as the property name, and leaves it to the callee
142 * to fill in value for this property.
143 */
144 public void addPropertyKey(@NonNull String name) {
145 writeShort(createPropertyIndex(name));
146 }
147
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
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
166 private void endPropertyMap() {
167 writeShort(SIG_END_MAP);
168 }
169
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
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
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
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
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}