351 lines
13 KiB
Java
351 lines
13 KiB
Java
/*
|
|
* 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}.
|
|
*
|
|
* <p>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 <T> void writeNullableToParcel(
|
|
@NonNull Parcel targetParcel,
|
|
@Nullable T nullableField,
|
|
@NonNull ParcelWriter<T> 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}.
|
|
*
|
|
* <p>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> T readNullableFromParcel(
|
|
@NonNull Parcel sourceParcel, @NonNull ParcelReader<T> 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}.
|
|
*
|
|
* <p>All keys of the {@code sourceMap} must be convertible to and from {@link String} objects.
|
|
*
|
|
* <p>Use to write a {@link Map} which will be later unparceled by {@link
|
|
* #readMapFromParcel(Parcel, StringToObjectConverter, Class)}.
|
|
*/
|
|
public static <K, V extends Parcelable> void writeMapToParcel(
|
|
@NonNull Parcel targetParcel, @NonNull Map<K, V> sourceMap) {
|
|
Objects.requireNonNull(targetParcel);
|
|
Objects.requireNonNull(sourceMap);
|
|
|
|
Bundle tempBundle = new Bundle();
|
|
for (Map.Entry<K, V> 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}.
|
|
*
|
|
* <p>Use to read a {@link Map} written with {@link #writeMapToParcel(Parcel, Map)}.
|
|
*/
|
|
public static <K, V extends Parcelable> Map<K, V> readMapFromParcel(
|
|
@NonNull Parcel sourceParcel,
|
|
@NonNull StringToObjectConverter<K> stringToKeyConverter,
|
|
@NonNull Class<V> valueClass) {
|
|
Objects.requireNonNull(sourceParcel);
|
|
Objects.requireNonNull(stringToKeyConverter);
|
|
Objects.requireNonNull(valueClass);
|
|
|
|
Bundle tempBundle = Bundle.CREATOR.createFromParcel(sourceParcel);
|
|
tempBundle.setClassLoader(valueClass.getClassLoader());
|
|
Map<K, V> 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.
|
|
*
|
|
* <p>Use to write a {@link Set} which will be unparceled by {@link #readSetFromParcel(Parcel,
|
|
* Parcelable.Creator)} later.
|
|
*/
|
|
public static <T extends Parcelable> void writeSetToParcel(
|
|
@NonNull Parcel targetParcel, @NonNull Set<T> sourceSet) {
|
|
Objects.requireNonNull(targetParcel);
|
|
Objects.requireNonNull(sourceSet);
|
|
|
|
ArrayList<T> tempList = new ArrayList<>(sourceSet);
|
|
targetParcel.writeTypedList(tempList);
|
|
}
|
|
|
|
/**
|
|
* Reads and returns a {@link Set} of {@link Parcelable} objects from a source {@link Parcel}.
|
|
*
|
|
* <p>Use to read a {@link Set} that was written by {@link #writeSetToParcel(Parcel, Set)}.
|
|
*/
|
|
public static <T extends Parcelable> Set<T> readSetFromParcel(
|
|
@NonNull Parcel sourceParcel, @NonNull Parcelable.Creator<T> 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.
|
|
*
|
|
* <p>Use to write a {@link Set} which will be unparceled by {@link
|
|
* #readStringSetFromParcel(Parcel)} later.
|
|
*/
|
|
public static void writeStringSetToParcel(
|
|
@NonNull Parcel targetParcel, @NonNull Set<String> sourceSet) {
|
|
Objects.requireNonNull(targetParcel);
|
|
Objects.requireNonNull(sourceSet);
|
|
|
|
ArrayList<String> tempList = new ArrayList<>(sourceSet);
|
|
targetParcel.writeStringList(tempList);
|
|
}
|
|
|
|
/**
|
|
* Reads and returns a {@link Set} of {@link String} objects from a source {@link Parcel}.
|
|
*
|
|
* <p>Use to read a {@link Set} that was written by {@link #writeStringSetToParcel(Parcel,
|
|
* Set)}.
|
|
*/
|
|
public static Set<String> 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.
|
|
*
|
|
* <p>Use to write a {@link Set} which will be unparceled by {@link
|
|
* #readIntegerSetFromParcel(Parcel)} later.
|
|
*/
|
|
public static void writeIntegerSetToParcel(
|
|
@NonNull Parcel targetParcel, @NonNull Set<Integer> 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}.
|
|
*
|
|
* <p>Use to read a {@link Set} that was written by {@link #writeIntegerSetToParcel(Parcel,
|
|
* Set)}.
|
|
*/
|
|
public static Set<Integer> readIntegerSetFromParcel(@NonNull Parcel sourceParcel) {
|
|
Objects.requireNonNull(sourceParcel);
|
|
|
|
final int setSize = sourceParcel.readInt();
|
|
int[] tempArray = new int[setSize];
|
|
HashSet<Integer> 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.
|
|
*
|
|
* <p>If an error is encountered while writing any element of the input {@code sourceList}, the
|
|
* element will be skipped.
|
|
*
|
|
* <p>Use to write a {@link List} which will be unparceled by {@link
|
|
* #readInstantListFromParcel(Parcel)} later.
|
|
*/
|
|
public static void writeInstantListToParcel(
|
|
@NonNull Parcel targetParcel, @NonNull List<Instant> 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}.
|
|
*
|
|
* <p>If an error is encountered while reading an element from the {@code sourceParcel}, the
|
|
* element will be skipped.
|
|
*
|
|
* <p>Use to read a {@link List} that was written by {@link #writeInstantListToParcel(Parcel,
|
|
* List)}.
|
|
*/
|
|
public static List<Instant> readInstantListFromParcel(@NonNull Parcel sourceParcel) {
|
|
Objects.requireNonNull(sourceParcel);
|
|
|
|
final int listSize = sourceParcel.readInt();
|
|
long[] tempArray = new long[listSize];
|
|
ArrayList<Instant> 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 <T> the type of the source object to be written
|
|
* @hide
|
|
*/
|
|
@FunctionalInterface
|
|
public interface ParcelWriter<T> {
|
|
/** 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 <T> the type of the object to be read from the source parcel
|
|
* @hide
|
|
*/
|
|
@FunctionalInterface
|
|
public interface ParcelReader<T> {
|
|
/** 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 <T> the type of the object which will be converted from a {@link String}
|
|
* @hide
|
|
*/
|
|
@FunctionalInterface
|
|
public interface StringToObjectConverter<T> {
|
|
/** Converts the {@code sourceString} to an object of the specified type. */
|
|
T convertFromString(@NonNull String sourceString);
|
|
}
|
|
}
|