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