blob: d81e719866aa65431661d28d48be28cc3fe61a42 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.uwb.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** @hide */
public class PersistableBundleUtils {
// private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d";
// private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH";
// private static final String MAP_KEY_FORMAT = "MAP_KEY_%d";
// private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d";
//
// private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
// private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY";
// private static final String INTEGER_KEY = "INTEGER_KEY";
// private static final String STRING_KEY = "STRING_KEY";
//
// private final static char[] HEX_DIGITS =
// {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
// private final static char[] HEX_LOWER_CASE_DIGITS =
// {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
// /**
// * Functional interface to convert an object of the specified type to a PersistableBundle.
// *
// * @param <T> the type of the source object
// */
// public interface Serializer<T> {
// /**
// * Converts this object to a PersistableBundle.
// *
// * @return the PersistableBundle representation of this object
// */
// PersistableBundle toPersistableBundle(T obj);
// }
//
// /**
// * Functional interface used to create an object of the specified type from a PersistableBundle.
// *
// * @param <T> the type of the resultant object
// */
// public interface Deserializer<T> {
// /**
// * Creates an instance of specified type from a PersistableBundle representation.
// *
// * @param in the PersistableBundle representation
// * @return an instance of the specified type
// */
// T fromPersistableBundle(PersistableBundle in);
// }
//
// /** Serializer to convert an integer to a PersistableBundle. */
// public static final Serializer<Integer> INTEGER_SERIALIZER =
// (i) -> {
// final PersistableBundle result = new PersistableBundle();
// result.putInt(INTEGER_KEY, i);
// return result;
// };
//
// /** Deserializer to convert a PersistableBundle to an integer. */
// public static final Deserializer<Integer> INTEGER_DESERIALIZER =
// (bundle) -> {
// Objects.requireNonNull(bundle, "PersistableBundle is null");
// return bundle.getInt(INTEGER_KEY);
// };
//
// /** Serializer to convert s String to a PersistableBundle. */
// public static final Serializer<String> STRING_SERIALIZER =
// (i) -> {
// final PersistableBundle result = new PersistableBundle();
// result.putString(STRING_KEY, i);
// return result;
// };
//
// /** Deserializer to convert a PersistableBundle to a String. */
// public static final Deserializer<String> STRING_DESERIALIZER =
// (bundle) -> {
// Objects.requireNonNull(bundle, "PersistableBundle is null");
// return bundle.getString(STRING_KEY);
// };
//
// /**
// * Converts a ParcelUuid to a PersistableBundle.
// *
// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
// * PersistableBundle object.
// *
// * @param uuid a ParcelUuid instance to persist
// * @return the PersistableBundle instance
// */
// public static PersistableBundle fromParcelUuid(ParcelUuid uuid) {
// final PersistableBundle result = new PersistableBundle();
//
// result.putString(PARCEL_UUID_KEY, uuid.toString());
//
// return result;
// }
//
// /**
// * Converts from a PersistableBundle to a ParcelUuid.
// *
// * @param bundle the PersistableBundle containing the ParcelUuid
// * @return the ParcelUuid instance
// */
// public static ParcelUuid toParcelUuid(PersistableBundle bundle) {
// return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY));
// }
//
// /**
// * Converts from a list of Persistable objects to a single PersistableBundle.
// *
// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
// * PersistableBundle object.
// *
// * @param <T> the type of the objects to convert to the PersistableBundle
// * @param in the list of objects to be serialized into a PersistableBundle
// * @param serializer an implementation of the {@link Serializer} functional interface that
// * converts an object of type T to a PersistableBundle
// */
// @NonNull
// public static <T> PersistableBundle fromList(
// @NonNull List<T> in, @NonNull Serializer<T> serializer) {
// final PersistableBundle result = new PersistableBundle();
//
// result.putInt(COLLECTION_SIZE_KEY, in.size());
// for (int i = 0; i < in.size(); i++) {
// final String key = String.format(LIST_KEY_FORMAT, i);
// result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i)));
// }
// return result;
// }
//
// /**
// * Converts from a PersistableBundle to a list of objects.
// *
// * @param <T> the type of the objects to convert from a PersistableBundle
// * @param in the PersistableBundle containing the persisted list
// * @param deserializer an implementation of the {@link Deserializer} functional interface that
// * builds the relevant type of objects.
// */
// @NonNull
// public static <T> List<T> toList(
// @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) {
// final int listLength = in.getInt(COLLECTION_SIZE_KEY);
// final ArrayList<T> result = new ArrayList<>(listLength);
//
// for (int i = 0; i < listLength; i++) {
// final String key = String.format(LIST_KEY_FORMAT, i);
// final PersistableBundle item = in.getPersistableBundle(key);
//
// result.add(deserializer.fromPersistableBundle(item));
// }
// return result;
// }
//
// // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and
// // BaseBundle#getByteArray are exposed.
//
// /**
// * Converts a byte array to a PersistableBundle.
// *
// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
// * PersistableBundle object.
// *
// * @param array a byte array instance to persist
// * @return the PersistableBundle instance
// */
// public static PersistableBundle fromByteArray(byte[] array) {
// final PersistableBundle result = new PersistableBundle();
//
// result.putString(BYTE_ARRAY_KEY, toHexString(array));
//
// return result;
// }
//
// // Copied from com.android.internal.util.HexDump
// @UnsupportedAppUsage
// public static String toHexString(byte[] array, int offset, int length) {
// return toHexString(array, offset, length, true);
// }
//
// public static String toHexString(byte[] array, int offset, int length, boolean upperCase) {
// char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
// char[] buf = new char[length * 2];
//
// int bufIndex = 0;
// for (int i = offset; i < offset + length; i++) {
// byte b = array[i];
// buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
// buf[bufIndex++] = digits[b & 0x0F];
// }
//
// return new String(buf);
// }
//
// @UnsupportedAppUsage
// public static String toHexString(byte[] array) {
// return toHexString(array, 0, array.length, true);
// }
//
// @UnsupportedAppUsage
// public static String toHexString(int i) {
// return toHexString(toByteArray(i));
// }
//
// public static byte[] toByteArray(int i) {
// byte[] array = new byte[4];
//
// array[3] = (byte) (i & 0xFF);
// array[2] = (byte) ((i >> 8) & 0xFF);
// array[1] = (byte) ((i >> 16) & 0xFF);
// array[0] = (byte) ((i >> 24) & 0xFF);
//
// return array;
// }
//
// @UnsupportedAppUsage
// public static byte[] hexStringToByteArray(String hexString) {
// int length = hexString.length();
// byte[] buffer = new byte[length / 2];
//
// for (int i = 0; i < length; i += 2) {
// buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(
// hexString.charAt(i + 1)));
// }
//
// return buffer;
// }
//
// private static int toByte(char c) {
// if (c >= '0' && c <= '9') return (c - '0');
// if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
// if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
//
// throw new RuntimeException("Invalid hex char '" + c + "'");
// }
//
// /**
// * Converts from a PersistableBundle to a byte array.
// *
// * @param bundle the PersistableBundle containing the byte array
// * @return the byte array instance
// */
// public static byte[] toByteArray(PersistableBundle bundle) {
// Objects.requireNonNull(bundle, "PersistableBundle is null");
//
// String hex = bundle.getString(BYTE_ARRAY_KEY);
// if (hex == null || hex.length() % 2 != 0) {
// throw new IllegalArgumentException("PersistableBundle contains invalid byte array");
// }
//
// return hexStringToByteArray(hex);
// }
//
// /**
// * Converts from a Map of Persistable objects to a single PersistableBundle.
// *
// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
// * PersistableBundle object.
// *
// * @param <K> the type of the map-key to convert to the PersistableBundle
// * @param <V> the type of the map-value to convert to the PersistableBundle
// * @param in the Map of objects implementing the {@link Persistable} interface
// * @param keySerializer an implementation of the {@link Serializer} functional interface that
// * converts a map-key of type T to a PersistableBundle
// * @param valueSerializer an implementation of the {@link Serializer} functional interface that
// * converts a map-value of type E to a PersistableBundle
// */
// @NonNull
// public static <K, V> PersistableBundle fromMap(
// @NonNull Map<K, V> in,
// @NonNull Serializer<K> keySerializer,
// @NonNull Serializer<V> valueSerializer) {
// final PersistableBundle result = new PersistableBundle();
//
// result.putInt(COLLECTION_SIZE_KEY, in.size());
// int i = 0;
// for (Entry<K, V> entry : in.entrySet()) {
// final String keyKey = String.format(MAP_KEY_FORMAT, i);
// final String valueKey = String.format(MAP_VALUE_FORMAT, i);
// result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey()));
// result.putPersistableBundle(
// valueKey, valueSerializer.toPersistableBundle(entry.getValue()));
//
// i++;
// }
//
// return result;
// }
//
// /**
// * Converts from a PersistableBundle to a Map of objects.
// *
// * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the
// * guarantees on the ordering can only ever be as strong as the map that was serialized in
// * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the
// * deserialized map similarly may be of a non-deterministic order.
// *
// * @param <K> the type of the map-key to convert from a PersistableBundle
// * @param <V> the type of the map-value to convert from a PersistableBundle
// * @param in the PersistableBundle containing the persisted Map
// * @param keyDeserializer an implementation of the {@link Deserializer} functional interface
// * that builds the relevant type of map-key.
// * @param valueDeserializer an implementation of the {@link Deserializer} functional interface
// * that builds the relevant type of map-value.
// * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve
// * ordering).
// */
// @NonNull
// public static <K, V> LinkedHashMap<K, V> toMap(
// @NonNull PersistableBundle in,
// @NonNull Deserializer<K> keyDeserializer,
// @NonNull Deserializer<V> valueDeserializer) {
// final int mapSize = in.getInt(COLLECTION_SIZE_KEY);
// final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize);
//
// for (int i = 0; i < mapSize; i++) {
// final String keyKey = String.format(MAP_KEY_FORMAT, i);
// final String valueKey = String.format(MAP_VALUE_FORMAT, i);
// final PersistableBundle keyBundle = in.getPersistableBundle(keyKey);
// final PersistableBundle valueBundle = in.getPersistableBundle(valueKey);
//
// final K key = keyDeserializer.fromPersistableBundle(keyBundle);
// final V value = valueDeserializer.fromPersistableBundle(valueBundle);
// result.put(key, value);
// }
// return result;
// }
//
// /**
// * Converts a PersistableBundle into a disk-stable byte array format
// *
// * @param bundle the PersistableBundle to be converted to a disk-stable format
// * @return the byte array representation of the PersistableBundle
// */
// @Nullable
// public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException {
// final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// bundle.writeToStream(outputStream);
// return outputStream.toByteArray();
// }
//
// /**
// * Converts from a disk-stable byte array format to a PersistableBundle
// *
// * @param bytes the disk-stable byte array
// * @return the PersistableBundle parsed from this byte array.
// */
// public static PersistableBundle fromDiskStableBytes(@NonNull byte[] bytes) throws IOException {
// final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
// return PersistableBundle.readFromStream(inputStream);
// }
//
// /**
// * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk.
// *
// * <p>This class will enforce exclusion between reads and writes using the standard semantics of
// * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the
// * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states
// * are n readers, OR 1 writer (but not both).
// */
// public static class LockingReadWriteHelper {
// private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock();
// private final String mPath;
//
// public LockingReadWriteHelper(@NonNull String path) {
// mPath = Objects.requireNonNull(path, "fileName was null");
// }
//
// /**
// * Reads the {@link PersistableBundle} from the disk.
// *
// * @return the PersistableBundle, if the file existed, or null otherwise
// */
// @Nullable
// public PersistableBundle readFromDisk() throws IOException {
// try {
// mDiskLock.readLock().lock();
// final File file = new File(mPath);
// if (!file.exists()) {
// return null;
// }
//
// try (FileInputStream fis = new FileInputStream(file)) {
// return PersistableBundle.readFromStream(fis);
// }
// } finally {
// mDiskLock.readLock().unlock();
// }
// }
//
// /**
// * Writes a {@link PersistableBundle} to disk.
// *
// * @param bundle the {@link PersistableBundle} to write to disk
// */
// public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException {
// Objects.requireNonNull(bundle, "bundle was null");
//
// try {
// mDiskLock.writeLock().lock();
// final File file = new File(mPath);
// if (!file.exists()) {
// file.getParentFile().mkdirs();
// }
//
// try (FileOutputStream fos = new FileOutputStream(file)) {
// bundle.writeToStream(fos);
// }
// } finally {
// mDiskLock.writeLock().unlock();
// }
// }
// }
//
// /**
// * Returns a copy of the persistable bundle with only the specified keys
// *
// * <p>This allows for holding minimized copies for memory-saving purposes.
// */
// @NonNull
// public static PersistableBundle minimizeBundle(
// @NonNull PersistableBundle bundle, String... keys) {
// final PersistableBundle minimized = new PersistableBundle();
//
// if (bundle == null) {
// return minimized;
// }
//
// for (String key : keys) {
// if (bundle.containsKey(key)) {
// final Object value = bundle.get(key);
// if (value == null) {
// continue;
// }
//
// if (value instanceof Boolean) {
// minimized.putBoolean(key, (Boolean) value);
// } else if (value instanceof boolean[]) {
// minimized.putBooleanArray(key, (boolean[]) value);
// } else if (value instanceof Double) {
// minimized.putDouble(key, (Double) value);
// } else if (value instanceof double[]) {
// minimized.putDoubleArray(key, (double[]) value);
// } else if (value instanceof Integer) {
// minimized.putInt(key, (Integer) value);
// } else if (value instanceof int[]) {
// minimized.putIntArray(key, (int[]) value);
// } else if (value instanceof Long) {
// minimized.putLong(key, (Long) value);
// } else if (value instanceof long[]) {
// minimized.putLongArray(key, (long[]) value);
// } else if (value instanceof String) {
// minimized.putString(key, (String) value);
// } else if (value instanceof String[]) {
// minimized.putStringArray(key, (String[]) value);
// } else if (value instanceof PersistableBundle) {
// minimized.putPersistableBundle(key, (PersistableBundle) value);
// } else {
// continue;
// }
// }
// }
//
// return minimized;
// }
/** Builds a stable hashcode */
public static int getHashCode(@Nullable PersistableBundle bundle) {
if (bundle == null) {
return -1;
}
int iterativeHashcode = 0;
TreeSet<String> treeSet = new TreeSet<>(bundle.keySet());
for (String key : treeSet) {
Object val = bundle.get(key);
if (val instanceof PersistableBundle) {
iterativeHashcode =
Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val));
} else {
iterativeHashcode = Objects.hash(iterativeHashcode, key, val);
}
}
return iterativeHashcode;
}
/** Checks for persistable bundle equality */
public static boolean isEqual(
@Nullable PersistableBundle left, @Nullable PersistableBundle right) {
// Check for pointer equality & null equality
if (Objects.equals(left, right)) {
return true;
}
// If only one of the two is null, but not the other, not equal by definition.
if (Objects.isNull(left) != Objects.isNull(right)) {
return false;
}
if (!left.keySet().equals(right.keySet())) {
return false;
}
for (String key : left.keySet()) {
Object leftVal = left.get(key);
Object rightVal = right.get(key);
// Check for equality
if (Objects.equals(leftVal, rightVal)) {
continue;
} else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) {
// If only one of the two is null, but not the other, not equal by definition.
return false;
} else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) {
// If classes are different, not equal by definition.
return false;
}
if (leftVal instanceof PersistableBundle) {
if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) {
return false;
}
} else if (leftVal.getClass().isArray()) {
if (leftVal instanceof boolean[]) {
if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) {
return false;
}
} else if (leftVal instanceof double[]) {
if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) {
return false;
}
} else if (leftVal instanceof int[]) {
if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) {
return false;
}
} else if (leftVal instanceof long[]) {
if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) {
return false;
}
} else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) {
return false;
}
} else {
if (!Objects.equals(leftVal, rightVal)) {
return false;
}
}
}
return true;
}
// /**
// * Wrapper class around PersistableBundles to allow equality comparisons
// *
// * <p>This class exposes the minimal getters to retrieve values.
// */
// public static class PersistableBundleWrapper {
// @NonNull
// private final PersistableBundle mBundle;
//
// public PersistableBundleWrapper(@NonNull PersistableBundle bundle) {
// mBundle = Objects.requireNonNull(bundle, "Bundle was null");
// }
//
// /**
// * Retrieves the integer associated with the provided key.
// *
// * @param key the string key to query
// * @param defaultValue the value to return if key does not exist
// * @return the int value, or the default
// */
// public int getInt(String key, int defaultValue) {
// return mBundle.getInt(key, defaultValue);
// }
//
// @Override
// public int hashCode() {
// return getHashCode(mBundle);
// }
//
// @Override
// public boolean equals(Object obj) {
// if (!(obj instanceof PersistableBundleWrapper)) {
// return false;
// }
//
// final PersistableBundleWrapper other = (PersistableBundleWrapper) obj;
//
// return isEqual(mBundle, other.mBundle);
// }
// }
}