diff options
author | 2021-08-31 10:08:39 +0000 | |
---|---|---|
committer | 2021-08-31 10:08:39 +0000 | |
commit | 872f7a88e54b1aa8c76c23099d6e78bdf82f8cdb (patch) | |
tree | 0a5b1f8b2db9f1b8fe2656d65ee1fd28dfa29d59 | |
parent | e5fc82b09203c5bce7ed83e41b576ef2f75d35a6 (diff) | |
parent | 93b4b55ee719427d877828871dd0837b34145d9c (diff) |
Merge "Lazy bundle" am: 93b4b55ee7
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1787847
Change-Id: I36812425c05b2849d15082f5c73c52a81ad1b423
-rw-r--r-- | core/java/android/os/BaseBundle.java | 103 | ||||
-rw-r--r-- | core/java/android/os/Bundle.java | 60 | ||||
-rw-r--r-- | core/java/android/os/Parcel.java | 567 |
3 files changed, 560 insertions, 170 deletions
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 1692921f4a8f..6da02f5c9ff5 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -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,7 +248,40 @@ 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, @@ -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; } diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 102f52566590..b6163f1105c6 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -331,47 +331,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; } } } @@ -392,7 +354,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; @@ -973,7 +935,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; } @@ -1000,7 +962,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; } @@ -1027,7 +989,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; } @@ -1054,7 +1016,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; } @@ -1078,7 +1040,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; } @@ -1301,7 +1263,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); } @@ -1313,7 +1275,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(); } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index e06e7b6be90a..c42c1d9dcd15 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -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 @@ -679,6 +682,65 @@ public final class Parcel { } /** + * 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. This is typically written @@ -1832,108 +1894,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 @@ -3208,7 +3368,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; @@ -3307,6 +3640,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 @@ -3609,49 +3956,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; @@ -3736,4 +4083,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 + ")"; + } + } } |