blob: d5716bfaaf008ba21005ef7c343376f3d754f44a [file] [log] [blame]
Siva Velusamy0d857b92015-04-22 10:23:56 -07001package android.view;
2
3import android.annotation.NonNull;
4import android.annotation.Nullable;
Mathew Inwooda570dee2018-08-17 14:56:00 +01005import android.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
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
Mathew Inwooda570dee2018-08-17 14:56:00 +010094 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -070095 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
Mathew Inwooda570dee2018-08-17 14:56:00 +0100105 @UnsupportedAppUsage
Siva Velusamy0d857b92015-04-22 10:23:56 -0700106 public void addProperty(@NonNull String name, int v) {
107 writeShort(createPropertyIndex(name));
108 writeInt(v);
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, float v) {
113 writeShort(createPropertyIndex(name));
114 writeFloat(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, @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}