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); + } +}