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