From b92cbc787e0420b12619540083fee850775ce3c7 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Fri, 6 Nov 2020 14:53:28 -0800 Subject: [PATCH 1/5] Add utilities to persist lists of Persistable objects This change adds a utility class to enable persistance of Lists of objects. The PersistableBundle class does not currently support lists or arrays of PersistableBundles, presumably due to the potential for key conflicts. The utility classes added here avoid that concern by nesting all lists as separate persistable bundles. Bug: 163594033 Test: New PersistableBundleUtilsTest added, passing Change-Id: I89478cf0d05d41a4b0d769de4859421061a1f1d9 --- .../vcn/util/PersistableBundleUtils.java | 105 ++++++++++++++ .../vcn/util/PersistableBundleUtilsTest.java | 131 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java create mode 100644 tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java new file mode 100644 index 0000000000000..73054ce046239 --- /dev/null +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 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 com.android.server.vcn.util; + +import android.annotation.NonNull; +import android.os.PersistableBundle; + +import java.util.ArrayList; +import java.util.List; + +/** @hide */ +public class PersistableBundleUtils { + private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d"; + private static final String LIST_LENGTH_KEY = "LIST_LENGTH"; + + /** + * Functional interface to convert an object of the specified type to a PersistableBundle. + * + * @param the type of the source object + */ + public interface Serializer { + /** + * 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 the type of the resultant object + */ + public interface Deserializer { + /** + * 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); + } + + /** + * Converts from a list of Persistable objects to a single PersistableBundle. + * + *

To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param 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 PersistableBundle fromList( + @NonNull List in, @NonNull Serializer serializer) { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(LIST_LENGTH_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 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 List toList( + @NonNull PersistableBundle in, @NonNull Deserializer deserializer) { + final int listLength = in.getInt(LIST_LENGTH_KEY); + final ArrayList 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; + } +} diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java new file mode 100644 index 0000000000000..f78eeb69b5eb6 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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 com.android.server.vcn.util; + +import static org.junit.Assert.assertEquals; + +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PersistableBundleUtilsTest { + private static final String TEST_KEY = "testKey"; + private static final String TEST_STRING_PREFIX = "testString"; + private static final int[] TEST_INT_ARRAY = new int[] {0, 1, 2, 3, 4}; + + private static final int NUM_COLLECTION_ENTRIES = 10; + + private static class TestClass { + private static final String TEST_INTEGER_KEY = "mTestInteger"; + private final int mTestInteger; + + private static final String TEST_INT_ARRAY_KEY = "mTestIntArray"; + private final int[] mTestIntArray; + + private static final String TEST_STRING_KEY = "mTestString"; + private final String mTestString; + + private static final String TEST_PERSISTABLE_BUNDLE_KEY = "mTestPersistableBundle"; + private final PersistableBundle mTestPersistableBundle; + + TestClass( + int testInteger, + int[] testIntArray, + String testString, + PersistableBundle testPersistableBundle) { + mTestInteger = testInteger; + mTestIntArray = testIntArray; + mTestString = testString; + mTestPersistableBundle = testPersistableBundle; + } + + TestClass(PersistableBundle in) { + mTestInteger = in.getInt(TEST_INTEGER_KEY); + mTestIntArray = in.getIntArray(TEST_INT_ARRAY_KEY); + mTestString = in.getString(TEST_STRING_KEY); + mTestPersistableBundle = in.getPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY); + } + + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TEST_INTEGER_KEY, mTestInteger); + result.putIntArray(TEST_INT_ARRAY_KEY, mTestIntArray); + result.putString(TEST_STRING_KEY, mTestString); + result.putPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY, mTestPersistableBundle); + + return result; + } + + @Override + public int hashCode() { + return Objects.hash( + mTestInteger, + Arrays.hashCode(mTestIntArray), + mTestString, + mTestPersistableBundle); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TestClass)) { + return false; + } + + final TestClass other = (TestClass) o; + + // TODO: Add a proper equals() to PersistableBundle. But in the meantime, force + // TODO: unparcelling in order to allow test comparison. + if (mTestPersistableBundle.size() != other.mTestPersistableBundle.size()) { + return false; + } + + return mTestInteger == other.mTestInteger + && Arrays.equals(mTestIntArray, other.mTestIntArray) + && mTestString.equals(other.mTestString) + && mTestPersistableBundle.kindofEquals(other.mTestPersistableBundle); + } + } + + @Test + public void testConversionLossless() throws Exception { + final List sourceList = new ArrayList<>(); + for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) { + final PersistableBundle innerBundle = new PersistableBundle(); + innerBundle.putInt(TEST_KEY, i); + + sourceList.add(new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle)); + } + + final PersistableBundle bundled = + PersistableBundleUtils.fromList(sourceList, TestClass::toPersistableBundle); + final List resultList = PersistableBundleUtils.toList(bundled, TestClass::new); + + assertEquals(sourceList, resultList); + } +} From 296c895e10cf835e3158e0327e4f202559323f3e Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Fri, 6 Nov 2020 21:43:58 -0800 Subject: [PATCH 2/5] Add utils for converting Maps and ParcelUuid to/from PersistableBundle This commit expands the PersistableBundleUtils by adding maps. LinkedHashMap is used in an attempt to preserve ordering where stability is important. Similarly, this commit adds the ability to persist ParcelUuid(s) via conversion to Strings. This commit also adds a helper method to safely read and write to and from disk Bug: 163611304 Test: New tests added, passing Change-Id: Ife24e94006445007be68ab0e03f27b2fd5643aa2 --- .../vcn/util/PersistableBundleUtils.java | 186 +++++++++++++++++- .../vcn/util/PersistableBundleUtilsTest.java | 63 +++++- 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 73054ce046239..7d276c7976769 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. @@ -56,6 +72,33 @@ public class PersistableBundleUtils { T fromPersistableBundle(PersistableBundle in); } + /** + * Converts a ParcelUuid to a PersistableBundle. + * + *

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. * @@ -72,7 +115,7 @@ public class PersistableBundleUtils { @NonNull List in, @NonNull Serializer 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 List toList( @NonNull PersistableBundle in, @NonNull Deserializer deserializer) { - final int listLength = in.getInt(LIST_LENGTH_KEY); + final int listLength = in.getInt(COLLECTION_SIZE_KEY); final ArrayList 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. + * + *

To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param the type of the map-key to convert to the PersistableBundle + * @param 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 PersistableBundle fromMap( + @NonNull Map in, + @NonNull Serializer keySerializer, + @NonNull Serializer valueSerializer) { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(COLLECTION_SIZE_KEY, in.size()); + int i = 0; + for (Entry 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. + * + *

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 the type of the map-key to convert from a PersistableBundle + * @param 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 LinkedHashMap toMap( + @NonNull PersistableBundle in, + @NonNull Deserializer keyDeserializer, + @NonNull Deserializer valueDeserializer) { + final int mapSize = in.getInt(COLLECTION_SIZE_KEY); + final LinkedHashMap 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. + * + *

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 f78eeb69b5eb6..07d3049ab6edf 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 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 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 resultList = + PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new); + + assertEquals(sourceMap, resultList); + } } From a3e954ffda99595afa64f9de6e015390df394128 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Mon, 9 Nov 2020 00:09:39 -0800 Subject: [PATCH 3/5] Include PersistableBundleUtils in framework-ike-shared-srcs Bug: 163604823 Test: FrameworksIkeTests, CtsIkeTestCases Change-Id: Iee38086e122293f59ae14d3350f45a63671e8566 --- Android.bp | 1 + 1 file changed, 1 insertion(+) diff --git a/Android.bp b/Android.bp index eb1718efbef65..570040f3ab93c 100644 --- a/Android.bp +++ b/Android.bp @@ -744,6 +744,7 @@ filegroup { "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", + "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", "telephony/java/android/telephony/Annotation.java", ], } From 42444745fc65fcff9f307d512f90696a3eb3e653 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Mon, 9 Nov 2020 10:45:04 -0800 Subject: [PATCH 4/5] Support converting byte array to/from PersistableBundle Test: FrameworksVcnTests:PersistableBundleUtilsTest(new tests added) Change-Id: I1d700c9b6d10a40ef43abcab449d4b07d897f0ec --- .../vcn/util/PersistableBundleUtils.java | 40 +++++++++++++++++++ .../vcn/util/PersistableBundleUtilsTest.java | 11 +++++ 2 files changed, 51 insertions(+) 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 7d276c7976769..3672f9a2465e3 100644 --- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.os.ParcelUuid; import android.os.PersistableBundle; +import com.android.internal.util.HexDump; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -42,6 +44,7 @@ public class PersistableBundleUtils { 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"; /** * Functional interface to convert an object of the specified type to a PersistableBundle. @@ -146,6 +149,43 @@ public class PersistableBundleUtils { return result; } + // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and + // BaseBundle#getByteArray are exposed. + + /** + * Converts a byte array to a PersistableBundle. + * + *

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, HexDump.toHexString(array)); + + return result; + } + + /** + * 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 HexDump.hexStringToByteArray(hex); + } + /** * Converts from a Map of Persistable objects to a single PersistableBundle. * 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 07d3049ab6edf..4d8424bc8726a 100644 --- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -16,6 +16,7 @@ package com.android.server.vcn.util; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.os.PersistableBundle; @@ -189,4 +190,14 @@ public class PersistableBundleUtilsTest { assertEquals(sourceMap, resultList); } + + @Test + public void testByteArrayConversionLossless() { + final byte[] byteArray = "testByteArrayConversionLossless".getBytes(); + + PersistableBundle bundle = PersistableBundleUtils.fromByteArray(byteArray); + byte[] result = PersistableBundleUtils.toByteArray(bundle); + + assertArrayEquals(byteArray, result); + } } From cfb6847e33f84b4d8a8482075b4001bd8698f10e Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Thu, 10 Sep 2020 12:09:17 -0700 Subject: [PATCH 5/5] Support converting integer to/from PersistableBundle This CL is for facilitating converting Map and List that contains integer type objects. Bug: 163604823 Test: PersistableBundleUtilsTest(new tests added) Change-Id: I24239caf70035e19c3fb5eb4a85b6a0c6ccadb5a --- .../server/vcn/util/PersistableBundleUtils.java | 16 ++++++++++++++++ .../vcn/util/PersistableBundleUtilsTest.java | 11 +++++++++++ 2 files changed, 27 insertions(+) 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 3672f9a2465e3..5c1b5ffb22099 100644 --- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -45,6 +45,7 @@ public class PersistableBundleUtils { 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"; /** * Functional interface to convert an object of the specified type to a PersistableBundle. @@ -75,6 +76,21 @@ public class PersistableBundleUtils { T fromPersistableBundle(PersistableBundle in); } + /** Serializer to convert an integer to a PersistableBundle. */ + public static final Serializer 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_DESERIALIZER = + (bundle) -> { + Objects.requireNonNull(bundle, "PersistableBundle is null"); + return bundle.getInt(INTEGER_KEY); + }; + /** * Converts a ParcelUuid to a PersistableBundle. * 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 4d8424bc8726a..a44a734a2dce6 100644 --- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -200,4 +200,15 @@ public class PersistableBundleUtilsTest { assertArrayEquals(byteArray, result); } + + @Test + public void testIntegerConversionLossless() throws Exception { + final int testInt = 1; + final PersistableBundle integerBundle = + PersistableBundleUtils.INTEGER_SERIALIZER.toPersistableBundle(testInt); + final int result = + PersistableBundleUtils.INTEGER_DESERIALIZER.fromPersistableBundle(integerBundle); + + assertEquals(testInt, result); + } }