1 /* 2 * Copyright (C) 2020 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 17 package com.android.server.vcn.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.ParcelUuid; 22 import android.os.PersistableBundle; 23 24 import com.android.internal.util.HexDump; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.LinkedHashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Map.Entry; 38 import java.util.Objects; 39 import java.util.TreeSet; 40 import java.util.concurrent.locks.ReadWriteLock; 41 import java.util.concurrent.locks.ReentrantReadWriteLock; 42 43 /** @hide */ 44 public class PersistableBundleUtils { 45 private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d"; 46 private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH"; 47 private static final String MAP_KEY_FORMAT = "MAP_KEY_%d"; 48 private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d"; 49 50 private static final String PARCEL_UUID_KEY = "PARCEL_UUID"; 51 private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY"; 52 private static final String INTEGER_KEY = "INTEGER_KEY"; 53 private static final String STRING_KEY = "STRING_KEY"; 54 55 /** 56 * Functional interface to convert an object of the specified type to a PersistableBundle. 57 * 58 * @param <T> the type of the source object 59 */ 60 public interface Serializer<T> { 61 /** 62 * Converts this object to a PersistableBundle. 63 * 64 * @return the PersistableBundle representation of this object 65 */ toPersistableBundle(T obj)66 PersistableBundle toPersistableBundle(T obj); 67 } 68 69 /** 70 * Functional interface used to create an object of the specified type from a PersistableBundle. 71 * 72 * @param <T> the type of the resultant object 73 */ 74 public interface Deserializer<T> { 75 /** 76 * Creates an instance of specified type from a PersistableBundle representation. 77 * 78 * @param in the PersistableBundle representation 79 * @return an instance of the specified type 80 */ fromPersistableBundle(PersistableBundle in)81 T fromPersistableBundle(PersistableBundle in); 82 } 83 84 /** Serializer to convert an integer to a PersistableBundle. */ 85 public static final Serializer<Integer> INTEGER_SERIALIZER = 86 (i) -> { 87 final PersistableBundle result = new PersistableBundle(); 88 result.putInt(INTEGER_KEY, i); 89 return result; 90 }; 91 92 /** Deserializer to convert a PersistableBundle to an integer. */ 93 public static final Deserializer<Integer> INTEGER_DESERIALIZER = 94 (bundle) -> { 95 Objects.requireNonNull(bundle, "PersistableBundle is null"); 96 return bundle.getInt(INTEGER_KEY); 97 }; 98 99 /** Serializer to convert s String to a PersistableBundle. */ 100 public static final Serializer<String> STRING_SERIALIZER = 101 (i) -> { 102 final PersistableBundle result = new PersistableBundle(); 103 result.putString(STRING_KEY, i); 104 return result; 105 }; 106 107 /** Deserializer to convert a PersistableBundle to a String. */ 108 public static final Deserializer<String> STRING_DESERIALIZER = 109 (bundle) -> { 110 Objects.requireNonNull(bundle, "PersistableBundle is null"); 111 return bundle.getString(STRING_KEY); 112 }; 113 114 /** 115 * Converts a ParcelUuid to a PersistableBundle. 116 * 117 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 118 * PersistableBundle object. 119 * 120 * @param uuid a ParcelUuid instance to persist 121 * @return the PersistableBundle instance 122 */ fromParcelUuid(ParcelUuid uuid)123 public static PersistableBundle fromParcelUuid(ParcelUuid uuid) { 124 final PersistableBundle result = new PersistableBundle(); 125 126 result.putString(PARCEL_UUID_KEY, uuid.toString()); 127 128 return result; 129 } 130 131 /** 132 * Converts from a PersistableBundle to a ParcelUuid. 133 * 134 * @param bundle the PersistableBundle containing the ParcelUuid 135 * @return the ParcelUuid instance 136 */ toParcelUuid(PersistableBundle bundle)137 public static ParcelUuid toParcelUuid(PersistableBundle bundle) { 138 return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY)); 139 } 140 141 /** 142 * Converts from a list of Persistable objects to a single PersistableBundle. 143 * 144 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 145 * PersistableBundle object. 146 * 147 * @param <T> the type of the objects to convert to the PersistableBundle 148 * @param in the list of objects to be serialized into a PersistableBundle 149 * @param serializer an implementation of the {@link Serializer} functional interface that 150 * converts an object of type T to a PersistableBundle 151 */ 152 @NonNull fromList( @onNull List<T> in, @NonNull Serializer<T> serializer)153 public static <T> PersistableBundle fromList( 154 @NonNull List<T> in, @NonNull Serializer<T> serializer) { 155 final PersistableBundle result = new PersistableBundle(); 156 157 result.putInt(COLLECTION_SIZE_KEY, in.size()); 158 for (int i = 0; i < in.size(); i++) { 159 final String key = String.format(LIST_KEY_FORMAT, i); 160 result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i))); 161 } 162 return result; 163 } 164 165 /** 166 * Converts from a PersistableBundle to a list of objects. 167 * 168 * @param <T> the type of the objects to convert from a PersistableBundle 169 * @param in the PersistableBundle containing the persisted list 170 * @param deserializer an implementation of the {@link Deserializer} functional interface that 171 * builds the relevant type of objects. 172 */ 173 @NonNull toList( @onNull PersistableBundle in, @NonNull Deserializer<T> deserializer)174 public static <T> List<T> toList( 175 @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) { 176 final int listLength = in.getInt(COLLECTION_SIZE_KEY); 177 final ArrayList<T> result = new ArrayList<>(listLength); 178 179 for (int i = 0; i < listLength; i++) { 180 final String key = String.format(LIST_KEY_FORMAT, i); 181 final PersistableBundle item = in.getPersistableBundle(key); 182 183 result.add(deserializer.fromPersistableBundle(item)); 184 } 185 return result; 186 } 187 188 // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and 189 // BaseBundle#getByteArray are exposed. 190 191 /** 192 * Converts a byte array to a PersistableBundle. 193 * 194 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 195 * PersistableBundle object. 196 * 197 * @param array a byte array instance to persist 198 * @return the PersistableBundle instance 199 */ fromByteArray(byte[] array)200 public static PersistableBundle fromByteArray(byte[] array) { 201 final PersistableBundle result = new PersistableBundle(); 202 203 result.putString(BYTE_ARRAY_KEY, HexDump.toHexString(array)); 204 205 return result; 206 } 207 208 /** 209 * Converts from a PersistableBundle to a byte array. 210 * 211 * @param bundle the PersistableBundle containing the byte array 212 * @return the byte array instance 213 */ toByteArray(PersistableBundle bundle)214 public static byte[] toByteArray(PersistableBundle bundle) { 215 Objects.requireNonNull(bundle, "PersistableBundle is null"); 216 217 String hex = bundle.getString(BYTE_ARRAY_KEY); 218 if (hex == null || hex.length() % 2 != 0) { 219 throw new IllegalArgumentException("PersistableBundle contains invalid byte array"); 220 } 221 222 return HexDump.hexStringToByteArray(hex); 223 } 224 225 /** 226 * Converts from a Map of Persistable objects to a single PersistableBundle. 227 * 228 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 229 * PersistableBundle object. 230 * 231 * @param <K> the type of the map-key to convert to the PersistableBundle 232 * @param <V> the type of the map-value to convert to the PersistableBundle 233 * @param in the Map of objects implementing the {@link Persistable} interface 234 * @param keySerializer an implementation of the {@link Serializer} functional interface that 235 * converts a map-key of type T to a PersistableBundle 236 * @param valueSerializer an implementation of the {@link Serializer} functional interface that 237 * converts a map-value of type E to a PersistableBundle 238 */ 239 @NonNull fromMap( @onNull Map<K, V> in, @NonNull Serializer<K> keySerializer, @NonNull Serializer<V> valueSerializer)240 public static <K, V> PersistableBundle fromMap( 241 @NonNull Map<K, V> in, 242 @NonNull Serializer<K> keySerializer, 243 @NonNull Serializer<V> valueSerializer) { 244 final PersistableBundle result = new PersistableBundle(); 245 246 result.putInt(COLLECTION_SIZE_KEY, in.size()); 247 int i = 0; 248 for (Entry<K, V> entry : in.entrySet()) { 249 final String keyKey = String.format(MAP_KEY_FORMAT, i); 250 final String valueKey = String.format(MAP_VALUE_FORMAT, i); 251 result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey())); 252 result.putPersistableBundle( 253 valueKey, valueSerializer.toPersistableBundle(entry.getValue())); 254 255 i++; 256 } 257 258 return result; 259 } 260 261 /** 262 * Converts from a PersistableBundle to a Map of objects. 263 * 264 * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the 265 * guarantees on the ordering can only ever be as strong as the map that was serialized in 266 * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the 267 * deserialized map similarly may be of a non-deterministic order. 268 * 269 * @param <K> the type of the map-key to convert from a PersistableBundle 270 * @param <V> the type of the map-value to convert from a PersistableBundle 271 * @param in the PersistableBundle containing the persisted Map 272 * @param keyDeserializer an implementation of the {@link Deserializer} functional interface 273 * that builds the relevant type of map-key. 274 * @param valueDeserializer an implementation of the {@link Deserializer} functional interface 275 * that builds the relevant type of map-value. 276 * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve 277 * ordering). 278 */ 279 @NonNull toMap( @onNull PersistableBundle in, @NonNull Deserializer<K> keyDeserializer, @NonNull Deserializer<V> valueDeserializer)280 public static <K, V> LinkedHashMap<K, V> toMap( 281 @NonNull PersistableBundle in, 282 @NonNull Deserializer<K> keyDeserializer, 283 @NonNull Deserializer<V> valueDeserializer) { 284 final int mapSize = in.getInt(COLLECTION_SIZE_KEY); 285 final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize); 286 287 for (int i = 0; i < mapSize; i++) { 288 final String keyKey = String.format(MAP_KEY_FORMAT, i); 289 final String valueKey = String.format(MAP_VALUE_FORMAT, i); 290 final PersistableBundle keyBundle = in.getPersistableBundle(keyKey); 291 final PersistableBundle valueBundle = in.getPersistableBundle(valueKey); 292 293 final K key = keyDeserializer.fromPersistableBundle(keyBundle); 294 final V value = valueDeserializer.fromPersistableBundle(valueBundle); 295 result.put(key, value); 296 } 297 return result; 298 } 299 300 /** 301 * Converts a PersistableBundle into a disk-stable byte array format 302 * 303 * @param bundle the PersistableBundle to be converted to a disk-stable format 304 * @return the byte array representation of the PersistableBundle 305 */ 306 @Nullable toDiskStableBytes(@onNull PersistableBundle bundle)307 public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException { 308 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 309 bundle.writeToStream(outputStream); 310 return outputStream.toByteArray(); 311 } 312 313 /** 314 * Converts from a disk-stable byte array format to a PersistableBundle 315 * 316 * @param bytes the disk-stable byte array 317 * @return the PersistableBundle parsed from this byte array. 318 */ fromDiskStableBytes(@onNull byte[] bytes)319 public static PersistableBundle fromDiskStableBytes(@NonNull byte[] bytes) throws IOException { 320 final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); 321 return PersistableBundle.readFromStream(inputStream); 322 } 323 324 /** 325 * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk. 326 * 327 * <p>This class will enforce exclusion between reads and writes using the standard semantics of 328 * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the 329 * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states 330 * are n readers, OR 1 writer (but not both). 331 */ 332 public static class LockingReadWriteHelper { 333 private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock(); 334 private final String mPath; 335 LockingReadWriteHelper(@onNull String path)336 public LockingReadWriteHelper(@NonNull String path) { 337 mPath = Objects.requireNonNull(path, "fileName was null"); 338 } 339 340 /** 341 * Reads the {@link PersistableBundle} from the disk. 342 * 343 * @return the PersistableBundle, if the file existed, or null otherwise 344 */ 345 @Nullable readFromDisk()346 public PersistableBundle readFromDisk() throws IOException { 347 try { 348 mDiskLock.readLock().lock(); 349 final File file = new File(mPath); 350 if (!file.exists()) { 351 return null; 352 } 353 354 try (FileInputStream fis = new FileInputStream(file)) { 355 return PersistableBundle.readFromStream(fis); 356 } 357 } finally { 358 mDiskLock.readLock().unlock(); 359 } 360 } 361 362 /** 363 * Writes a {@link PersistableBundle} to disk. 364 * 365 * @param bundle the {@link PersistableBundle} to write to disk 366 */ writeToDisk(@onNull PersistableBundle bundle)367 public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException { 368 Objects.requireNonNull(bundle, "bundle was null"); 369 370 try { 371 mDiskLock.writeLock().lock(); 372 final File file = new File(mPath); 373 if (!file.exists()) { 374 file.getParentFile().mkdirs(); 375 } 376 377 try (FileOutputStream fos = new FileOutputStream(file)) { 378 bundle.writeToStream(fos); 379 } 380 } finally { 381 mDiskLock.writeLock().unlock(); 382 } 383 } 384 } 385 386 /** 387 * Returns a copy of the persistable bundle with only the specified keys 388 * 389 * <p>This allows for holding minimized copies for memory-saving purposes. 390 */ 391 @NonNull minimizeBundle( @onNull PersistableBundle bundle, String... keys)392 public static PersistableBundle minimizeBundle( 393 @NonNull PersistableBundle bundle, String... keys) { 394 final PersistableBundle minimized = new PersistableBundle(); 395 396 if (bundle == null) { 397 return minimized; 398 } 399 400 for (String key : keys) { 401 if (bundle.containsKey(key)) { 402 final Object value = bundle.get(key); 403 if (value == null) { 404 continue; 405 } 406 407 if (value instanceof Boolean) { 408 minimized.putBoolean(key, (Boolean) value); 409 } else if (value instanceof boolean[]) { 410 minimized.putBooleanArray(key, (boolean[]) value); 411 } else if (value instanceof Double) { 412 minimized.putDouble(key, (Double) value); 413 } else if (value instanceof double[]) { 414 minimized.putDoubleArray(key, (double[]) value); 415 } else if (value instanceof Integer) { 416 minimized.putInt(key, (Integer) value); 417 } else if (value instanceof int[]) { 418 minimized.putIntArray(key, (int[]) value); 419 } else if (value instanceof Long) { 420 minimized.putLong(key, (Long) value); 421 } else if (value instanceof long[]) { 422 minimized.putLongArray(key, (long[]) value); 423 } else if (value instanceof String) { 424 minimized.putString(key, (String) value); 425 } else if (value instanceof String[]) { 426 minimized.putStringArray(key, (String[]) value); 427 } else if (value instanceof PersistableBundle) { 428 minimized.putPersistableBundle(key, (PersistableBundle) value); 429 } else { 430 continue; 431 } 432 } 433 } 434 435 return minimized; 436 } 437 438 /** Builds a stable hashcode */ getHashCode(@ullable PersistableBundle bundle)439 public static int getHashCode(@Nullable PersistableBundle bundle) { 440 if (bundle == null) { 441 return -1; 442 } 443 444 int iterativeHashcode = 0; 445 TreeSet<String> treeSet = new TreeSet<>(bundle.keySet()); 446 for (String key : treeSet) { 447 Object val = bundle.get(key); 448 if (val instanceof PersistableBundle) { 449 iterativeHashcode = 450 Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val)); 451 } else { 452 iterativeHashcode = Objects.hash(iterativeHashcode, key, val); 453 } 454 } 455 456 return iterativeHashcode; 457 } 458 459 /** Checks for persistable bundle equality */ isEqual( @ullable PersistableBundle left, @Nullable PersistableBundle right)460 public static boolean isEqual( 461 @Nullable PersistableBundle left, @Nullable PersistableBundle right) { 462 // Check for pointer equality & null equality 463 if (Objects.equals(left, right)) { 464 return true; 465 } 466 467 // If only one of the two is null, but not the other, not equal by definition. 468 if (Objects.isNull(left) != Objects.isNull(right)) { 469 return false; 470 } 471 472 if (!left.keySet().equals(right.keySet())) { 473 return false; 474 } 475 476 for (String key : left.keySet()) { 477 Object leftVal = left.get(key); 478 Object rightVal = right.get(key); 479 480 // Check for equality 481 if (Objects.equals(leftVal, rightVal)) { 482 continue; 483 } else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) { 484 // If only one of the two is null, but not the other, not equal by definition. 485 return false; 486 } else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) { 487 // If classes are different, not equal by definition. 488 return false; 489 } 490 if (leftVal instanceof PersistableBundle) { 491 if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) { 492 return false; 493 } 494 } else if (leftVal.getClass().isArray()) { 495 if (leftVal instanceof boolean[]) { 496 if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) { 497 return false; 498 } 499 } else if (leftVal instanceof double[]) { 500 if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) { 501 return false; 502 } 503 } else if (leftVal instanceof int[]) { 504 if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) { 505 return false; 506 } 507 } else if (leftVal instanceof long[]) { 508 if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) { 509 return false; 510 } 511 } else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) { 512 return false; 513 } 514 } else { 515 if (!Objects.equals(leftVal, rightVal)) { 516 return false; 517 } 518 } 519 } 520 521 return true; 522 } 523 524 /** 525 * Wrapper class around PersistableBundles to allow equality comparisons 526 * 527 * <p>This class exposes the minimal getters to retrieve values. 528 */ 529 public static class PersistableBundleWrapper { 530 @NonNull private final PersistableBundle mBundle; 531 PersistableBundleWrapper(@onNull PersistableBundle bundle)532 public PersistableBundleWrapper(@NonNull PersistableBundle bundle) { 533 mBundle = Objects.requireNonNull(bundle, "Bundle was null"); 534 } 535 536 /** 537 * Retrieves the integer associated with the provided key. 538 * 539 * @param key the string key to query 540 * @param defaultValue the value to return if key does not exist 541 * @return the int value, or the default 542 */ getInt(String key, int defaultValue)543 public int getInt(String key, int defaultValue) { 544 return mBundle.getInt(key, defaultValue); 545 } 546 547 /** 548 * Returns the value associated with the given key, or null if no mapping of the desired 549 * type exists for the given key or a null value is explicitly associated with the key. 550 * 551 * @param key a String, or null 552 * @param defaultValue the value to return if key does not exist 553 * @return an int[] value, or null 554 */ 555 @Nullable getIntArray(@ullable String key, @Nullable int[] defaultValue)556 public int[] getIntArray(@Nullable String key, @Nullable int[] defaultValue) { 557 final int[] value = mBundle.getIntArray(key); 558 return value == null ? defaultValue : value; 559 } 560 561 @Override hashCode()562 public int hashCode() { 563 return getHashCode(mBundle); 564 } 565 566 @Override equals(Object obj)567 public boolean equals(Object obj) { 568 if (!(obj instanceof PersistableBundleWrapper)) { 569 return false; 570 } 571 572 final PersistableBundleWrapper other = (PersistableBundleWrapper) obj; 573 574 return isEqual(mBundle, other.mBundle); 575 } 576 577 @Override toString()578 public String toString() { 579 return mBundle.toString(); 580 } 581 } 582 } 583