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