Merge "Add @hide Bundle.getParcelable() with explicit type"

This commit is contained in:
Bernardo Rufino
2022-02-21 13:07:13 +00:00
committed by Gerrit Code Review
3 changed files with 76 additions and 29 deletions

View File

@@ -31,7 +31,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Function;
/** /**
* A mapping from String keys to values of various types. In most cases, you * A mapping from String keys to values of various types. In most cases, you
@@ -252,11 +252,10 @@ public class BaseBundle {
if (size == 0) { if (size == 0) {
return null; return null;
} }
Object o = getValueAt(0);
try { try {
return (String) o; return getValueAt(0, String.class);
} catch (ClassCastException e) { } catch (ClassCastException | BadParcelableException e) {
typeWarning("getPairValue()", o, "String", e); typeWarning("getPairValue()", /* value */ null, "String", e);
return null; return null;
} }
} }
@@ -309,7 +308,7 @@ public class BaseBundle {
} }
for (int i = 0, n = mMap.size(); i < n; i++) { for (int i = 0, n = mMap.size(); i < n; i++) {
// Triggers deserialization of i-th item, if needed // Triggers deserialization of i-th item, if needed
getValueAt(i); getValueAt(i, /* clazz */ null);
} }
} }
} }
@@ -324,8 +323,21 @@ public class BaseBundle {
* @hide * @hide
*/ */
final Object getValue(String key) { final Object getValue(String key) {
return getValue(key, /* clazz */ null);
}
/**
* Returns the value for key {@code key} for expected return type {@param clazz} (or {@code
* null} for no type check).
*
* This call should always be made after {@link #unparcel()} or inside a lock after making sure
* {@code mMap} is not null.
*
* @hide
*/
final <T> T getValue(String key, @Nullable Class<T> clazz) {
int i = mMap.indexOfKey(key); int i = mMap.indexOfKey(key);
return (i >= 0) ? getValueAt(i) : null; return (i >= 0) ? getValueAt(i, clazz) : null;
} }
/** /**
@@ -336,11 +348,12 @@ public class BaseBundle {
* *
* @hide * @hide
*/ */
final Object getValueAt(int i) { @SuppressWarnings("unchecked")
final <T> T getValueAt(int i, @Nullable Class<T> clazz) {
Object object = mMap.valueAt(i); Object object = mMap.valueAt(i);
if (object instanceof Supplier<?>) { if (object instanceof Function<?, ?>) {
try { try {
object = ((Supplier<?>) object).get(); object = ((Function<Class<?>, ?>) object).apply(clazz);
} catch (BadParcelableException e) { } catch (BadParcelableException e) {
if (sShouldDefuse) { if (sShouldDefuse) {
Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
@@ -351,7 +364,7 @@ public class BaseBundle {
} }
mMap.setValueAt(i, object); mMap.setValueAt(i, object);
} }
return object; return (clazz != null) ? clazz.cast(object) : (T) object;
} }
private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel, private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
@@ -528,7 +541,7 @@ public class BaseBundle {
} else { } else {
// Following semantic above of failing in case we get a serialized value vs a // Following semantic above of failing in case we get a serialized value vs a
// deserialized one, we'll compare the map. If a certain element hasn't been // deserialized one, we'll compare the map. If a certain element hasn't been
// deserialized yet, it's a Supplier (or more specifically a LazyValue, but let's // deserialized yet, it's a function object (or more specifically a LazyValue, but let's
// pretend we don't know that here :P), we'll use that element's equality comparison as // pretend we don't know that here :P), we'll use that element's equality comparison as
// map naturally does. That will takes care of comparing the payload if needed (see // map naturally does. That will takes care of comparing the payload if needed (see
// Parcel.readLazyValue() for details). // Parcel.readLazyValue() for details).
@@ -982,15 +995,19 @@ public class BaseBundle {
} }
// Log a message if the value was non-null but not of the expected type // Log a message if the value was non-null but not of the expected type
void typeWarning(String key, Object value, String className, void typeWarning(String key, @Nullable Object value, String className,
Object defaultValue, ClassCastException e) { Object defaultValue, RuntimeException e) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Key "); sb.append("Key ");
sb.append(key); sb.append(key);
sb.append(" expected "); sb.append(" expected ");
sb.append(className); sb.append(className);
if (value != null) {
sb.append(" but value was a "); sb.append(" but value was a ");
sb.append(value.getClass().getName()); sb.append(value.getClass().getName());
} else {
sb.append(" but value was of a different type ");
}
sb.append(". The default value "); sb.append(". The default value ");
sb.append(defaultValue); sb.append(defaultValue);
sb.append(" was returned."); sb.append(" was returned.");
@@ -998,8 +1015,7 @@ public class BaseBundle {
Log.w(TAG, "Attempt to cast generated internal exception:", e); Log.w(TAG, "Attempt to cast generated internal exception:", e);
} }
void typeWarning(String key, Object value, String className, void typeWarning(String key, @Nullable Object value, String className, RuntimeException e) {
ClassCastException e) {
typeWarning(key, value, className, "<null>", e); typeWarning(key, value, className, "<null>", e);
} }

View File

@@ -16,6 +16,9 @@
package android.os; package android.os;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage; import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap; import android.util.ArrayMap;
@@ -912,6 +915,33 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
} }
} }
/**
* Returns the value associated with the given key, or {@code null} if
* no mapping of the desired type exists for the given key or a {@code null}
* value is explicitly associated with the key.
*
* <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
* you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
* Otherwise, this method might throw an exception or return {@code null}.
*
* @param key a String, or {@code null}
* @param clazz The type of the object expected or {@code null} for performing no checks.
* @return a Parcelable value, or {@code null}
*
* @hide
*/
@SuppressWarnings("unchecked")
@Nullable
public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
unparcel();
try {
return getValue(key, requireNonNull(clazz));
} catch (ClassCastException | BadParcelableException e) {
typeWarning(key, /* value */ null, "Parcelable", e);
return null;
}
}
/** /**
* Returns the value associated with the given key, or {@code null} if * Returns the value associated with the given key, or {@code null} if
* no mapping of the desired type exists for the given key or a null * no mapping of the desired type exists for the given key or a null

View File

@@ -4312,18 +4312,19 @@ public final class Parcel {
} }
/** /**
* This will return a {@link Supplier} for length-prefixed types that deserializes the object * This will return a {@link Function} for length-prefixed types that deserializes the object
* when {@link Supplier#get()} is called, for other types it will return the object itself. * when {@link Function#apply} is called with the expected class of the return object (or {@code
* null} for no type check), for other types it will return the object itself.
* *
* <p>After calling {@link Supplier#get()} the parcel cursor will not change. Note that you * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that
* shouldn't recycle the parcel, not at least until all objects have been retrieved. No * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No
* synchronization attempts are made. * synchronization attempts are made.
* *
* </p>The supplier returned implements {@link #equals(Object)} and {@link #hashCode()}. Two * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
* suppliers are equal if either of the following is true: * function objects are equal if either of the following is true:
* <ul> * <ul>
* <li>{@link Supplier#get()} has been called on both and both objects returned are equal. * <li>{@link Function#apply} has been called on both and both objects returned are equal.
* <li>{@link Supplier#get()} hasn't been called on either one and everything below is true: * <li>{@link Function#apply} hasn't been called on either one and everything below is true:
* <ul> * <ul>
* <li>The {@code loader} parameters used to retrieve each are equal. * <li>The {@code loader} parameters used to retrieve each are equal.
* <li>They both have the same type. * <li>They both have the same type.
@@ -4350,7 +4351,7 @@ public final class Parcel {
} }
private static final class LazyValue implements Supplier<Object> { private static final class LazyValue implements Function<Class<?>, Object> {
/** /**
* | 4B | 4B | * | 4B | 4B |
* mSource = Parcel{... | type | length | object | ...} * mSource = Parcel{... | type | length | object | ...}
@@ -4382,7 +4383,7 @@ public final class Parcel {
} }
@Override @Override
public Object get() { public Object apply(@Nullable Class<?> clazz) {
Parcel source = mSource; Parcel source = mSource;
if (source != null) { if (source != null) {
synchronized (source) { synchronized (source) {
@@ -4391,7 +4392,7 @@ public final class Parcel {
int restore = source.dataPosition(); int restore = source.dataPosition();
try { try {
source.setDataPosition(mPosition); source.setDataPosition(mPosition);
mObject = source.readValue(mLoader); mObject = source.readValue(mLoader, clazz);
} finally { } finally {
source.setDataPosition(restore); source.setDataPosition(restore);
} }