Merge "Lazy bundle"

This commit is contained in:
Bernardo Rufino
2021-08-31 09:18:47 +00:00
committed by Gerrit Code Review
3 changed files with 560 additions and 170 deletions

View File

@@ -31,6 +31,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Set;
import java.util.function.Supplier;
/**
* A mapping from String keys to values of various types. In most cases, you
@@ -38,7 +39,8 @@ import java.util.Set;
* {@link PersistableBundle} subclass.
*/
public class BaseBundle {
private static final String TAG = "Bundle";
/** @hide */
protected static final String TAG = "Bundle";
static final boolean DEBUG = false;
// Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -95,7 +97,7 @@ public class BaseBundle {
Parcel mParcelledData = null;
/**
* Whether {@link #mParcelledData} was generated by native coed or not.
* Whether {@link #mParcelledData} was generated by native code or not.
*/
private boolean mParcelledByNative;
@@ -198,7 +200,7 @@ public class BaseBundle {
if (size == 0) {
return null;
}
Object o = mMap.valueAt(0);
Object o = getValueAt(0);
try {
return (String) o;
} catch (ClassCastException e) {
@@ -229,7 +231,12 @@ public class BaseBundle {
* using the currently assigned class loader.
*/
@UnsupportedAppUsage
/* package */ void unparcel() {
final void unparcel() {
unparcel(/* itemwise */ false);
}
/** Deserializes the underlying data and each item if {@code itemwise} is true. */
final void unparcel(boolean itemwise) {
synchronized (this) {
final Parcel source = mParcelledData;
if (source != null) {
@@ -241,9 +248,42 @@ public class BaseBundle {
+ ": no parcelled data");
}
}
if (itemwise) {
for (int i = 0, n = mMap.size(); i < n; i++) {
// Triggers deserialization of i-th item, if needed
getValueAt(i);
}
}
}
}
/**
* Returns the value for key {@code key}.
*
* @hide
*/
final Object getValue(String key) {
int i = mMap.indexOfKey(key);
return (i >= 0) ? getValueAt(i) : null;
}
/**
* Returns the value for a certain position in the array map.
*
* @hide
*/
final Object getValueAt(int i) {
Object object = mMap.valueAt(i);
if (object instanceof Supplier<?>) {
Supplier<?> supplier = (Supplier<?>) object;
synchronized (this) {
object = supplier.get();
}
mMap.setValueAt(i, object);
}
return object;
}
private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
boolean parcelledByNative) {
if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
@@ -282,15 +322,8 @@ public class BaseBundle {
map.ensureCapacity(count);
}
try {
if (parcelledByNative) {
// If it was parcelled by native code, then the array map keys aren't sorted
// by their hash codes, so use the safe (slow) one.
parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
} else {
// If parcelled by Java, we know the contents are sorted properly,
// so we can use ArrayMap.append().
parcelledData.readArrayMapInternal(map, count, mClassLoader);
}
recycleParcel &= parcelledData.readArrayMap(map, count, !parcelledByNative,
/* lazy */ true, mClassLoader);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -342,7 +375,7 @@ public class BaseBundle {
/** @hide */
ArrayMap<String, Object> getMap() {
unparcel();
unparcel(/* itemwise */ true);
return mMap;
}
@@ -400,7 +433,12 @@ public class BaseBundle {
}
/**
* @hide This kind-of does an equality comparison. Kind-of.
* Performs a loose equality check, which means there can be false negatives but if the method
* returns true than both objects are guaranteed to be equal.
*
* The point is that this method is a light-weight check in performance terms.
*
* @hide
*/
public boolean kindofEquals(BaseBundle other) {
if (other == null) {
@@ -415,6 +453,12 @@ public class BaseBundle {
} else if (isParcelled()) {
return mParcelledData.compareData(other.mParcelledData) == 0;
} else {
// 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 yet, it's a Supplier (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
// map naturally does. That will takes care of comparing the payload if needed (see
// Parcel.readLazyValue() for details).
return mMap.equals(other.mMap);
}
}
@@ -453,7 +497,7 @@ public class BaseBundle {
final int N = fromMap.size();
mMap = new ArrayMap<>(N);
for (int i = 0; i < N; i++) {
mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
mMap.append(fromMap.keyAt(i), deepCopyValue(from.getValueAt(i)));
}
}
} else {
@@ -526,7 +570,7 @@ public class BaseBundle {
@Nullable
public Object get(String key) {
unparcel();
return mMap.get(key);
return getValue(key);
}
/**
@@ -1001,7 +1045,7 @@ public class BaseBundle {
*/
char getChar(String key, char defaultValue) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return defaultValue;
}
@@ -1266,7 +1310,7 @@ public class BaseBundle {
@Nullable
Serializable getSerializable(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1289,7 +1333,7 @@ public class BaseBundle {
@Nullable
ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1312,7 +1356,7 @@ public class BaseBundle {
@Nullable
ArrayList<String> getStringArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1335,7 +1379,7 @@ public class BaseBundle {
@Nullable
ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1404,7 +1448,7 @@ public class BaseBundle {
@Nullable
short[] getShortArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1427,7 +1471,7 @@ public class BaseBundle {
@Nullable
char[] getCharArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1496,7 +1540,7 @@ public class BaseBundle {
@Nullable
float[] getFloatArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1585,7 +1629,7 @@ public class BaseBundle {
void writeToParcelInner(Parcel parcel, int flags) {
// If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
if (parcel.hasReadWriteHelper()) {
unparcel();
unparcel(/* itemwise */ true);
}
// Keep implementation in sync with writeToParcel() in
// frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -1660,10 +1704,13 @@ public class BaseBundle {
}
if (parcel.hasReadWriteHelper()) {
// If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
// unparcel right away.
// If the parcel has a read-write helper, it's better to deserialize immediately
// otherwise the helper would have to either maintain valid state long after the bundle
// had been constructed with parcel or to make sure they trigger deserialization of the
// bundle immediately; neither of which is obvious.
synchronized (this) {
initializeFromParcelLocked(parcel, /*recycleParcel=*/ false, isNativeBundle);
unparcel(/* itemwise */ true);
}
return;
}

View File

@@ -330,47 +330,9 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
// It's been unparcelled, so we need to walk the map
for (int i=mMap.size()-1; i>=0; i--) {
Object obj = mMap.valueAt(i);
if (obj instanceof Parcelable) {
if ((((Parcelable)obj).describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
fdFound = true;
break;
}
} else if (obj instanceof Parcelable[]) {
Parcelable[] array = (Parcelable[]) obj;
for (int n = array.length - 1; n >= 0; n--) {
Parcelable p = array[n];
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
fdFound = true;
break;
}
}
} else if (obj instanceof SparseArray) {
SparseArray<? extends Parcelable> array =
(SparseArray<? extends Parcelable>) obj;
for (int n = array.size() - 1; n >= 0; n--) {
Parcelable p = array.valueAt(n);
if (p != null && (p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
fdFound = true;
break;
}
}
} else if (obj instanceof ArrayList) {
ArrayList array = (ArrayList) obj;
// an ArrayList here might contain either Strings or
// Parcelables; only look inside for Parcelables
if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
for (int n = array.size() - 1; n >= 0; n--) {
Parcelable p = (Parcelable) array.get(n);
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
fdFound = true;
break;
}
}
}
if (Parcel.hasFileDescriptors(obj)) {
fdFound = true;
break;
}
}
}
@@ -391,7 +353,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Bundle filterValues() {
unparcel();
unparcel(/* itemwise */ true);
Bundle bundle = this;
if (mMap != null) {
ArrayMap<String, Object> map = mMap;
@@ -972,7 +934,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public Bundle getBundle(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -999,7 +961,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public <T extends Parcelable> T getParcelable(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1026,7 +988,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public Parcelable[] getParcelableArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1053,7 +1015,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1077,7 +1039,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
@Nullable
public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
Object o = getValue(key);
if (o == null) {
return null;
}
@@ -1300,7 +1262,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
try {
super.writeToParcelInner(parcel, flags);
writeToParcelInner(parcel, flags);
} finally {
parcel.restoreAllowFds(oldAllowFds);
}
@@ -1312,7 +1274,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param parcel The parcel to overwrite this bundle from.
*/
public void readFromParcel(Parcel parcel) {
super.readFromParcelInner(parcel);
readFromParcelInner(parcel);
mFlags = FLAG_ALLOW_FDS;
maybePrefillHasFds();
}

View File

@@ -26,6 +26,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
import android.util.SizeF;
import android.util.Slog;
@@ -56,7 +57,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
/**
* Container for a message (data and object references) that can
@@ -648,6 +651,65 @@ public final class Parcel {
return nativeHasFileDescriptors(mNativePtr);
}
/**
* Check if the object used in {@link #readValue(ClassLoader)} / {@link #writeValue(Object)}
* has file descriptors.
*
* <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method
* for that.
*
* @throws IllegalArgumentException if you provide any object not supported by above methods.
* Most notably, if you pass {@link Parcel}, this method will throw, for that check
* {@link Parcel#hasFileDescriptors()}
*
* @hide
*/
public static boolean hasFileDescriptors(Object value) {
getValueType(value); // Will throw if value is not supported
if (value instanceof LazyValue) {
return ((LazyValue) value).hasFileDescriptors();
} else if (value instanceof Parcelable) {
if ((((Parcelable) value).describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
return true;
}
} else if (value instanceof Parcelable[]) {
Parcelable[] array = (Parcelable[]) value;
for (int n = array.length - 1; n >= 0; n--) {
Parcelable p = array[n];
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
return true;
}
}
} else if (value instanceof SparseArray<?>) {
SparseArray<?> array = (SparseArray<?>) value;
for (int n = array.size() - 1; n >= 0; n--) {
Object object = array.valueAt(n);
if (object instanceof Parcelable) {
Parcelable p = (Parcelable) object;
if (p != null && (p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
return true;
}
}
}
} else if (value instanceof ArrayList<?>) {
ArrayList<?> array = (ArrayList<?>) value;
for (int n = array.size() - 1; n >= 0; n--) {
Object object = array.get(n);
if (object instanceof Parcelable) {
Parcelable p = (Parcelable) object;
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
return true;
}
}
}
}
return false;
}
/**
* Store or read an IBinder interface token in the parcel at the current
* {@link #dataPosition}. This is used to validate that the marshalled
@@ -1795,108 +1857,206 @@ public final class Parcel {
* should be used).</p>
*/
public final void writeValue(@Nullable Object v) {
if (v instanceof LazyValue) {
LazyValue value = (LazyValue) v;
value.writeToParcel(this);
return;
}
int type = getValueType(v);
writeInt(type);
if (isLengthPrefixed(type)) {
// Length
int length = dataPosition();
writeInt(-1); // Placeholder
// Object
int start = dataPosition();
writeValue(type, v);
int end = dataPosition();
// Backpatch length
setDataPosition(length);
writeInt(end - start);
setDataPosition(end);
} else {
writeValue(type, v);
}
}
/** @hide */
public static int getValueType(@Nullable Object v) {
if (v == null) {
writeInt(VAL_NULL);
return VAL_NULL;
} else if (v instanceof String) {
writeInt(VAL_STRING);
writeString((String) v);
return VAL_STRING;
} else if (v instanceof Integer) {
writeInt(VAL_INTEGER);
writeInt((Integer) v);
return VAL_INTEGER;
} else if (v instanceof Map) {
writeInt(VAL_MAP);
writeMap((Map) v);
return VAL_MAP;
} else if (v instanceof Bundle) {
// Must be before Parcelable
writeInt(VAL_BUNDLE);
writeBundle((Bundle) v);
return VAL_BUNDLE;
} else if (v instanceof PersistableBundle) {
writeInt(VAL_PERSISTABLEBUNDLE);
writePersistableBundle((PersistableBundle) v);
// Must be before Parcelable
return VAL_PERSISTABLEBUNDLE;
} else if (v instanceof SizeF) {
// Must be before Parcelable
return VAL_SIZEF;
} else if (v instanceof Parcelable) {
// IMPOTANT: cases for classes that implement Parcelable must
// come before the Parcelable case, so that their specific VAL_*
// come before the Parcelable case, so that their speci fic VAL_*
// types will be written.
writeInt(VAL_PARCELABLE);
writeParcelable((Parcelable) v, 0);
return VAL_PARCELABLE;
} else if (v instanceof Short) {
writeInt(VAL_SHORT);
writeInt(((Short) v).intValue());
return VAL_SHORT;
} else if (v instanceof Long) {
writeInt(VAL_LONG);
writeLong((Long) v);
return VAL_LONG;
} else if (v instanceof Float) {
writeInt(VAL_FLOAT);
writeFloat((Float) v);
return VAL_FLOAT;
} else if (v instanceof Double) {
writeInt(VAL_DOUBLE);
writeDouble((Double) v);
return VAL_DOUBLE;
} else if (v instanceof Boolean) {
writeInt(VAL_BOOLEAN);
writeInt((Boolean) v ? 1 : 0);
return VAL_BOOLEAN;
} else if (v instanceof CharSequence) {
// Must be after String
writeInt(VAL_CHARSEQUENCE);
writeCharSequence((CharSequence) v);
return VAL_CHARSEQUENCE;
} else if (v instanceof List) {
writeInt(VAL_LIST);
writeList((List) v);
return VAL_LIST;
} else if (v instanceof SparseArray) {
writeInt(VAL_SPARSEARRAY);
writeSparseArray((SparseArray) v);
return VAL_SPARSEARRAY;
} else if (v instanceof boolean[]) {
writeInt(VAL_BOOLEANARRAY);
writeBooleanArray((boolean[]) v);
return VAL_BOOLEANARRAY;
} else if (v instanceof byte[]) {
writeInt(VAL_BYTEARRAY);
writeByteArray((byte[]) v);
return VAL_BYTEARRAY;
} else if (v instanceof String[]) {
writeInt(VAL_STRINGARRAY);
writeStringArray((String[]) v);
return VAL_STRINGARRAY;
} else if (v instanceof CharSequence[]) {
// Must be after String[] and before Object[]
writeInt(VAL_CHARSEQUENCEARRAY);
writeCharSequenceArray((CharSequence[]) v);
return VAL_CHARSEQUENCEARRAY;
} else if (v instanceof IBinder) {
writeInt(VAL_IBINDER);
writeStrongBinder((IBinder) v);
return VAL_IBINDER;
} else if (v instanceof Parcelable[]) {
writeInt(VAL_PARCELABLEARRAY);
writeParcelableArray((Parcelable[]) v, 0);
return VAL_PARCELABLEARRAY;
} else if (v instanceof int[]) {
writeInt(VAL_INTARRAY);
writeIntArray((int[]) v);
return VAL_INTARRAY;
} else if (v instanceof long[]) {
writeInt(VAL_LONGARRAY);
writeLongArray((long[]) v);
return VAL_LONGARRAY;
} else if (v instanceof Byte) {
writeInt(VAL_BYTE);
writeInt((Byte) v);
return VAL_BYTE;
} else if (v instanceof Size) {
writeInt(VAL_SIZE);
writeSize((Size) v);
} else if (v instanceof SizeF) {
writeInt(VAL_SIZEF);
writeSizeF((SizeF) v);
return VAL_SIZE;
} else if (v instanceof double[]) {
writeInt(VAL_DOUBLEARRAY);
writeDoubleArray((double[]) v);
return VAL_DOUBLEARRAY;
} else {
Class<?> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) {
// Only pure Object[] are written here, Other arrays of non-primitive types are
// handled by serialization as this does not record the component type.
writeInt(VAL_OBJECTARRAY);
writeArray((Object[]) v);
return VAL_OBJECTARRAY;
} else if (v instanceof Serializable) {
// Must be last
writeInt(VAL_SERIALIZABLE);
writeSerializable((Serializable) v);
return VAL_SERIALIZABLE;
} else {
throw new RuntimeException("Parcel: unable to marshal value " + v);
throw new IllegalArgumentException("Parcel: unknown type for value " + v);
}
}
}
/**
* Writes value {@code v} in the parcel. This does NOT write the int representing the type
* first.
*
* @hide
*/
public void writeValue(int type, @Nullable Object v) {
switch (type) {
case VAL_NULL:
break;
case VAL_STRING:
writeString((String) v);
break;
case VAL_INTEGER:
writeInt((Integer) v);
break;
case VAL_MAP:
writeMap((Map) v);
break;
case VAL_BUNDLE:
writeBundle((Bundle) v);
break;
case VAL_PERSISTABLEBUNDLE:
writePersistableBundle((PersistableBundle) v);
break;
case VAL_PARCELABLE:
writeParcelable((Parcelable) v, 0);
break;
case VAL_SHORT:
writeInt(((Short) v).intValue());
break;
case VAL_LONG:
writeLong((Long) v);
break;
case VAL_FLOAT:
writeFloat((Float) v);
break;
case VAL_DOUBLE:
writeDouble((Double) v);
break;
case VAL_BOOLEAN:
writeInt((Boolean) v ? 1 : 0);
break;
case VAL_CHARSEQUENCE:
writeCharSequence((CharSequence) v);
break;
case VAL_LIST:
writeList((List) v);
break;
case VAL_SPARSEARRAY:
writeSparseArray((SparseArray) v);
break;
case VAL_BOOLEANARRAY:
writeBooleanArray((boolean[]) v);
break;
case VAL_BYTEARRAY:
writeByteArray((byte[]) v);
break;
case VAL_STRINGARRAY:
writeStringArray((String[]) v);
break;
case VAL_CHARSEQUENCEARRAY:
writeCharSequenceArray((CharSequence[]) v);
break;
case VAL_IBINDER:
writeStrongBinder((IBinder) v);
break;
case VAL_PARCELABLEARRAY:
writeParcelableArray((Parcelable[]) v, 0);
break;
case VAL_INTARRAY:
writeIntArray((int[]) v);
break;
case VAL_LONGARRAY:
writeLongArray((long[]) v);
break;
case VAL_BYTE:
writeInt((Byte) v);
break;
case VAL_SIZE:
writeSize((Size) v);
break;
case VAL_SIZEF:
writeSizeF((SizeF) v);
break;
case VAL_DOUBLEARRAY:
writeDoubleArray((double[]) v);
break;
case VAL_OBJECTARRAY:
writeArray((Object[]) v);
break;
case VAL_SERIALIZABLE:
writeSerializable((Serializable) v);
break;
default:
throw new RuntimeException("Parcel: unable to marshal value " + v);
}
}
/**
* Flatten the name of the class of the Parcelable and its contents
@@ -3167,7 +3327,180 @@ public final class Parcel {
@Nullable
public final Object readValue(@Nullable ClassLoader loader) {
int type = readInt();
final Object object;
if (isLengthPrefixed(type)) {
int length = readInt();
int start = dataPosition();
object = readValue(type, loader);
int actual = dataPosition() - start;
if (actual != length) {
Log.w(TAG,
"Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
+ " consumed " + actual + " bytes, but " + length + " expected.");
}
} else {
object = readValue(type, loader);
}
return object;
}
/**
* This will return a {@link Supplier} for length-prefixed types that deserializes the object
* when {@link Supplier#get()} is called, for other types it will return the object itself.
*
* <p>After calling {@link Supplier#get()} the parcel cursor will not change. Note that you
* shouldn't recycle the parcel, not at least until all objects have been retrieved. No
* synchronization attempts are made.
*
* </p>The supplier returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
* suppliers are equal if either of the following is true:
* <ul>
* <li>{@link Supplier#get()} 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:
* <ul>
* <li>The {@code loader} parameters used to retrieve each are equal.
* <li>They both have the same type.
* <li>They have the same payload length.
* <li>Their binary content is the same.
* </ul>
* </ul>
*
* @hide
*/
@Nullable
public Object readLazyValue(@Nullable ClassLoader loader) {
int start = dataPosition();
int type = readInt();
if (isLengthPrefixed(type)) {
int length = readInt();
setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
return new LazyValue(this, start, length, type, loader);
} else {
return readValue(type, loader);
}
}
private static final class LazyValue implements Supplier<Object> {
private final int mPosition;
private final int mLength;
private final int mType;
@Nullable private final ClassLoader mLoader;
@Nullable private Parcel mSource;
@Nullable private Object mObject;
@Nullable private Parcel mValueParcel;
LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
mSource = source;
mPosition = position;
mLength = length;
mType = type;
mLoader = loader;
}
@Override
public Object get() {
if (mObject == null) {
int restore = mSource.dataPosition();
try {
mSource.setDataPosition(mPosition);
mObject = mSource.readValue(mLoader);
} finally {
mSource.setDataPosition(restore);
}
mSource = null;
if (mValueParcel != null) {
mValueParcel.recycle();
mValueParcel = null;
}
}
return mObject;
}
public void writeToParcel(Parcel out) {
if (mObject == null) {
int restore = mSource.dataPosition();
try {
mSource.setDataPosition(mPosition);
out.writeInt(mSource.readInt()); // Type
out.writeInt(mSource.readInt()); // Length
out.appendFrom(mSource, mSource.dataPosition(), mLength);
} finally {
mSource.setDataPosition(restore);
}
} else {
out.writeValue(mObject);
}
}
public boolean hasFileDescriptors() {
return getValueParcel().hasFileDescriptors();
}
@Override
public String toString() {
return mObject == null
? "Supplier{" + valueTypeToString(mType) + "@" + mPosition + "+" + mLength + '}'
: "Supplier{" + mObject + "}";
}
/**
* We're checking if the *lazy value* is equal to another one, not if the *object*
* represented by the lazy value is equal to the other one. So, if there are two lazy values
* and one of them has been deserialized but the other hasn't this will always return false.
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof LazyValue)) {
return false;
}
LazyValue value = (LazyValue) other;
// Check if they are either both serialized or both deserialized
if ((mObject == null) != (value.mObject == null)) {
return false;
}
// If both are deserialized, compare the live objects
if (mObject != null) {
return mObject.equals(value.mObject);
}
// Better safely fail here since this could mean we get different objects
if (!Objects.equals(mLoader, value.mLoader)) {
return false;
}
// Otherwise compare metadata prior to comparing payload
if (mType != value.mType || mLength != value.mLength) {
return false;
}
// Finally we compare the payload
return getValueParcel().compareData(value.getValueParcel()) == 0;
}
@Override
public int hashCode() {
return Objects.hash(mObject, mLoader, mType, mLength);
}
/** This extracts the parcel section responsible for the object and returns it. */
private Parcel getValueParcel() {
if (mValueParcel == null) {
mValueParcel = Parcel.obtain();
// mLength is the length of object representation, excluding the type and length.
// mPosition is the position of the entire value container, right before the type.
// So, we add 4 bytes for the type + 4 bytes for the length written.
mValueParcel.appendFrom(mSource, mPosition, mLength + 8);
}
return mValueParcel;
}
}
/**
* Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
* type first.
*/
@Nullable
private Object readValue(int type, @Nullable ClassLoader loader) {
switch (type) {
case VAL_NULL:
return null;
@@ -3266,6 +3599,20 @@ public final class Parcel {
}
}
private boolean isLengthPrefixed(int type) {
switch (type) {
case VAL_PARCELABLE:
case VAL_PARCELABLEARRAY:
case VAL_LIST:
case VAL_SPARSEARRAY:
case VAL_BUNDLE:
case VAL_SERIALIZABLE:
return true;
default:
return false;
}
}
/**
* Read and return a new Parcelable from the parcel. The given class loader
* will be used to load any enclosed Parcelables. If it is null, the default
@@ -3564,49 +3911,49 @@ public final class Parcel {
}
}
/* package */ void readArrayMapInternal(@NonNull ArrayMap outVal, int N,
@Nullable ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
}
int startPos;
while (N > 0) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
String key = readString();
Object value = readValue(loader);
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
outVal.append(key, value);
N--;
}
outVal.validate();
/* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
int size, @Nullable ClassLoader loader) {
readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
}
/* package */ void readArrayMapSafelyInternal(@NonNull ArrayMap outVal, int N,
@Nullable ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading safely " + N + " ArrayMap entries", here);
}
while (N > 0) {
/**
* Reads a map into {@code map}.
*
* @param sorted Whether the keys are sorted by their hashes, if so we use an optimized path.
* @param lazy Whether to populate the map with lazy {@link Supplier} objects for
* length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
* details.
* @return whether the parcel can be recycled or not.
* @hide
*/
boolean readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
boolean lazy, @Nullable ClassLoader loader) {
boolean recycle = true;
while (size > 0) {
String key = readString();
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read safe #" + (N-1) + ": key=0x"
+ (key != null ? key.hashCode() : 0) + " " + key);
Object value = readValue(loader);
outVal.put(key, value);
N--;
Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
if (value instanceof LazyValue) {
recycle = false;
}
if (sorted) {
map.append(key, value);
} else {
map.put(key, value);
}
size--;
}
if (sorted) {
map.validate();
}
return recycle;
}
/**
* @hide For testing only.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void readArrayMap(@NonNull ArrayMap outVal, @Nullable ClassLoader loader) {
public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal,
@Nullable ClassLoader loader) {
final int N = readInt();
if (N < 0) {
return;
@@ -3691,4 +4038,38 @@ public final class Parcel {
public long getBlobAshmemSize() {
return nativeGetBlobAshmemSize(mNativePtr);
}
private static String valueTypeToString(int type) {
switch (type) {
case VAL_NULL: return "VAL_NULL";
case VAL_INTEGER: return "VAL_INTEGER";
case VAL_MAP: return "VAL_MAP";
case VAL_BUNDLE: return "VAL_BUNDLE";
case VAL_PERSISTABLEBUNDLE: return "VAL_PERSISTABLEBUNDLE";
case VAL_PARCELABLE: return "VAL_PARCELABLE";
case VAL_SHORT: return "VAL_SHORT";
case VAL_LONG: return "VAL_LONG";
case VAL_FLOAT: return "VAL_FLOAT";
case VAL_DOUBLE: return "VAL_DOUBLE";
case VAL_BOOLEAN: return "VAL_BOOLEAN";
case VAL_CHARSEQUENCE: return "VAL_CHARSEQUENCE";
case VAL_LIST: return "VAL_LIST";
case VAL_SPARSEARRAY: return "VAL_SPARSEARRAY";
case VAL_BOOLEANARRAY: return "VAL_BOOLEANARRAY";
case VAL_BYTEARRAY: return "VAL_BYTEARRAY";
case VAL_STRINGARRAY: return "VAL_STRINGARRAY";
case VAL_CHARSEQUENCEARRAY: return "VAL_CHARSEQUENCEARRAY";
case VAL_IBINDER: return "VAL_IBINDER";
case VAL_PARCELABLEARRAY: return "VAL_PARCELABLEARRAY";
case VAL_INTARRAY: return "VAL_INTARRAY";
case VAL_LONGARRAY: return "VAL_LONGARRAY";
case VAL_BYTE: return "VAL_BYTE";
case VAL_SIZE: return "VAL_SIZE";
case VAL_SIZEF: return "VAL_SIZEF";
case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
default: return "UNKNOWN(" + type + ")";
}
}
}