Merge "Lazy bundle"
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1692921f..6da02f5 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -31,6 +31,7 @@
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 @@
* {@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 @@
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 @@
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 @@
* 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 @@
+ ": 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 @@
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 @@
/** @hide */
ArrayMap<String, Object> getMap() {
- unparcel();
+ unparcel(/* itemwise */ true);
return mMap;
}
@@ -400,7 +433,12 @@
}
/**
- * @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 @@
} 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 @@
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 @@
@Nullable
public Object get(String key) {
unparcel();
- return mMap.get(key);
+ return getValue(key);
}
/**
@@ -1001,7 +1045,7 @@
*/
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
}
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;
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 1c1f5c0..5626bde 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -330,47 +330,9 @@
// 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 @@
*/
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 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 @@
* @param parcel The parcel to overwrite this bundle from.
*/
public void readFromParcel(Parcel parcel) {
- super.readFromParcelInner(parcel);
+ readFromParcelInner(parcel);
mFlags = FLAG_ALLOW_FDS;
maybePrefillHasFds();
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6acdcc4..575eb37 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -26,6 +26,7 @@
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.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
@@ -649,6 +652,65 @@
}
/**
+ * 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
* transaction is intended for the target interface.
@@ -1795,108 +1857,206 @@
* 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 @@
@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 @@
}
}
+ 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 @@
}
}
- /* 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 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 + ")";
+ }
+ }
}