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