• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.widget.remotecompose.core.operations;
17 
18 import static com.android.internal.widget.remotecompose.core.CoreDocument.MAJOR_VERSION;
19 import static com.android.internal.widget.remotecompose.core.CoreDocument.MINOR_VERSION;
20 import static com.android.internal.widget.remotecompose.core.CoreDocument.PATCH_VERSION;
21 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
22 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG;
23 
24 import android.annotation.NonNull;
25 
26 import com.android.internal.widget.remotecompose.core.CoreDocument;
27 import com.android.internal.widget.remotecompose.core.Operation;
28 import com.android.internal.widget.remotecompose.core.Operations;
29 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
30 import com.android.internal.widget.remotecompose.core.RemoteContext;
31 import com.android.internal.widget.remotecompose.core.WireBuffer;
32 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
33 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
34 
35 import java.io.DataInputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.util.List;
39 
40 /**
41  * Describe some basic information for a RemoteCompose document
42  *
43  * <p>It encodes the version of the document (following semantic versioning) as well as the
44  * dimensions of the document in pixels.
45  */
46 public class Header extends Operation implements RemoteComposeOperation {
47     private static final int OP_CODE = Operations.HEADER;
48     private static final String CLASS_NAME = "Header";
49     private static final int MAGIC_NUMBER = 0x048C0000; // to uniquly identify the protocol
50 
51     int mMajorVersion;
52     int mMinorVersion;
53     int mPatchVersion;
54 
55     int mWidth = 256;
56     int mHeight = 256;
57 
58     float mDensity = 3;
59     long mCapabilities = 0;
60     private IntMap<Object> mProperties;
61 
62     /**
63      * Get a property on the header
64      *
65      * @param property the property to get
66      * @return the value of the property
67      */
get(short property)68     public Object get(short property) {
69         return mProperties.get(property);
70     }
71 
72     /** the width of the document */
73     public static final short DOC_WIDTH = 5;
74 
75     /** The height of the document */
76     public static final short DOC_HEIGHT = 6;
77 
78     /** The density at generation */
79     public static final short DOC_DENSITY_AT_GENERATION = 7;
80 
81     /** The desired FPS for the document */
82     public static final short DOC_DESIRED_FPS = 8;
83 
84     /** The description of the contents of the document */
85     public static final short DOC_CONTENT_DESCRIPTION = 9;
86 
87     /** The source of the document */
88     public static final short DOC_SOURCE = 11;
89 
90     public static final short DOC_DATA_UPDATE = 12;
91 
92     /** The object is an integer */
93     private static final short DATA_TYPE_INT = 0;
94 
95     /** The object is an float */
96     private static final short DATA_TYPE_FLOAT = 1;
97 
98     /** The object is an LONG */
99     private static final short DATA_TYPE_LONG = 2;
100 
101     /** The object is an UTF-8 encoded string */
102     private static final short DATA_TYPE_STRING = 3;
103 
104     private static final short[] KEYS = {
105         DOC_WIDTH,
106         DOC_HEIGHT,
107         DOC_DENSITY_AT_GENERATION,
108         DOC_DESIRED_FPS,
109         DOC_CONTENT_DESCRIPTION,
110         DOC_SOURCE,
111         DOC_DATA_UPDATE
112     };
113     private static final String[] KEY_NAMES = {
114         "DOC_WIDTH",
115         "DOC_HEIGHT",
116         "DOC_DENSITY_AT_GENERATION",
117         "DOC_DESIRED_FPS",
118         "DOC_CONTENT_DESCRIPTION",
119         "DOC_SOURCE"
120     };
121 
122     /**
123      * It encodes the version of the document (following semantic versioning) as well as the
124      * dimensions of the document in pixels.
125      *
126      * @param majorVersion the major version of the RemoteCompose document API
127      * @param minorVersion the minor version of the RemoteCompose document API
128      * @param patchVersion the patch version of the RemoteCompose document API
129      * @param width the width of the RemoteCompose document
130      * @param height the height of the RemoteCompose document
131      * @param density the density at which the document was originally created
132      * @param capabilities bitmask field storing needed capabilities (unused for now)
133      */
Header( int majorVersion, int minorVersion, int patchVersion, int width, int height, float density, long capabilities)134     public Header(
135             int majorVersion,
136             int minorVersion,
137             int patchVersion,
138             int width,
139             int height,
140             float density,
141             long capabilities) {
142         this.mMajorVersion = majorVersion;
143         this.mMinorVersion = minorVersion;
144         this.mPatchVersion = patchVersion;
145         this.mWidth = width;
146         this.mHeight = height;
147         this.mDensity = density;
148         this.mCapabilities = capabilities;
149     }
150 
151     /**
152      * @param majorVersion the major version of the RemoteCompose document API
153      * @param minorVersion the minor version of the RemoteCompose document API
154      * @param patchVersion the patch version of the RemoteCompose document API
155      * @param properties the properties of the document
156      */
Header(int majorVersion, int minorVersion, int patchVersion, IntMap<Object> properties)157     public Header(int majorVersion, int minorVersion, int patchVersion, IntMap<Object> properties) {
158         this.mMajorVersion = majorVersion;
159         this.mMinorVersion = minorVersion;
160         this.mPatchVersion = patchVersion;
161         if (properties != null) {
162             this.mProperties = properties;
163             this.mWidth = getInt(DOC_WIDTH, 256);
164             this.mHeight = getInt(DOC_HEIGHT, 256);
165             this.mDensity = getFloat(DOC_DENSITY_AT_GENERATION, 0);
166         }
167     }
168 
getInt(int key, int defaultValue)169     private int getInt(int key, int defaultValue) {
170         if (mProperties == null) {
171             return defaultValue;
172         }
173         Integer value = (Integer) mProperties.get(key);
174         if (value != null) {
175             return value;
176         } else {
177             return defaultValue;
178         }
179     }
180 
getLong(int key, long defaultValue)181     private long getLong(int key, long defaultValue) {
182         if (mProperties == null) {
183             return defaultValue;
184         }
185         Long value = (Long) mProperties.get(key);
186         if (value != null) {
187             return value;
188         } else {
189             return defaultValue;
190         }
191     }
192 
getFloat(int key, float defaultValue)193     private float getFloat(int key, float defaultValue) {
194         if (mProperties == null) {
195             return defaultValue;
196         }
197         Float value = (Float) mProperties.get(key);
198         if (value != null) {
199             return value;
200         } else {
201             return defaultValue;
202         }
203     }
204 
getString(int key, String defaultValue)205     private String getString(int key, String defaultValue) {
206         if (mProperties == null) {
207             return defaultValue;
208         }
209         String value = (String) mProperties.get(key);
210         if (value != null) {
211             return value;
212         } else {
213             return defaultValue;
214         }
215     }
216 
217     @Override
write(@onNull WireBuffer buffer)218     public void write(@NonNull WireBuffer buffer) {
219         apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
220     }
221 
222     @NonNull
223     @Override
toString()224     public String toString() {
225         String prop = "";
226         if (mProperties != null) {
227             for (int i = 0; i < KEYS.length; i++) {
228                 Object p = mProperties.get(KEYS[i]);
229                 if (p != null) {
230                     prop += "\n  " + KEY_NAMES[i] + " " + p.toString();
231                 }
232             }
233             return "HEADER v" + mMajorVersion + "." + mMinorVersion + "." + mPatchVersion + prop;
234         }
235         return "HEADER v"
236                 + mMajorVersion
237                 + "."
238                 + mMinorVersion
239                 + "."
240                 + mPatchVersion
241                 + ", "
242                 + mWidth
243                 + " x "
244                 + mHeight
245                 + " ["
246                 + mCapabilities
247                 + "]"
248                 + prop;
249     }
250 
251     @Override
apply(@onNull RemoteContext context)252     public void apply(@NonNull RemoteContext context) {
253         context.header(
254                 mMajorVersion,
255                 mMinorVersion,
256                 mPatchVersion,
257                 mWidth,
258                 mHeight,
259                 mCapabilities,
260                 mProperties);
261     }
262 
263     @NonNull
264     @Override
deepToString(@onNull String indent)265     public String deepToString(@NonNull String indent) {
266         return toString();
267     }
268 
269     /**
270      * The name of the class
271      *
272      * @return the name
273      */
274     @NonNull
name()275     public static String name() {
276         return CLASS_NAME;
277     }
278 
279     /**
280      * The OP_CODE for this command
281      *
282      * @return the opcode
283      */
id()284     public static int id() {
285         return OP_CODE;
286     }
287 
288     /**
289      * Apply the header to the wire buffer
290      *
291      * @param buffer
292      * @param width
293      * @param height
294      * @param density
295      * @param capabilities
296      */
apply( @onNull WireBuffer buffer, int width, int height, float density, long capabilities)297     public static void apply(
298             @NonNull WireBuffer buffer, int width, int height, float density, long capabilities) {
299         buffer.start(OP_CODE);
300         buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
301         buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
302         buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
303         buffer.writeInt(width);
304         buffer.writeInt(height);
305         // buffer.writeFloat(density); TODO fix or remove
306         buffer.writeLong(capabilities);
307     }
308 
309     /**
310      * Apply the header to the wire buffer
311      *
312      * @param buffer
313      */
apply(@onNull WireBuffer buffer, short[] type, Object[] value)314     public static void apply(@NonNull WireBuffer buffer, short[] type, Object[] value) {
315         buffer.start(OP_CODE);
316         buffer.writeInt(MAJOR_VERSION | MAGIC_NUMBER); // major version number of the protocol
317         buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
318         buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
319         buffer.writeInt(type.length);
320         writeMap(buffer, type, value);
321     }
322 
323     /**
324      * @param is the stream to read from
325      * @return the header
326      * @throws IOException if there is an error reading the header
327      */
readDirect(InputStream is)328     public static Header readDirect(InputStream is) throws IOException {
329         DataInputStream stream = new DataInputStream(is);
330         try {
331 
332             int type = stream.readByte();
333 
334             if (type != OP_CODE) {
335                 throw new IOException("Invalid header " + type + " != " + OP_CODE);
336             }
337             int majorVersion = stream.readInt();
338             int minorVersion = stream.readInt();
339             int patchVersion = stream.readInt();
340 
341             if (majorVersion < 0x10000) {
342                 int width = stream.readInt();
343                 int height = stream.readInt();
344                 // float density = is.read();
345                 float density = 1f;
346                 long capabilities = stream.readLong();
347                 return new Header(
348                         majorVersion,
349                         minorVersion,
350                         patchVersion,
351                         width,
352                         height,
353                         density,
354                         capabilities);
355             }
356 
357             if ((majorVersion & 0xFFFF0000) != MAGIC_NUMBER) {
358                 throw new IOException(
359                         "Invalid header MAGIC_NUMBER "
360                                 + (majorVersion & 0xFFFF0000)
361                                 + " != "
362                                 + MAGIC_NUMBER);
363             }
364             majorVersion &= 0xFFFF;
365             int len = stream.readInt();
366             short[] types = new short[len];
367             Object[] values = new Object[len];
368             readMap(stream, types, values);
369             IntMap<Object> map = new IntMap<>();
370             for (int i = 0; i < len; i++) {
371                 map.put(types[i], values[i]);
372             }
373             return new Header(majorVersion, minorVersion, patchVersion, map);
374 
375         } finally {
376             stream.close();
377         }
378     }
379 
380     /**
381      * Read this operation and add it to the list of operations
382      *
383      * @param stream the buffer to read
384      * @param types the list of types that will be populated
385      * @param values the list of values that will be populated
386      */
readMap(DataInputStream stream, short[] types, Object[] values)387     private static void readMap(DataInputStream stream, short[] types, Object[] values)
388             throws IOException {
389         for (int i = 0; i < types.length; i++) {
390             short tag = (short) stream.readShort();
391             int itemLen = stream.readShort();
392             int dataType = tag >> 10;
393             types[i] = (short) (tag & 0x3F);
394             Object value;
395             switch (dataType) {
396                 case DATA_TYPE_INT:
397                     values[i] = stream.readInt();
398                     break;
399                 case DATA_TYPE_FLOAT:
400                     values[i] = stream.readFloat();
401                     break;
402                 case DATA_TYPE_LONG:
403                     values[i] = stream.readLong();
404                     break;
405                 case DATA_TYPE_STRING:
406                     int slen = stream.readInt();
407                     byte[] data = new byte[slen];
408                     stream.readFully(data);
409                     values[i] = new String(data);
410                     break;
411             }
412         }
413     }
414 
415     /**
416      * Read this operation and add it to the list of operations
417      *
418      * @param buffer the buffer to read
419      * @param operations the list of operations that will be added to
420      */
read(@onNull WireBuffer buffer, @NonNull List<Operation> operations)421     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
422         int majorVersion = buffer.readInt();
423         int minorVersion = buffer.readInt();
424         int patchVersion = buffer.readInt();
425         if (majorVersion < 0x10000) {
426             int width = buffer.readInt();
427             int height = buffer.readInt();
428             // float density = buffer.readFloat();
429             float density = 1f;
430             long capabilities = buffer.readLong();
431             Header header =
432                     new Header(
433                             majorVersion,
434                             minorVersion,
435                             patchVersion,
436                             width,
437                             height,
438                             density,
439                             capabilities);
440             operations.add(header);
441         } else {
442             majorVersion &= 0xFFFF;
443             int length = buffer.readInt();
444             short[] types = new short[length];
445             Object[] values = new Object[length];
446             readMap(buffer, types, values);
447             IntMap<Object> map = new IntMap<>();
448             for (int i = 0; i < length; i++) {
449                 map.put(types[i], values[i]);
450             }
451             Header header = new Header(majorVersion, minorVersion, patchVersion, map);
452             operations.add(header);
453         }
454     }
455 
456     /**
457      * Read this operation and add it to the list of operations
458      *
459      * @param buffer the buffer to read
460      * @param types the list of types that will be populated
461      * @param values the list of values that will be populated
462      */
readMap(@onNull WireBuffer buffer, short[] types, Object[] values)463     private static void readMap(@NonNull WireBuffer buffer, short[] types, Object[] values) {
464         for (int i = 0; i < types.length; i++) {
465             short tag = (short) buffer.readShort();
466             int itemLen = buffer.readShort();
467             int dataType = tag >> 10;
468             types[i] = (short) (tag & 0x3F);
469             Object value;
470             switch (dataType) {
471                 case DATA_TYPE_INT:
472                     values[i] = buffer.readInt();
473                     break;
474                 case DATA_TYPE_FLOAT:
475                     values[i] = buffer.readFloat();
476                     break;
477                 case DATA_TYPE_LONG:
478                     values[i] = buffer.readLong();
479                     break;
480                 case DATA_TYPE_STRING:
481                     values[i] = buffer.readUTF8();
482                     break;
483             }
484         }
485     }
486 
487     /**
488      * Write the map of values to the buffer
489      *
490      * @param buffer the buffer to read
491      * @param types the list of types that will be written
492      * @param values the list of values that will be written
493      */
writeMap(@onNull WireBuffer buffer, short[] types, Object[] values)494     private static void writeMap(@NonNull WireBuffer buffer, short[] types, Object[] values) {
495         for (int i = 0; i < types.length; i++) {
496             short tag = types[i];
497             if (values[i] instanceof String) {
498                 tag = (short) (tag | (DATA_TYPE_STRING << 10));
499                 buffer.writeShort(tag);
500                 String str = (String) values[i];
501                 byte[] data = str.getBytes();
502                 buffer.writeShort((data.length + 4));
503                 buffer.writeBuffer(data);
504             } else if (values[i] instanceof Integer) {
505                 tag = (short) (tag | (DATA_TYPE_INT << 10));
506                 buffer.writeShort(tag);
507                 buffer.writeShort(4);
508                 buffer.writeInt((Integer) values[i]);
509             } else if (values[i] instanceof Float) {
510                 tag = (short) (tag | (DATA_TYPE_FLOAT << 10));
511                 buffer.writeShort(tag);
512                 buffer.writeShort(4);
513 
514                 buffer.writeFloat((float) values[i]);
515             } else if (values[i] instanceof Long) {
516                 tag = (short) (tag | (DATA_TYPE_LONG << 10));
517                 buffer.writeShort(tag);
518                 buffer.writeShort(8);
519                 buffer.writeLong((Long) values[i]);
520             }
521         }
522     }
523 
524     /**
525      * Populate the documentation with a description of this operation
526      *
527      * @param doc to append the description to.
528      */
documentation(@onNull DocumentationBuilder doc)529     public static void documentation(@NonNull DocumentationBuilder doc) {
530         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
531                 .description(
532                         "Document metadata, containing the version,"
533                                 + " original size & density, capabilities mask")
534                 .field(INT, "MAJOR_VERSION", "Major version")
535                 .field(INT, "MINOR_VERSION", "Minor version")
536                 .field(INT, "PATCH_VERSION", "Patch version")
537                 .field(INT, "WIDTH", "Major version")
538                 .field(INT, "HEIGHT", "Major version")
539                 // .field(FLOAT, "DENSITY", "Major version")
540                 .field(LONG, "CAPABILITIES", "Major version");
541     }
542 
543     /**
544      * Set the version on a document
545      *
546      * @param document
547      */
setVersion(CoreDocument document)548     public void setVersion(CoreDocument document) {
549         document.setUpdateDoc(getInt(DOC_DATA_UPDATE, 0) != 0);
550         document.setVersion(mMajorVersion, mMinorVersion, mPatchVersion);
551     }
552 }
553