1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.vcn.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.ParcelUuid; 22 import android.os.PersistableBundle; 23 24 import com.android.internal.util.HexDump; 25 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.LinkedHashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Map.Entry; 35 import java.util.Objects; 36 import java.util.concurrent.locks.ReadWriteLock; 37 import java.util.concurrent.locks.ReentrantReadWriteLock; 38 39 /** @hide */ 40 public class PersistableBundleUtils { 41 private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d"; 42 private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH"; 43 private static final String MAP_KEY_FORMAT = "MAP_KEY_%d"; 44 private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d"; 45 46 private static final String PARCEL_UUID_KEY = "PARCEL_UUID"; 47 private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY"; 48 private static final String INTEGER_KEY = "INTEGER_KEY"; 49 50 /** 51 * Functional interface to convert an object of the specified type to a PersistableBundle. 52 * 53 * @param <T> the type of the source object 54 */ 55 public interface Serializer<T> { 56 /** 57 * Converts this object to a PersistableBundle. 58 * 59 * @return the PersistableBundle representation of this object 60 */ toPersistableBundle(T obj)61 PersistableBundle toPersistableBundle(T obj); 62 } 63 64 /** 65 * Functional interface used to create an object of the specified type from a PersistableBundle. 66 * 67 * @param <T> the type of the resultant object 68 */ 69 public interface Deserializer<T> { 70 /** 71 * Creates an instance of specified type from a PersistableBundle representation. 72 * 73 * @param in the PersistableBundle representation 74 * @return an instance of the specified type 75 */ fromPersistableBundle(PersistableBundle in)76 T fromPersistableBundle(PersistableBundle in); 77 } 78 79 /** Serializer to convert an integer to a PersistableBundle. */ 80 public static final Serializer<Integer> INTEGER_SERIALIZER = 81 (i) -> { 82 final PersistableBundle result = new PersistableBundle(); 83 result.putInt(INTEGER_KEY, i); 84 return result; 85 }; 86 87 /** Deserializer to convert a PersistableBundle to an integer. */ 88 public static final Deserializer<Integer> INTEGER_DESERIALIZER = 89 (bundle) -> { 90 Objects.requireNonNull(bundle, "PersistableBundle is null"); 91 return bundle.getInt(INTEGER_KEY); 92 }; 93 94 /** 95 * Converts a ParcelUuid to a PersistableBundle. 96 * 97 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 98 * PersistableBundle object. 99 * 100 * @param uuid a ParcelUuid instance to persist 101 * @return the PersistableBundle instance 102 */ fromParcelUuid(ParcelUuid uuid)103 public static PersistableBundle fromParcelUuid(ParcelUuid uuid) { 104 final PersistableBundle result = new PersistableBundle(); 105 106 result.putString(PARCEL_UUID_KEY, uuid.toString()); 107 108 return result; 109 } 110 111 /** 112 * Converts from a PersistableBundle to a ParcelUuid. 113 * 114 * @param bundle the PersistableBundle containing the ParcelUuid 115 * @return the ParcelUuid instance 116 */ toParcelUuid(PersistableBundle bundle)117 public static ParcelUuid toParcelUuid(PersistableBundle bundle) { 118 return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY)); 119 } 120 121 /** 122 * Converts from a list of Persistable objects to a single PersistableBundle. 123 * 124 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 125 * PersistableBundle object. 126 * 127 * @param <T> the type of the objects to convert to the PersistableBundle 128 * @param in the list of objects to be serialized into a PersistableBundle 129 * @param serializer an implementation of the {@link Serializer} functional interface that 130 * converts an object of type T to a PersistableBundle 131 */ 132 @NonNull fromList( @onNull List<T> in, @NonNull Serializer<T> serializer)133 public static <T> PersistableBundle fromList( 134 @NonNull List<T> in, @NonNull Serializer<T> serializer) { 135 final PersistableBundle result = new PersistableBundle(); 136 137 result.putInt(COLLECTION_SIZE_KEY, in.size()); 138 for (int i = 0; i < in.size(); i++) { 139 final String key = String.format(LIST_KEY_FORMAT, i); 140 result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i))); 141 } 142 return result; 143 } 144 145 /** 146 * Converts from a PersistableBundle to a list of objects. 147 * 148 * @param <T> the type of the objects to convert from a PersistableBundle 149 * @param in the PersistableBundle containing the persisted list 150 * @param deserializer an implementation of the {@link Deserializer} functional interface that 151 * builds the relevant type of objects. 152 */ 153 @NonNull toList( @onNull PersistableBundle in, @NonNull Deserializer<T> deserializer)154 public static <T> List<T> toList( 155 @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) { 156 final int listLength = in.getInt(COLLECTION_SIZE_KEY); 157 final ArrayList<T> result = new ArrayList<>(listLength); 158 159 for (int i = 0; i < listLength; i++) { 160 final String key = String.format(LIST_KEY_FORMAT, i); 161 final PersistableBundle item = in.getPersistableBundle(key); 162 163 result.add(deserializer.fromPersistableBundle(item)); 164 } 165 return result; 166 } 167 168 // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and 169 // BaseBundle#getByteArray are exposed. 170 171 /** 172 * Converts a byte array to a PersistableBundle. 173 * 174 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 175 * PersistableBundle object. 176 * 177 * @param array a byte array instance to persist 178 * @return the PersistableBundle instance 179 */ fromByteArray(byte[] array)180 public static PersistableBundle fromByteArray(byte[] array) { 181 final PersistableBundle result = new PersistableBundle(); 182 183 result.putString(BYTE_ARRAY_KEY, HexDump.toHexString(array)); 184 185 return result; 186 } 187 188 /** 189 * Converts from a PersistableBundle to a byte array. 190 * 191 * @param bundle the PersistableBundle containing the byte array 192 * @return the byte array instance 193 */ toByteArray(PersistableBundle bundle)194 public static byte[] toByteArray(PersistableBundle bundle) { 195 Objects.requireNonNull(bundle, "PersistableBundle is null"); 196 197 String hex = bundle.getString(BYTE_ARRAY_KEY); 198 if (hex == null || hex.length() % 2 != 0) { 199 throw new IllegalArgumentException("PersistableBundle contains invalid byte array"); 200 } 201 202 return HexDump.hexStringToByteArray(hex); 203 } 204 205 /** 206 * Converts from a Map of Persistable objects to a single PersistableBundle. 207 * 208 * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned 209 * PersistableBundle object. 210 * 211 * @param <K> the type of the map-key to convert to the PersistableBundle 212 * @param <V> the type of the map-value to convert to the PersistableBundle 213 * @param in the Map of objects implementing the {@link Persistable} interface 214 * @param keySerializer an implementation of the {@link Serializer} functional interface that 215 * converts a map-key of type T to a PersistableBundle 216 * @param valueSerializer an implementation of the {@link Serializer} functional interface that 217 * converts a map-value of type E to a PersistableBundle 218 */ 219 @NonNull fromMap( @onNull Map<K, V> in, @NonNull Serializer<K> keySerializer, @NonNull Serializer<V> valueSerializer)220 public static <K, V> PersistableBundle fromMap( 221 @NonNull Map<K, V> in, 222 @NonNull Serializer<K> keySerializer, 223 @NonNull Serializer<V> valueSerializer) { 224 final PersistableBundle result = new PersistableBundle(); 225 226 result.putInt(COLLECTION_SIZE_KEY, in.size()); 227 int i = 0; 228 for (Entry<K, V> entry : in.entrySet()) { 229 final String keyKey = String.format(MAP_KEY_FORMAT, i); 230 final String valueKey = String.format(MAP_VALUE_FORMAT, i); 231 result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey())); 232 result.putPersistableBundle( 233 valueKey, valueSerializer.toPersistableBundle(entry.getValue())); 234 235 i++; 236 } 237 238 return result; 239 } 240 241 /** 242 * Converts from a PersistableBundle to a Map of objects. 243 * 244 * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the 245 * guarantees on the ordering can only ever be as strong as the map that was serialized in 246 * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the 247 * deserialized map similarly may be of a non-deterministic order. 248 * 249 * @param <K> the type of the map-key to convert from a PersistableBundle 250 * @param <V> the type of the map-value to convert from a PersistableBundle 251 * @param in the PersistableBundle containing the persisted Map 252 * @param keyDeserializer an implementation of the {@link Deserializer} functional interface 253 * that builds the relevant type of map-key. 254 * @param valueDeserializer an implementation of the {@link Deserializer} functional interface 255 * that builds the relevant type of map-value. 256 * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve 257 * ordering). 258 */ 259 @NonNull toMap( @onNull PersistableBundle in, @NonNull Deserializer<K> keyDeserializer, @NonNull Deserializer<V> valueDeserializer)260 public static <K, V> LinkedHashMap<K, V> toMap( 261 @NonNull PersistableBundle in, 262 @NonNull Deserializer<K> keyDeserializer, 263 @NonNull Deserializer<V> valueDeserializer) { 264 final int mapSize = in.getInt(COLLECTION_SIZE_KEY); 265 final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize); 266 267 for (int i = 0; i < mapSize; i++) { 268 final String keyKey = String.format(MAP_KEY_FORMAT, i); 269 final String valueKey = String.format(MAP_VALUE_FORMAT, i); 270 final PersistableBundle keyBundle = in.getPersistableBundle(keyKey); 271 final PersistableBundle valueBundle = in.getPersistableBundle(valueKey); 272 273 final K key = keyDeserializer.fromPersistableBundle(keyBundle); 274 final V value = valueDeserializer.fromPersistableBundle(valueBundle); 275 result.put(key, value); 276 } 277 return result; 278 } 279 280 /** 281 * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk. 282 * 283 * <p>This class will enforce exclusion between reads and writes using the standard semantics of 284 * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the 285 * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states 286 * are n readers, OR 1 writer (but not both). 287 */ 288 public static class LockingReadWriteHelper { 289 private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock(); 290 private final String mPath; 291 LockingReadWriteHelper(@onNull String path)292 public LockingReadWriteHelper(@NonNull String path) { 293 mPath = Objects.requireNonNull(path, "fileName was null"); 294 } 295 296 /** 297 * Reads the {@link PersistableBundle} from the disk. 298 * 299 * @return the PersistableBundle, if the file existed, or null otherwise 300 */ 301 @Nullable readFromDisk()302 public PersistableBundle readFromDisk() throws IOException { 303 try { 304 mDiskLock.readLock().lock(); 305 final File file = new File(mPath); 306 if (!file.exists()) { 307 return null; 308 } 309 310 try (FileInputStream fis = new FileInputStream(file)) { 311 return PersistableBundle.readFromStream(fis); 312 } 313 } finally { 314 mDiskLock.readLock().unlock(); 315 } 316 } 317 318 /** 319 * Writes a {@link PersistableBundle} to disk. 320 * 321 * @param bundle the {@link PersistableBundle} to write to disk 322 */ writeToDisk(@onNull PersistableBundle bundle)323 public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException { 324 Objects.requireNonNull(bundle, "bundle was null"); 325 326 try { 327 mDiskLock.writeLock().lock(); 328 final File file = new File(mPath); 329 if (!file.exists()) { 330 file.getParentFile().mkdirs(); 331 } 332 333 try (FileOutputStream fos = new FileOutputStream(file)) { 334 bundle.writeToStream(fos); 335 } 336 } finally { 337 mDiskLock.writeLock().unlock(); 338 } 339 } 340 } 341 } 342