Merge changes from topics "PersistableBundleUtils", "bytearray-utils", "integer-utils" am: e9226f999f am: 089a20034d

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1495099

Change-Id: I6acf78b6eaf0ad2661b66a9f3e202b2a74b37233
This commit is contained in:
Yan Yan
2020-11-16 21:14:51 +00:00
committed by Automerger Merge Worker
3 changed files with 556 additions and 0 deletions

View File

@@ -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",
],
}

View File

@@ -0,0 +1,341 @@
/*
* 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.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;
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 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";
/**
* 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);
};
/**
* 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, 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.
*
* <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();
}
}
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.assertArrayEquals;
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.LinkedHashMap;
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 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;
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 testListConversionLossless() throws Exception {
final List<TestClass> 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<TestClass> resultList = PersistableBundleUtils.toList(bundled, TestClass::new);
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);
}
@Test
public void testByteArrayConversionLossless() {
final byte[] byteArray = "testByteArrayConversionLossless".getBytes();
PersistableBundle bundle = PersistableBundleUtils.fromByteArray(byteArray);
byte[] result = PersistableBundleUtils.toByteArray(bundle);
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);
}
}