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; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 21 import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess; 22 import com.android.internal.widget.remotecompose.core.operations.utilities.CollectionsAccess; 23 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap; 24 import com.android.internal.widget.remotecompose.core.operations.utilities.IntFloatMap; 25 import com.android.internal.widget.remotecompose.core.operations.utilities.IntIntMap; 26 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; 27 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.HashMap; 32 33 /** 34 * Represents runtime state for a RemoteCompose document State includes things like the value of 35 * variables 36 */ 37 public class RemoteComposeState implements CollectionsAccess { 38 public static final int START_ID = 42; 39 // private static final int MAX_FLOATS = 500; 40 private static int sMaxColors = 200; 41 42 private static final int MAX_DATA = 1000; 43 private final IntMap<Object> mIntDataMap = new IntMap<>(); 44 private final IntMap<Boolean> mIntWrittenMap = new IntMap<>(); 45 private final HashMap<Object, Integer> mDataIntMap = new HashMap<>(); 46 private final IntFloatMap mFloatMap = new IntFloatMap(); // efficient cache 47 private final IntIntMap mIntegerMap = new IntIntMap(); // efficient cache 48 private final IntIntMap mColorMap = new IntIntMap(); // efficient cache 49 private final IntMap<DataMap> mDataMapMap = new IntMap<>(); 50 private final IntMap<Object> mObjectMap = new IntMap<>(); 51 52 // path information 53 private final IntMap<Object> mPathMap = new IntMap<>(); 54 private final IntMap<float[]> mPathData = new IntMap<>(); 55 56 private boolean[] mColorOverride = new boolean[sMaxColors]; 57 @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>(); 58 59 private final boolean[] mDataOverride = new boolean[MAX_DATA]; 60 private final boolean[] mIntegerOverride = new boolean[MAX_DATA]; 61 private final boolean[] mFloatOverride = new boolean[MAX_DATA]; 62 63 private int mNextId = START_ID; 64 @NonNull private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY}; 65 @Nullable private RemoteContext mRemoteContext = null; 66 67 /** 68 * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be 69 * accessed with this command 70 * 71 * @param id the id of the object 72 * @return the object 73 */ 74 @Nullable getFromId(int id)75 public Object getFromId(int id) { 76 return mIntDataMap.get(id); 77 } 78 79 /** 80 * true if the cache contain this id 81 * 82 * @param id the id of the object 83 * @return true if the cache contain this id 84 */ containsId(int id)85 public boolean containsId(int id) { 86 return mIntDataMap.get(id) != null; 87 } 88 89 /** Return the id of an item from the cache. */ dataGetId(@onNull Object data)90 public int dataGetId(@NonNull Object data) { 91 Integer res = mDataIntMap.get(data); 92 if (res == null) { 93 return -1; 94 } 95 return res; 96 } 97 98 /** 99 * Add an item to the cache. Generates an id for the item and adds it to the cache based on that 100 * id. 101 */ cacheData(@onNull Object item)102 public int cacheData(@NonNull Object item) { 103 int id = nextId(); 104 mDataIntMap.put(item, id); 105 mIntDataMap.put(id, item); 106 return id; 107 } 108 109 /** 110 * Add an item to the cache. Generates an id for the item and adds it to the cache based on that 111 * id. 112 */ cacheData(@onNull Object item, int type)113 public int cacheData(@NonNull Object item, int type) { 114 int id = nextId(type); 115 mDataIntMap.put(item, id); 116 mIntDataMap.put(id, item); 117 return id; 118 } 119 120 /** Insert an item in the cache */ cacheData(int id, @NonNull Object item)121 public void cacheData(int id, @NonNull Object item) { 122 mDataIntMap.put(item, id); 123 mIntDataMap.put(id, item); 124 } 125 126 /** Insert an item in the cache */ updateData(int id, @NonNull Object item)127 public void updateData(int id, @NonNull Object item) { 128 if (!mDataOverride[id]) { 129 Object previous = mIntDataMap.get(id); 130 if (previous != item) { 131 mDataIntMap.remove(previous); 132 mDataIntMap.put(item, id); 133 mIntDataMap.put(id, item); 134 updateListeners(id); 135 } 136 } 137 } 138 139 /** 140 * Get the path asociated with the Data 141 * 142 * @param id of path 143 * @return path object 144 */ getPath(int id)145 public Object getPath(int id) { 146 return mPathMap.get(id); 147 } 148 149 /** 150 * Cache a path object. Object will be cleared if you update path data. 151 * 152 * @param id number asociated with path 153 * @param path the path object typically Android Path 154 */ putPath(int id, Object path)155 public void putPath(int id, Object path) { 156 mPathMap.put(id, path); 157 } 158 159 /** 160 * The path data the Array of floats that is asoicated with the path It also removes the current 161 * path object. 162 * 163 * @param id the integer asociated with the data and path 164 * @param data the array of floats that represents the path 165 */ putPathData(int id, float[] data)166 public void putPathData(int id, float[] data) { 167 mPathData.put(id, data); 168 mPathMap.remove(id); 169 } 170 171 /** 172 * Get the path data asociated with the id 173 * 174 * @param id number that represents the path 175 * @return path data 176 */ getPathData(int id)177 public float[] getPathData(int id) { 178 return mPathData.get(id); 179 } 180 181 /** 182 * Adds a data Override. 183 * 184 * @param id the id of the data 185 * @param item the new value 186 */ overrideData(int id, @NonNull Object item)187 public void overrideData(int id, @NonNull Object item) { 188 Object previous = mIntDataMap.get(id); 189 if (previous != item) { 190 mDataIntMap.remove(previous); 191 mDataIntMap.put(item, id); 192 mIntDataMap.put(id, item); 193 mDataOverride[id] = true; 194 updateListeners(id); 195 } 196 } 197 198 /** Insert an item in the cache */ cacheFloat(float item)199 public int cacheFloat(float item) { 200 int id = nextId(); 201 mFloatMap.put(id, item); 202 mIntegerMap.put(id, (int) item); 203 return id; 204 } 205 206 /** Insert an item in the cache */ cacheFloat(int id, float item)207 public void cacheFloat(int id, float item) { 208 mFloatMap.put(id, item); 209 } 210 211 /** Insert an float item in the cache */ updateFloat(int id, float value)212 public void updateFloat(int id, float value) { 213 if (!mFloatOverride[id]) { 214 float previous = mFloatMap.get(id); 215 if (previous != value) { 216 mFloatMap.put(id, value); 217 mIntegerMap.put(id, (int) value); 218 updateListeners(id); 219 } 220 } 221 } 222 223 /** 224 * Adds a float Override. 225 * 226 * @param id The id of the float 227 * @param value the override value 228 */ overrideFloat(int id, float value)229 public void overrideFloat(int id, float value) { 230 float previous = mFloatMap.get(id); 231 if (previous != value) { 232 mFloatMap.put(id, value); 233 mIntegerMap.put(id, (int) value); 234 mFloatOverride[id] = true; 235 updateListeners(id); 236 } 237 } 238 239 /** 240 * Insert an item in the cache 241 * 242 * @param item integer item to cache 243 * @return the id of the integer 244 */ cacheInteger(int item)245 public int cacheInteger(int item) { 246 int id = nextId(); 247 mIntegerMap.put(id, item); 248 mFloatMap.put(id, item); 249 return id; 250 } 251 252 /** 253 * Insert an integer item in the cache 254 * 255 * @param id the id of the integer 256 * @param value the value of the integer 257 */ updateInteger(int id, int value)258 public void updateInteger(int id, int value) { 259 if (!mIntegerOverride[id]) { 260 int previous = mIntegerMap.get(id); 261 if (previous != value) { 262 mFloatMap.put(id, value); 263 mIntegerMap.put(id, value); 264 updateListeners(id); 265 } 266 } 267 } 268 269 /** 270 * Adds a integer Override. 271 * 272 * @param id 273 * @param value the new value 274 */ overrideInteger(int id, int value)275 public void overrideInteger(int id, int value) { 276 int previous = mIntegerMap.get(id); 277 if (previous != value) { 278 mIntegerMap.put(id, value); 279 mFloatMap.put(id, value); 280 mIntegerOverride[id] = true; 281 updateListeners(id); 282 } 283 } 284 285 /** 286 * get a float from the float cache 287 * 288 * @param id of the float value 289 * @return the float value 290 */ getFloat(int id)291 public float getFloat(int id) { 292 return mFloatMap.get(id); 293 } 294 295 /** 296 * get an integer from the cache 297 * 298 * @param id of the integer value 299 * @return the integer 300 */ getInteger(int id)301 public int getInteger(int id) { 302 return mIntegerMap.get(id); 303 } 304 305 /** 306 * Get the color from the cache 307 * 308 * @param id The id of the color 309 * @return The color 310 */ getColor(int id)311 public int getColor(int id) { 312 return mColorMap.get(id); 313 } 314 315 /** 316 * Modify the color at id. 317 * 318 * @param id 319 * @param color 320 */ updateColor(int id, int color)321 public void updateColor(int id, int color) { 322 if (id < sMaxColors && mColorOverride[id]) { 323 return; 324 } 325 mColorMap.put(id, color); 326 updateListeners(id); 327 } 328 updateListeners(int id)329 private void updateListeners(int id) { 330 ArrayList<VariableSupport> v = mVarListeners.get(id); 331 if (v != null && mRemoteContext != null) { 332 for (int i = 0; i < v.size(); i++) { 333 VariableSupport c = v.get(i); 334 c.markDirty(); 335 } 336 } 337 } 338 339 /** 340 * Adds a colorOverride. This is a list of ids and their colors optimized for playback; 341 * 342 * @param id 343 * @param color 344 */ overrideColor(int id, int color)345 public void overrideColor(int id, int color) { 346 if (id >= sMaxColors) { 347 sMaxColors *= 2; 348 mColorOverride = Arrays.copyOf(mColorOverride, sMaxColors); 349 } 350 mColorOverride[id] = true; 351 mColorMap.put(id, color); 352 updateListeners(id); 353 } 354 355 /** Clear the color Overrides */ clearColorOverride()356 public void clearColorOverride() { 357 for (int i = 0; i < mColorOverride.length; i++) { 358 mColorOverride[i] = false; 359 } 360 } 361 362 /** 363 * Clear the data override 364 * 365 * @param id the data id to clear 366 */ clearDataOverride(int id)367 public void clearDataOverride(int id) { 368 mDataOverride[id] = false; 369 updateListeners(id); 370 } 371 372 /** 373 * Clear the integer override 374 * 375 * @param id the integer id to clear 376 */ clearIntegerOverride(int id)377 public void clearIntegerOverride(int id) { 378 mIntegerOverride[id] = false; 379 updateListeners(id); 380 } 381 382 /** 383 * Clear the float override 384 * 385 * @param id the float id to clear 386 */ clearFloatOverride(int id)387 public void clearFloatOverride(int id) { 388 mFloatOverride[id] = false; 389 updateListeners(id); 390 } 391 392 /** 393 * Method to determine if a cached value has been written to the documents WireBuffer based on 394 * its id. 395 * 396 * @param id id to check 397 * @return true if the value has not been written to the WireBuffer 398 */ wasNotWritten(int id)399 public boolean wasNotWritten(int id) { 400 return !mIntWrittenMap.get(id); 401 } 402 403 /** Method to mark that a value, represented by its id, has been written to the WireBuffer */ markWritten(int id)404 public void markWritten(int id) { 405 mIntWrittenMap.put(id, true); 406 } 407 408 /** Clear the record of the values that have been written to the WireBuffer. */ reset()409 public void reset() { 410 mIntWrittenMap.clear(); 411 mDataIntMap.clear(); 412 } 413 414 /** 415 * Get the next available id 416 * 417 * @return 418 */ nextId()419 public int nextId() { 420 return mNextId++; 421 } 422 423 /** 424 * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is 425 * collections 426 * 427 * @return return a unique id in the set 428 */ nextId(int type)429 public int nextId(int type) { 430 if (0 == type) { 431 return mNextId++; 432 } 433 return mIdMaps[type]++; 434 } 435 436 /** 437 * Set the next id 438 * 439 * @param id set the id to increment off of 440 */ setNextId(int id)441 public void setNextId(int id) { 442 mNextId = id; 443 } 444 445 @NonNull IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>(); 446 @NonNull ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>(); 447 add(int id, @NonNull VariableSupport variableSupport)448 private void add(int id, @NonNull VariableSupport variableSupport) { 449 ArrayList<VariableSupport> v = mVarListeners.get(id); 450 if (v == null) { 451 v = new ArrayList<VariableSupport>(); 452 mVarListeners.put(id, v); 453 } 454 v.add(variableSupport); 455 mAllVarListeners.add(variableSupport); 456 } 457 458 /** 459 * Commands that listen to variables add themselves. 460 * 461 * @param id id of variable to listen to 462 * @param variableSupport command that listens to variable 463 */ listenToVar(int id, @NonNull VariableSupport variableSupport)464 public void listenToVar(int id, @NonNull VariableSupport variableSupport) { 465 add(id, variableSupport); 466 } 467 468 /** 469 * Is any command listening to this variable 470 * 471 * @param id The Variable id 472 * @return true if any command is listening to this variable 473 */ hasListener(int id)474 public boolean hasListener(int id) { 475 return mVarListeners.get(id) != null; 476 } 477 478 /** 479 * List of Commands that need to be updated 480 * 481 * @param context The context 482 * @return The number of ops to update 483 */ getOpsToUpdate(@onNull RemoteContext context)484 public int getOpsToUpdate(@NonNull RemoteContext context) { 485 if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) { 486 return 1; 487 } 488 if (mVarListeners.get(RemoteContext.ID_TIME_IN_SEC) != null) { 489 return 1000; 490 } 491 if (mVarListeners.get(RemoteContext.ID_TIME_IN_MIN) != null) { 492 return 1000 * 60; 493 } 494 return -1; 495 } 496 497 /** 498 * Set the width of the overall document on screen. 499 * 500 * @param width the width of the document in pixels 501 */ setWindowWidth(float width)502 public void setWindowWidth(float width) { 503 updateFloat(RemoteContext.ID_WINDOW_WIDTH, width); 504 } 505 506 /** 507 * Set the width of the overall document on screen. 508 * 509 * @param height the height of the document in pixels 510 */ setWindowHeight(float height)511 public void setWindowHeight(float height) { 512 updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height); 513 } 514 515 /** 516 * Add an array access 517 * 518 * @param id The id of the array Access 519 * @param collection The array access 520 */ addCollection(int id, @NonNull ArrayAccess collection)521 public void addCollection(int id, @NonNull ArrayAccess collection) { 522 mCollectionMap.put(id & 0xFFFFF, collection); 523 } 524 525 @Override getFloatValue(int id, int index)526 public float getFloatValue(int id, int index) { 527 return mCollectionMap.get(id & 0xFFFFF).getFloatValue(index); // TODO: potential npe 528 } 529 530 @Override getFloats(int id)531 public @Nullable float[] getFloats(int id) { 532 return mCollectionMap.get(id & 0xFFFFF).getFloats(); // TODO: potential npe 533 } 534 535 @Override getId(int id, int index)536 public int getId(int id, int index) { 537 return mCollectionMap.get(id & 0xFFFFF).getId(index); 538 } 539 540 /** 541 * adds a DataMap to the cache 542 * 543 * @param id The id of the data map 544 * @param map The data map 545 */ putDataMap(int id, @NonNull DataMap map)546 public void putDataMap(int id, @NonNull DataMap map) { 547 mDataMapMap.put(id, map); 548 } 549 550 /** 551 * Get the DataMap asociated with the id 552 * 553 * @param id the id of the DataMap 554 * @return the DataMap 555 */ getDataMap(int id)556 public @Nullable DataMap getDataMap(int id) { 557 return mDataMapMap.get(id); 558 } 559 560 @Override getListLength(int id)561 public int getListLength(int id) { 562 return mCollectionMap.get(id & 0xFFFFF).getLength(); 563 } 564 565 /** 566 * sets the RemoteContext 567 * 568 * @param context the context 569 */ setContext(@onNull RemoteContext context)570 public void setContext(@NonNull RemoteContext context) { 571 mRemoteContext = context; 572 mRemoteContext.clearLastOpCount(); 573 } 574 575 /** 576 * Add an object to the cache. Uses the id for the item and adds it to the cache based 577 * 578 * @param id the id of the object 579 * @param value the object 580 */ updateObject(int id, @NonNull Object value)581 public void updateObject(int id, @NonNull Object value) { 582 mObjectMap.put(id, value); 583 } 584 585 /** 586 * Get an object from the cache 587 * 588 * @param id The id of the object 589 * @return The object 590 */ getObject(int id)591 public @Nullable Object getObject(int id) { 592 return mObjectMap.get(id); 593 } 594 } 595