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