1 /* 2 * Copyright (C) 2023 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 17 package com.android.adservices; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Build; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import java.time.Instant; 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Objects; 33 import java.util.Set; 34 import java.util.concurrent.Callable; 35 36 /** 37 * A utility extension to the {@link Parcelable} class for AdServices. 38 * 39 * @hide 40 */ 41 public final class AdServicesParcelableUtil { 42 /** 43 * Writes a nullable {@link Parcelable} object to a target {@link Parcel}. 44 * 45 * <p>An extra boolean is written to the {@code targetParcel} (see {@link 46 * #readNullableFromParcel(Parcel, ParcelReader)}) for the {@code nullableField}, where {@code 47 * true} is written if the field is not {@code null}. 48 */ writeNullableToParcel( @onNull Parcel targetParcel, @Nullable T nullableField, @NonNull ParcelWriter<T> parcelWriter)49 public static <T> void writeNullableToParcel( 50 @NonNull Parcel targetParcel, 51 @Nullable T nullableField, 52 @NonNull ParcelWriter<T> parcelWriter) { 53 Objects.requireNonNull(targetParcel); 54 Objects.requireNonNull(parcelWriter); 55 56 boolean isFieldPresent = (nullableField != null); 57 targetParcel.writeBoolean(isFieldPresent); 58 if (isFieldPresent) { 59 parcelWriter.write(targetParcel, nullableField); 60 } 61 } 62 63 /** 64 * Reads and returns a nullable object from a source {@link Parcel}. 65 * 66 * <p>This method expects a boolean (see {@link #writeNullableToParcel(Parcel, Object, 67 * ParcelWriter)}) that will be {@code true} if the nullable field is not {@code null} and reads 68 * and returns it using the given {@link Callable}. 69 */ readNullableFromParcel( @onNull Parcel sourceParcel, @NonNull ParcelReader<T> parcelReader)70 public static <T> T readNullableFromParcel( 71 @NonNull Parcel sourceParcel, @NonNull ParcelReader<T> parcelReader) { 72 Objects.requireNonNull(sourceParcel); 73 Objects.requireNonNull(parcelReader); 74 75 if (sourceParcel.readBoolean()) { 76 return parcelReader.read(sourceParcel); 77 } else { 78 return null; 79 } 80 } 81 82 /** 83 * Writes a {@link Map} of {@link Parcelable} keys and values to a target {@link Parcel}. 84 * 85 * <p>All keys of the {@code sourceMap} must be convertible to and from {@link String} objects. 86 * 87 * <p>Use to write a {@link Map} which will be later unparceled by {@link 88 * #readMapFromParcel(Parcel, StringToObjectConverter, Class)}. 89 */ writeMapToParcel( @onNull Parcel targetParcel, @NonNull Map<K, V> sourceMap)90 public static <K, V extends Parcelable> void writeMapToParcel( 91 @NonNull Parcel targetParcel, @NonNull Map<K, V> sourceMap) { 92 Objects.requireNonNull(targetParcel); 93 Objects.requireNonNull(sourceMap); 94 95 Bundle tempBundle = new Bundle(); 96 for (Map.Entry<K, V> entry : sourceMap.entrySet()) { 97 tempBundle.putParcelable(entry.getKey().toString(), entry.getValue()); 98 } 99 100 targetParcel.writeBundle(tempBundle); 101 } 102 103 /** 104 * Reads and returns a {@link Map} of {@link Parcelable} objects from a source {@link Parcel}. 105 * 106 * <p>Use to read a {@link Map} written with {@link #writeMapToParcel(Parcel, Map)}. 107 */ readMapFromParcel( @onNull Parcel sourceParcel, @NonNull StringToObjectConverter<K> stringToKeyConverter, @NonNull Class<V> valueClass)108 public static <K, V extends Parcelable> Map<K, V> readMapFromParcel( 109 @NonNull Parcel sourceParcel, 110 @NonNull StringToObjectConverter<K> stringToKeyConverter, 111 @NonNull Class<V> valueClass) { 112 Objects.requireNonNull(sourceParcel); 113 Objects.requireNonNull(stringToKeyConverter); 114 Objects.requireNonNull(valueClass); 115 116 Bundle tempBundle = Bundle.CREATOR.createFromParcel(sourceParcel); 117 tempBundle.setClassLoader(valueClass.getClassLoader()); 118 Map<K, V> resultMap = new HashMap<>(); 119 for (String key : tempBundle.keySet()) { 120 V value = 121 Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU 122 ? tempBundle.getParcelable(key) 123 : tempBundle.getParcelable(key, valueClass); 124 resultMap.put(stringToKeyConverter.convertFromString(key), value); 125 } 126 127 return resultMap; 128 } 129 130 /** 131 * Writes a {@link Set} of {@link Parcelable} objects to a target {@link Parcel} as an array. 132 * 133 * <p>Use to write a {@link Set} which will be unparceled by {@link #readSetFromParcel(Parcel, 134 * Parcelable.Creator)} later. 135 */ 136 public static <T extends Parcelable> void writeSetToParcel( 137 @NonNull Parcel targetParcel, @NonNull Set<T> sourceSet) { 138 Objects.requireNonNull(targetParcel); 139 Objects.requireNonNull(sourceSet); 140 141 ArrayList<T> tempList = new ArrayList<>(sourceSet); 142 targetParcel.writeTypedList(tempList); 143 } 144 145 /** 146 * Reads and returns a {@link Set} of {@link Parcelable} objects from a source {@link Parcel}. 147 * 148 * <p>Use to read a {@link Set} that was written by {@link #writeSetToParcel(Parcel, Set)}. 149 */ 150 public static <T extends Parcelable> Set<T> readSetFromParcel( 151 @NonNull Parcel sourceParcel, @NonNull Parcelable.Creator<T> creator) { 152 Objects.requireNonNull(sourceParcel); 153 Objects.requireNonNull(creator); 154 155 return new HashSet<>(Objects.requireNonNull(sourceParcel.createTypedArrayList(creator))); 156 } 157 158 /** 159 * Writes a {@link Set} of {@link String} objects to a target {@link Parcel} as a list. 160 * 161 * <p>Use to write a {@link Set} which will be unparceled by {@link 162 * #readStringSetFromParcel(Parcel)} later. 163 */ 164 public static void writeStringSetToParcel( 165 @NonNull Parcel targetParcel, @NonNull Set<String> sourceSet) { 166 Objects.requireNonNull(targetParcel); 167 Objects.requireNonNull(sourceSet); 168 169 ArrayList<String> tempList = new ArrayList<>(sourceSet); 170 targetParcel.writeStringList(tempList); 171 } 172 173 /** 174 * Reads and returns a {@link Set} of {@link String} objects from a source {@link Parcel}. 175 * 176 * <p>Use to read a {@link Set} that was written by {@link #writeStringSetToParcel(Parcel, 177 * Set)}. 178 */ 179 public static Set<String> readStringSetFromParcel(@NonNull Parcel sourceParcel) { 180 Objects.requireNonNull(sourceParcel); 181 182 return new HashSet<>(Objects.requireNonNull(sourceParcel.createStringArrayList())); 183 } 184 185 /** 186 * Writes a {@link Set} of {@link Integer} objects to a target {@link Parcel} as a list. 187 * 188 * <p>Use to write a {@link Set} which will be unparceled by {@link 189 * #readIntegerSetFromParcel(Parcel)} later. 190 */ 191 public static void writeIntegerSetToParcel( 192 @NonNull Parcel targetParcel, @NonNull Set<Integer> sourceSet) { 193 Objects.requireNonNull(targetParcel); 194 Objects.requireNonNull(sourceSet); 195 196 int[] tempArray = new int[sourceSet.size()]; 197 int actualArraySize = 0; 198 199 for (Integer integer : sourceSet) { 200 if (integer == null) { 201 LogUtil.w( 202 "Null value encountered while parceling Integer to int array; skipping" 203 + " element"); 204 continue; 205 } 206 207 tempArray[actualArraySize++] = integer; 208 } 209 210 // Writing the tempArray as is may write undefined values, so compress into a smaller 211 // accurately-fit array 212 int[] writeArray = new int[actualArraySize]; 213 System.arraycopy(tempArray, 0, writeArray, 0, actualArraySize); 214 215 targetParcel.writeInt(actualArraySize); 216 targetParcel.writeIntArray(writeArray); 217 } 218 219 /** 220 * Reads and returns a {@link Set} of {@link Integer} objects from a source {@link Parcel}. 221 * 222 * <p>Use to read a {@link Set} that was written by {@link #writeIntegerSetToParcel(Parcel, 223 * Set)}. 224 */ 225 public static Set<Integer> readIntegerSetFromParcel(@NonNull Parcel sourceParcel) { 226 Objects.requireNonNull(sourceParcel); 227 228 final int setSize = sourceParcel.readInt(); 229 int[] tempArray = new int[setSize]; 230 HashSet<Integer> targetSet = new HashSet<>(); 231 232 sourceParcel.readIntArray(tempArray); 233 for (int index = 0; index < setSize; index++) { 234 targetSet.add(tempArray[index]); 235 } 236 237 return targetSet; 238 } 239 240 /** 241 * Writes a {@link List} of {@link Instant} objects to a target {@link Parcel} as an array. 242 * 243 * <p>If an error is encountered while writing any element of the input {@code sourceList}, the 244 * element will be skipped. 245 * 246 * <p>Use to write a {@link List} which will be unparceled by {@link 247 * #readInstantListFromParcel(Parcel)} later. 248 */ 249 public static void writeInstantListToParcel( 250 @NonNull Parcel targetParcel, @NonNull List<Instant> sourceList) { 251 Objects.requireNonNull(targetParcel); 252 Objects.requireNonNull(sourceList); 253 254 long[] tempArray = new long[sourceList.size()]; 255 int actualArraySize = 0; 256 257 for (Instant instant : sourceList) { 258 long instantAsEpochMilli; 259 try { 260 instantAsEpochMilli = instant.toEpochMilli(); 261 } catch (Exception exception) { 262 LogUtil.w( 263 exception, 264 "Error encountered while parceling Instant %s to long; skipping element", 265 instant); 266 continue; 267 } 268 tempArray[actualArraySize++] = instantAsEpochMilli; 269 } 270 271 // Writing the tempArray as is may write undefined values, so compress into a smaller 272 // accurately-fit array 273 long[] writeArray = new long[actualArraySize]; 274 System.arraycopy(tempArray, 0, writeArray, 0, actualArraySize); 275 276 targetParcel.writeInt(actualArraySize); 277 targetParcel.writeLongArray(writeArray); 278 } 279 280 /** 281 * Reads and returns a {@link List} of {@link Instant} objects from a source {@link Parcel}. 282 * 283 * <p>If an error is encountered while reading an element from the {@code sourceParcel}, the 284 * element will be skipped. 285 * 286 * <p>Use to read a {@link List} that was written by {@link #writeInstantListToParcel(Parcel, 287 * List)}. 288 */ 289 public static List<Instant> readInstantListFromParcel(@NonNull Parcel sourceParcel) { 290 Objects.requireNonNull(sourceParcel); 291 292 final int listSize = sourceParcel.readInt(); 293 long[] tempArray = new long[listSize]; 294 ArrayList<Instant> targetList = new ArrayList<>(listSize); 295 296 sourceParcel.readLongArray(tempArray); 297 for (int ii = 0; ii < listSize; ii++) { 298 Instant instantFromMilli; 299 try { 300 instantFromMilli = Instant.ofEpochMilli(tempArray[ii]); 301 } catch (Exception exception) { 302 LogUtil.w( 303 exception, 304 "Error encountered while unparceling Instant from long %d; skipping" 305 + " element", 306 tempArray[ii]); 307 continue; 308 } 309 targetList.add(instantFromMilli); 310 } 311 312 return targetList; 313 } 314 315 /** 316 * A functional interface for writing a source object to a {@link Parcel}. 317 * 318 * @param <T> the type of the source object to be written 319 * @hide 320 */ 321 @FunctionalInterface 322 public interface ParcelWriter<T> { 323 /** Writes a {@code sourceObject} to the {@code targetParcel}. */ 324 void write(@NonNull Parcel targetParcel, @NonNull T sourceObject); 325 } 326 327 /** 328 * A functional interface for reading an object from a {@link Parcel}. 329 * 330 * @param <T> the type of the object to be read from the source parcel 331 * @hide 332 */ 333 @FunctionalInterface 334 public interface ParcelReader<T> { 335 /** Reads and returns an object from the {@code sourceParcel}. */ 336 T read(@NonNull Parcel sourceParcel); 337 } 338 339 /** 340 * A functional interface for converting a {@link String} to an object of type {@link T}. 341 * 342 * @param <T> the type of the object which will be converted from a {@link String} 343 * @hide 344 */ 345 @FunctionalInterface 346 public interface StringToObjectConverter<T> { 347 /** Converts the {@code sourceString} to an object of the specified type. */ 348 T convertFromString(@NonNull String sourceString); 349 } 350 } 351