1 /*
2  * Copyright (C) 2019 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 package com.android.internal.util;
17 
18 import static java.util.Collections.emptySet;
19 
20 import android.annotation.Nullable;
21 import android.os.Parcel;
22 import android.text.TextUtils;
23 import android.util.ArrayMap;
24 import android.util.ArraySet;
25 
26 import java.time.Instant;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.UUID;
32 import java.util.regex.Pattern;
33 
34 /**
35  * Describes a 2-way parcelling contract of type {@code T} into/out of a {@link Parcel}
36  *
37  * Implementations should be stateless.
38  *
39  * @param <T> the type being [un]parcelled
40  */
41 @android.ravenwood.annotation.RavenwoodKeepWholeClass
42 public interface Parcelling<T> {
43 
44     /**
45      * Write an item into parcel.
46      */
parcel(T item, Parcel dest, int parcelFlags)47     void parcel(T item, Parcel dest, int parcelFlags);
48 
49     /**
50      * Read an item from parcel.
51      */
unparcel(Parcel source)52     T unparcel(Parcel source);
53 
54 
55     /**
56      * A registry of {@link Parcelling} singletons.
57      */
58     class Cache {
Cache()59         private Cache() {}
60 
61         private static ArrayMap<Class, Parcelling> sCache = new ArrayMap<>();
62 
63         /**
64          * Retrieves an instance of a given {@link Parcelling} class if present.
65          */
get(Class<P> clazz)66         public static @Nullable <P extends Parcelling<?>> P get(Class<P> clazz) {
67             return (P) sCache.get(clazz);
68         }
69 
70         /**
71          * Stores an instance of a given {@link Parcelling}.
72          *
73          * @return the provided parcelling for convenience.
74          */
put(P parcelling)75         public static <P extends Parcelling<?>> P put(P parcelling) {
76             sCache.put(parcelling.getClass(), parcelling);
77             return parcelling;
78         }
79 
80         /**
81          * Produces an instance of a given {@link Parcelling} class, by either retrieving a cached
82          * instance or reflectively creating one.
83          */
getOrCreate(Class<P> clazz)84         public static <P extends Parcelling<?>> P getOrCreate(Class<P> clazz) {
85             // No synchronization - creating an extra instance in a race case is ok
86             P cached = get(clazz);
87             if (cached != null) {
88                 return cached;
89             } else {
90                 try {
91                     return put(clazz.newInstance());
92                 } catch (Exception e) {
93                     throw new RuntimeException(e);
94                 }
95             }
96         }
97     }
98 
99     /**
100      * Common {@link Parcelling} implementations.
101      */
102     interface BuiltIn {
103 
104         class ForInternedString implements Parcelling<String> {
105             @Override
parcel(@ullable String item, Parcel dest, int parcelFlags)106             public void parcel(@Nullable String item, Parcel dest, int parcelFlags) {
107                 dest.writeString(item);
108             }
109 
110             @Nullable
111             @Override
unparcel(Parcel source)112             public String unparcel(Parcel source) {
113                 return TextUtils.safeIntern(source.readString());
114             }
115         }
116 
117         class ForInternedStringArray implements Parcelling<String[]> {
118             @Override
parcel(String[] item, Parcel dest, int parcelFlags)119             public void parcel(String[] item, Parcel dest, int parcelFlags) {
120                 dest.writeStringArray(item);
121             }
122 
123             @Nullable
124             @Override
unparcel(Parcel source)125             public String[] unparcel(Parcel source) {
126                 String[] array = source.readStringArray();
127                 if (array != null) {
128                     int size = ArrayUtils.size(array);
129                     for (int index = 0; index < size; index++) {
130                         array[index] = TextUtils.safeIntern(array[index]);
131                     }
132                 }
133                 return array;
134             }
135         }
136 
137         class ForInternedStringList implements Parcelling<List<String>> {
138             @Override
parcel(List<String> item, Parcel dest, int parcelFlags)139             public void parcel(List<String> item, Parcel dest, int parcelFlags) {
140                 dest.writeStringList(item);
141             }
142 
143             @Override
unparcel(Parcel source)144             public List<String> unparcel(Parcel source) {
145                 ArrayList<String> list = source.createStringArrayList();
146                 if (list != null) {
147                     int size = list.size();
148                     for (int index = 0; index < size; index++) {
149                         list.set(index, list.get(index).intern());
150                     }
151                 }
152                 return CollectionUtils.emptyIfNull(list);
153             }
154         }
155 
156         class ForInternedStringValueMap implements Parcelling<Map<String, String>> {
157             @Override
parcel(Map<String, String> item, Parcel dest, int parcelFlags)158             public void parcel(Map<String, String> item, Parcel dest, int parcelFlags) {
159                 dest.writeMap(item);
160             }
161 
162             @Override
unparcel(Parcel source)163             public Map<String, String> unparcel(Parcel source) {
164                 ArrayMap<String, String> map = new ArrayMap<>();
165                 source.readMap(map, String.class.getClassLoader());
166                 for (int index = 0; index < map.size(); index++) {
167                     map.setValueAt(index, TextUtils.safeIntern(map.valueAt(index)));
168                 }
169                 return map;
170             }
171         }
172 
173         class ForStringSet implements Parcelling<Set<String>> {
174             @Override
parcel(Set<String> item, Parcel dest, int parcelFlags)175             public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
176                 if (item == null) {
177                     dest.writeInt(-1);
178                 } else {
179                     dest.writeInt(item.size());
180                     for (String string : item) {
181                         dest.writeString(string);
182                     }
183                 }
184             }
185 
186             @Override
unparcel(Parcel source)187             public Set<String> unparcel(Parcel source) {
188                 final int size = source.readInt();
189                 if (size < 0) {
190                     return emptySet();
191                 }
192                 Set<String> set = new ArraySet<>();
193                 for (int count = 0; count < size; count++) {
194                     set.add(source.readString());
195                 }
196                 return set;
197             }
198         }
199 
200         class ForInternedStringSet implements Parcelling<Set<String>> {
201             @Override
parcel(Set<String> item, Parcel dest, int parcelFlags)202             public void parcel(Set<String> item, Parcel dest, int parcelFlags) {
203                 if (item == null) {
204                     dest.writeInt(-1);
205                 } else {
206                     dest.writeInt(item.size());
207                     for (String string : item) {
208                         dest.writeString(string);
209                     }
210                 }
211             }
212 
213             @Override
unparcel(Parcel source)214             public Set<String> unparcel(Parcel source) {
215                 final int size = source.readInt();
216                 if (size < 0) {
217                     return emptySet();
218                 }
219                 Set<String> set = new ArraySet<>();
220                 for (int count = 0; count < size; count++) {
221                     set.add(TextUtils.safeIntern(source.readString()));
222                 }
223                 return set;
224             }
225         }
226 
227         class ForInternedStringArraySet implements Parcelling<ArraySet<String>> {
228             @Override
parcel(ArraySet<String> item, Parcel dest, int parcelFlags)229             public void parcel(ArraySet<String> item, Parcel dest, int parcelFlags) {
230                 if (item == null) {
231                     dest.writeInt(-1);
232                 } else {
233                     dest.writeInt(item.size());
234                     for (String string : item) {
235                         dest.writeString(string);
236                     }
237                 }
238             }
239 
240             @Override
unparcel(Parcel source)241             public ArraySet<String> unparcel(Parcel source) {
242                 final int size = source.readInt();
243                 if (size < 0) {
244                   return null;
245                 }
246                 ArraySet<String> set = new ArraySet<>();
247                 for (int count = 0; count < size; count++) {
248                     set.add(TextUtils.safeIntern(source.readString()));
249                 }
250                 return set;
251             }
252         }
253 
254         class ForBoolean implements Parcelling<Boolean> {
255             @Override
parcel(@ullable Boolean item, Parcel dest, int parcelFlags)256             public void parcel(@Nullable Boolean item, Parcel dest, int parcelFlags) {
257                 if (item == null) {
258                     // This writes 1 for null to mirror TypedArray.getInteger(booleanResId, 1)
259                     dest.writeInt(1);
260                 } else if (!item) {
261                     dest.writeInt(0);
262                 } else {
263                     dest.writeInt(-1);
264                 }
265             }
266 
267             @Nullable
268             @Override
unparcel(Parcel source)269             public Boolean unparcel(Parcel source) {
270                 switch (source.readInt()) {
271                     default:
272                         throw new IllegalStateException("Malformed Parcel reading Boolean: "
273                                 + source);
274                     case 1:
275                         return null;
276                     case 0:
277                         return Boolean.FALSE;
278                     case -1:
279                         return Boolean.TRUE;
280                 }
281             }
282         }
283 
284         class ForPattern implements Parcelling<Pattern> {
285 
286             @Override
parcel(Pattern item, Parcel dest, int parcelFlags)287             public void parcel(Pattern item, Parcel dest, int parcelFlags) {
288                 dest.writeString(item == null ? null : item.pattern());
289             }
290 
291             @Override
unparcel(Parcel source)292             public Pattern unparcel(Parcel source) {
293                 String s = source.readString();
294                 return s == null ? null : Pattern.compile(s);
295             }
296         }
297 
298         class ForUUID implements Parcelling<UUID> {
299 
300             @Override
parcel(UUID item, Parcel dest, int parcelFlags)301             public void parcel(UUID item, Parcel dest, int parcelFlags) {
302                 dest.writeString(item == null ? null : item.toString());
303             }
304 
305             @Override
unparcel(Parcel source)306             public UUID unparcel(Parcel source) {
307                 String string = source.readString();
308                 return string == null ? null : UUID.fromString(string);
309             }
310         }
311 
312         /**
313          * A {@link Parcelling} for {@link Instant}.
314          *
315          * The minimum value of an instant uses a millisecond offset of about -3.15e19 which is
316          * larger than Long.MIN_VALUE, so we can use Long.MIN_VALUE as a sentinel value to indicate
317          * a null Instant.
318          */
319         class ForInstant implements Parcelling<Instant> {
320 
321             @Override
parcel(Instant item, Parcel dest, int parcelFlags)322             public void parcel(Instant item, Parcel dest, int parcelFlags) {
323                 dest.writeLong(item == null ? Long.MIN_VALUE : item.getEpochSecond());
324                 dest.writeInt(item == null ? Integer.MIN_VALUE : item.getNano());
325             }
326 
327             @Override
unparcel(Parcel source)328             public Instant unparcel(Parcel source) {
329                 long epochSecond = source.readLong();
330                 int afterNano = source.readInt();
331 
332                 if (epochSecond == Long.MIN_VALUE) {
333                     return null;
334                 } else {
335                     return Instant.ofEpochSecond(epochSecond, afterNano);
336                 }
337             }
338         }
339     }
340 }
341