summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java186
-rw-r--r--tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java63
2 files changed, 245 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index 73054ce04623..7d276c797676 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -17,15 +17,31 @@
package com.android.server.vcn.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelUuid;
import android.os.PersistableBundle;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+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 LIST_LENGTH_KEY = "LIST_LENGTH";
+ 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";
/**
* Functional interface to convert an object of the specified type to a PersistableBundle.
@@ -57,6 +73,33 @@ public class PersistableBundleUtils {
}
/**
+ * 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
@@ -72,7 +115,7 @@ public class PersistableBundleUtils {
@NonNull List<T> in, @NonNull Serializer<T> serializer) {
final PersistableBundle result = new PersistableBundle();
- result.putInt(LIST_LENGTH_KEY, in.size());
+ 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)));
@@ -91,7 +134,7 @@ public class PersistableBundleUtils {
@NonNull
public static <T> List<T> toList(
@NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) {
- final int listLength = in.getInt(LIST_LENGTH_KEY);
+ final int listLength = in.getInt(COLLECTION_SIZE_KEY);
final ArrayList<T> result = new ArrayList<>(listLength);
for (int i = 0; i < listLength; i++) {
@@ -102,4 +145,141 @@ public class PersistableBundleUtils {
}
return result;
}
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
index f78eeb69b5eb..07d3049ab6ed 100644
--- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
+++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
@@ -40,6 +41,43 @@ public class PersistableBundleUtilsTest {
private static final int NUM_COLLECTION_ENTRIES = 10;
+ private static class TestKey {
+ private static final String TEST_INTEGER_KEY =
+ "mTestInteger"; // Purposely colliding with keys of test class to ensure namespacing
+ private final int mTestInteger;
+
+ TestKey(int testInteger) {
+ mTestInteger = testInteger;
+ }
+
+ TestKey(PersistableBundle in) {
+ mTestInteger = in.getInt(TEST_INTEGER_KEY);
+ }
+
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(TEST_INTEGER_KEY, mTestInteger);
+
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTestInteger);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TestKey)) {
+ return false;
+ }
+
+ final TestKey other = (TestKey) o;
+ return mTestInteger == other.mTestInteger;
+ }
+ }
+
private static class TestClass {
private static final String TEST_INTEGER_KEY = "mTestInteger";
private final int mTestInteger;
@@ -113,7 +151,7 @@ public class PersistableBundleUtilsTest {
}
@Test
- public void testConversionLossless() throws Exception {
+ public void testListConversionLossless() throws Exception {
final List<TestClass> sourceList = new ArrayList<>();
for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
final PersistableBundle innerBundle = new PersistableBundle();
@@ -128,4 +166,27 @@ public class PersistableBundleUtilsTest {
assertEquals(sourceList, resultList);
}
+
+ @Test
+ public void testMapConversionLossless() throws Exception {
+ final LinkedHashMap<TestKey, TestClass> sourceMap = new LinkedHashMap<>();
+ for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
+ final TestKey key = new TestKey(i * i);
+
+ final PersistableBundle innerBundle = new PersistableBundle();
+ innerBundle.putInt(TEST_KEY, i);
+ final TestClass value =
+ new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle);
+
+ sourceMap.put(key, value);
+ }
+
+ final PersistableBundle bundled =
+ PersistableBundleUtils.fromMap(
+ sourceMap, TestKey::toPersistableBundle, TestClass::toPersistableBundle);
+ final LinkedHashMap<TestKey, TestClass> resultList =
+ PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new);
+
+ assertEquals(sourceMap, resultList);
+ }
}