/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.adservices; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; /** * A utility extension to the {@link Parcelable} class for AdServices. * * @hide */ public final class AdServicesParcelableUtil { /** * Writes a nullable {@link Parcelable} object to a target {@link Parcel}. * *

An extra boolean is written to the {@code targetParcel} (see {@link * #readNullableFromParcel(Parcel, ParcelReader)}) for the {@code nullableField}, where {@code * true} is written if the field is not {@code null}. */ public static void writeNullableToParcel( @NonNull Parcel targetParcel, @Nullable T nullableField, @NonNull ParcelWriter parcelWriter) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(parcelWriter); boolean isFieldPresent = (nullableField != null); targetParcel.writeBoolean(isFieldPresent); if (isFieldPresent) { parcelWriter.write(targetParcel, nullableField); } } /** * Reads and returns a nullable object from a source {@link Parcel}. * *

This method expects a boolean (see {@link #writeNullableToParcel(Parcel, Object, * ParcelWriter)}) that will be {@code true} if the nullable field is not {@code null} and reads * and returns it using the given {@link Callable}. */ public static T readNullableFromParcel( @NonNull Parcel sourceParcel, @NonNull ParcelReader parcelReader) { Objects.requireNonNull(sourceParcel); Objects.requireNonNull(parcelReader); if (sourceParcel.readBoolean()) { return parcelReader.read(sourceParcel); } else { return null; } } /** * Writes a {@link Map} of {@link Parcelable} keys and values to a target {@link Parcel}. * *

All keys of the {@code sourceMap} must be convertible to and from {@link String} objects. * *

Use to write a {@link Map} which will be later unparceled by {@link * #readMapFromParcel(Parcel, StringToObjectConverter, Class)}. */ public static void writeMapToParcel( @NonNull Parcel targetParcel, @NonNull Map sourceMap) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(sourceMap); Bundle tempBundle = new Bundle(); for (Map.Entry entry : sourceMap.entrySet()) { tempBundle.putParcelable(entry.getKey().toString(), entry.getValue()); } targetParcel.writeBundle(tempBundle); } /** * Reads and returns a {@link Map} of {@link Parcelable} objects from a source {@link Parcel}. * *

Use to read a {@link Map} written with {@link #writeMapToParcel(Parcel, Map)}. */ public static Map readMapFromParcel( @NonNull Parcel sourceParcel, @NonNull StringToObjectConverter stringToKeyConverter, @NonNull Class valueClass) { Objects.requireNonNull(sourceParcel); Objects.requireNonNull(stringToKeyConverter); Objects.requireNonNull(valueClass); Bundle tempBundle = Bundle.CREATOR.createFromParcel(sourceParcel); tempBundle.setClassLoader(valueClass.getClassLoader()); Map resultMap = new HashMap<>(); for (String key : tempBundle.keySet()) { V value = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ? tempBundle.getParcelable(key) : tempBundle.getParcelable(key, valueClass); resultMap.put(stringToKeyConverter.convertFromString(key), value); } return resultMap; } /** * Writes a {@link Set} of {@link Parcelable} objects to a target {@link Parcel} as an array. * *

Use to write a {@link Set} which will be unparceled by {@link #readSetFromParcel(Parcel, * Parcelable.Creator)} later. */ public static void writeSetToParcel( @NonNull Parcel targetParcel, @NonNull Set sourceSet) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(sourceSet); ArrayList tempList = new ArrayList<>(sourceSet); targetParcel.writeTypedList(tempList); } /** * Reads and returns a {@link Set} of {@link Parcelable} objects from a source {@link Parcel}. * *

Use to read a {@link Set} that was written by {@link #writeSetToParcel(Parcel, Set)}. */ public static Set readSetFromParcel( @NonNull Parcel sourceParcel, @NonNull Parcelable.Creator creator) { Objects.requireNonNull(sourceParcel); Objects.requireNonNull(creator); return new HashSet<>(Objects.requireNonNull(sourceParcel.createTypedArrayList(creator))); } /** * Writes a {@link Set} of {@link String} objects to a target {@link Parcel} as a list. * *

Use to write a {@link Set} which will be unparceled by {@link * #readStringSetFromParcel(Parcel)} later. */ public static void writeStringSetToParcel( @NonNull Parcel targetParcel, @NonNull Set sourceSet) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(sourceSet); ArrayList tempList = new ArrayList<>(sourceSet); targetParcel.writeStringList(tempList); } /** * Reads and returns a {@link Set} of {@link String} objects from a source {@link Parcel}. * *

Use to read a {@link Set} that was written by {@link #writeStringSetToParcel(Parcel, * Set)}. */ public static Set readStringSetFromParcel(@NonNull Parcel sourceParcel) { Objects.requireNonNull(sourceParcel); return new HashSet<>(Objects.requireNonNull(sourceParcel.createStringArrayList())); } /** * Writes a {@link Set} of {@link Integer} objects to a target {@link Parcel} as a list. * *

Use to write a {@link Set} which will be unparceled by {@link * #readIntegerSetFromParcel(Parcel)} later. */ public static void writeIntegerSetToParcel( @NonNull Parcel targetParcel, @NonNull Set sourceSet) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(sourceSet); int[] tempArray = new int[sourceSet.size()]; int actualArraySize = 0; for (Integer integer : sourceSet) { if (integer == null) { LogUtil.w( "Null value encountered while parceling Integer to int array; skipping" + " element"); continue; } tempArray[actualArraySize++] = integer; } // Writing the tempArray as is may write undefined values, so compress into a smaller // accurately-fit array int[] writeArray = new int[actualArraySize]; System.arraycopy(tempArray, 0, writeArray, 0, actualArraySize); targetParcel.writeInt(actualArraySize); targetParcel.writeIntArray(writeArray); } /** * Reads and returns a {@link Set} of {@link Integer} objects from a source {@link Parcel}. * *

Use to read a {@link Set} that was written by {@link #writeIntegerSetToParcel(Parcel, * Set)}. */ public static Set readIntegerSetFromParcel(@NonNull Parcel sourceParcel) { Objects.requireNonNull(sourceParcel); final int setSize = sourceParcel.readInt(); int[] tempArray = new int[setSize]; HashSet targetSet = new HashSet<>(); sourceParcel.readIntArray(tempArray); for (int index = 0; index < setSize; index++) { targetSet.add(tempArray[index]); } return targetSet; } /** * Writes a {@link List} of {@link Instant} objects to a target {@link Parcel} as an array. * *

If an error is encountered while writing any element of the input {@code sourceList}, the * element will be skipped. * *

Use to write a {@link List} which will be unparceled by {@link * #readInstantListFromParcel(Parcel)} later. */ public static void writeInstantListToParcel( @NonNull Parcel targetParcel, @NonNull List sourceList) { Objects.requireNonNull(targetParcel); Objects.requireNonNull(sourceList); long[] tempArray = new long[sourceList.size()]; int actualArraySize = 0; for (Instant instant : sourceList) { long instantAsEpochMilli; try { instantAsEpochMilli = instant.toEpochMilli(); } catch (Exception exception) { LogUtil.w( exception, "Error encountered while parceling Instant %s to long; skipping element", instant); continue; } tempArray[actualArraySize++] = instantAsEpochMilli; } // Writing the tempArray as is may write undefined values, so compress into a smaller // accurately-fit array long[] writeArray = new long[actualArraySize]; System.arraycopy(tempArray, 0, writeArray, 0, actualArraySize); targetParcel.writeInt(actualArraySize); targetParcel.writeLongArray(writeArray); } /** * Reads and returns a {@link List} of {@link Instant} objects from a source {@link Parcel}. * *

If an error is encountered while reading an element from the {@code sourceParcel}, the * element will be skipped. * *

Use to read a {@link List} that was written by {@link #writeInstantListToParcel(Parcel, * List)}. */ public static List readInstantListFromParcel(@NonNull Parcel sourceParcel) { Objects.requireNonNull(sourceParcel); final int listSize = sourceParcel.readInt(); long[] tempArray = new long[listSize]; ArrayList targetList = new ArrayList<>(listSize); sourceParcel.readLongArray(tempArray); for (int ii = 0; ii < listSize; ii++) { Instant instantFromMilli; try { instantFromMilli = Instant.ofEpochMilli(tempArray[ii]); } catch (Exception exception) { LogUtil.w( exception, "Error encountered while unparceling Instant from long %d; skipping" + " element", tempArray[ii]); continue; } targetList.add(instantFromMilli); } return targetList; } /** * A functional interface for writing a source object to a {@link Parcel}. * * @param the type of the source object to be written * @hide */ @FunctionalInterface public interface ParcelWriter { /** Writes a {@code sourceObject} to the {@code targetParcel}. */ void write(@NonNull Parcel targetParcel, @NonNull T sourceObject); } /** * A functional interface for reading an object from a {@link Parcel}. * * @param the type of the object to be read from the source parcel * @hide */ @FunctionalInterface public interface ParcelReader { /** Reads and returns an object from the {@code sourceParcel}. */ T read(@NonNull Parcel sourceParcel); } /** * A functional interface for converting a {@link String} to an object of type {@link T}. * * @param the type of the object which will be converted from a {@link String} * @hide */ @FunctionalInterface public interface StringToObjectConverter { /** Converts the {@code sourceString} to an object of the specified type. */ T convertFromString(@NonNull String sourceString); } }