Lazy bundle
Implement lazy deserialization for custom types in bundle:
* Parcelable (VAL_PARCELABLE)
* Serializable (VAL_SERIALIZABLE)
* Parcelable array (VAL_PARCELABLEARRAY)
* Lists (VAL_LIST)
* Sparse array (VAL_SPARSEARRAY)
* Bundle (VAL_BUNDLE)
This enhances security, makes bundles more robust to deserialization
errors and avoid deserializing unneeded objects in some cases*, for more
details check go/lazy-bundle.
To do that, we prefix those types with their length when writing them on
the wire. Map serialization and deserialization now happens inside
Bundle (instead of calling Parcel's readArrayMapInternal() /
writeArrayMapInternal()) and we use an intermediary object - LazyValue -
that holds information about the position and length of the value we
will deserialize when queried.
So, there are basically 3 states:
1. We received the bundle but haven't queried anything about it (not
even isEmpty()): in this case the original parcel is held inside and
we haven't attempted any deserialization (except for the metadata at
the beginning such as the magic, etc)
2. We queried something on it (eg. isEmpty()): Now we deserialize the
bundle skipping the custom values above (we're able to do this now
with the length written on the wire) and instead placing LazyValue
objects for them in the map.
3. We query one of the lazy values: Now, we deserialize the object
represented by LazyValue and replace it on the map.
Since after (2) LazyValue objects are the only ones holding references
to the original Parcel, when all LazyValues are deserialized, the
original Parcel is available for GC.
Inside bundle now we differentiate between unparcel(itemwise = true) and
unparcel(itemwise = false) where the first also deserializes each item
(such that there are no LazyValues in the map). This is because some
operations such as kindofEquals() need all items deserialized.
I had to break a few methods in parcel into multiple methods in parcel
to be able to control the format in bundle. They are all @hide.
* In quick local experiments, counting the bytes that didn't need to be
deserialized after the change. Roughly 10% of bytes from custom-type
items in Bundle are not deserialized in the testing scenario (if I
haven't messed up the stats :). That's on a sdk_gphone_x86_64_arm64 a
few minutes after boot. Check
https://screenshot.googleplex.com/53uXrrqDMYahzg3, stats collection is
on ag/15403076.
Test: atest -d android.os.cts.ParcelTest android.os.cts.BundleTest android.os.BundleTest android.os.ParcelTest
Test: Boot device
Bug: 195622897
Change-Id: Icfe8880cad00c3cd2afcbe4b92400ad4579e680e
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1692921..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 + ")";
+ }
+ }
}