• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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