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