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