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 List} of {@link Instant} objects to a target {@link Parcel} as an array. 187 * 188 * <p>If an error is encountered while writing any element of the input {@code sourceList}, the 189 * element will be skipped. 190 * 191 * <p>Use to write a {@link List} which will be unparceled by {@link 192 * #readInstantListFromParcel(Parcel)} later. 193 */ 194 public static void writeInstantListToParcel( 195 @NonNull Parcel targetParcel, @NonNull List<Instant> sourceList) { 196 Objects.requireNonNull(targetParcel); 197 Objects.requireNonNull(sourceList); 198 199 long[] tempArray = new long[sourceList.size()]; 200 int actualArraySize = 0; 201 202 for (Instant instant : sourceList) { 203 long instantAsEpochMilli; 204 try { 205 instantAsEpochMilli = instant.toEpochMilli(); 206 } catch (Exception exception) { 207 LogUtil.w( 208 exception, 209 "Error encountered while parceling Instant %s to long; skipping element", 210 instant); 211 continue; 212 } 213 tempArray[actualArraySize++] = instantAsEpochMilli; 214 } 215 216 // Writing the tempArray as is may write undefined values, so compress into a smaller 217 // accurately-fit array 218 long[] writeArray = new long[actualArraySize]; 219 System.arraycopy(tempArray, 0, writeArray, 0, actualArraySize); 220 221 targetParcel.writeInt(actualArraySize); 222 targetParcel.writeLongArray(writeArray); 223 } 224 225 /** 226 * Reads and returns a {@link List} of {@link Instant} objects from a source {@link Parcel}. 227 * 228 * <p>If an error is encountered while reading an element from the {@code sourceParcel}, the 229 * element will be skipped. 230 * 231 * <p>Use to read a {@link List} that was written by {@link #writeInstantListToParcel(Parcel, 232 * List)}. 233 */ 234 public static List<Instant> readInstantListFromParcel(@NonNull Parcel sourceParcel) { 235 Objects.requireNonNull(sourceParcel); 236 237 final int listSize = sourceParcel.readInt(); 238 long[] tempArray = new long[listSize]; 239 ArrayList<Instant> targetList = new ArrayList<>(listSize); 240 241 sourceParcel.readLongArray(tempArray); 242 for (int ii = 0; ii < listSize; ii++) { 243 Instant instantFromMilli; 244 try { 245 instantFromMilli = Instant.ofEpochMilli(tempArray[ii]); 246 } catch (Exception exception) { 247 LogUtil.w( 248 exception, 249 "Error encountered while unparceling Instant from long %d; skipping" 250 + " element", 251 tempArray[ii]); 252 continue; 253 } 254 targetList.add(instantFromMilli); 255 } 256 257 return targetList; 258 } 259 260 /** 261 * A functional interface for writing a source object to a {@link Parcel}. 262 * 263 * @param <T> the type of the source object to be written 264 * @hide 265 */ 266 @FunctionalInterface 267 public interface ParcelWriter<T> { 268 /** Writes a {@code sourceObject} to the {@code targetParcel}. */ 269 void write(@NonNull Parcel targetParcel, @NonNull T sourceObject); 270 } 271 272 /** 273 * A functional interface for reading an object from a {@link Parcel}. 274 * 275 * @param <T> the type of the object to be read from the source parcel 276 * @hide 277 */ 278 @FunctionalInterface 279 public interface ParcelReader<T> { 280 /** Reads and returns an object from the {@code sourceParcel}. */ 281 T read(@NonNull Parcel sourceParcel); 282 } 283 284 /** 285 * A functional interface for converting a {@link String} to an object of type {@link T}. 286 * 287 * @param <T> the type of the object which will be converted from a {@link String} 288 * @hide 289 */ 290 @FunctionalInterface 291 public interface StringToObjectConverter<T> { 292 /** Converts the {@code sourceString} to an object of the specified type. */ 293 T convertFromString(@NonNull String sourceString); 294 } 295 } 296