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