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