Merge "Lazy bundle"

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

View File

@@ -31,6 +31,7 @@ import com.android.internal.util.IndentingPrintWriter;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
/** /**
* A mapping from String keys to values of various types. In most cases, you * A mapping from String keys to values of various types. In most cases, you
@@ -38,7 +39,8 @@ import java.util.Set;
* {@link PersistableBundle} subclass. * {@link PersistableBundle} subclass.
*/ */
public class BaseBundle { public class BaseBundle {
private static final String TAG = "Bundle"; /** @hide */
protected static final String TAG = "Bundle";
static final boolean DEBUG = false; static final boolean DEBUG = false;
// Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp. // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -95,7 +97,7 @@ public class BaseBundle {
Parcel mParcelledData = null; 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; private boolean mParcelledByNative;
@@ -198,7 +200,7 @@ public class BaseBundle {
if (size == 0) { if (size == 0) {
return null; return null;
} }
Object o = mMap.valueAt(0); Object o = getValueAt(0);
try { try {
return (String) o; return (String) o;
} catch (ClassCastException e) { } catch (ClassCastException e) {
@@ -229,7 +231,12 @@ public class BaseBundle {
* using the currently assigned class loader. * using the currently assigned class loader.
*/ */
@UnsupportedAppUsage @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) { synchronized (this) {
final Parcel source = mParcelledData; final Parcel source = mParcelledData;
if (source != null) { if (source != null) {
@@ -241,9 +248,42 @@ public class BaseBundle {
+ ": no parcelled data"); + ": 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, private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
boolean parcelledByNative) { boolean parcelledByNative) {
if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) { if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
@@ -282,15 +322,8 @@ public class BaseBundle {
map.ensureCapacity(count); map.ensureCapacity(count);
} }
try { try {
if (parcelledByNative) { recycleParcel &= parcelledData.readArrayMap(map, count, !parcelledByNative,
// If it was parcelled by native code, then the array map keys aren't sorted /* lazy */ true, mClassLoader);
// 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);
}
} catch (BadParcelableException e) { } catch (BadParcelableException e) {
if (sShouldDefuse) { if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -342,7 +375,7 @@ public class BaseBundle {
/** @hide */ /** @hide */
ArrayMap<String, Object> getMap() { ArrayMap<String, Object> getMap() {
unparcel(); unparcel(/* itemwise */ true);
return mMap; 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) { public boolean kindofEquals(BaseBundle other) {
if (other == null) { if (other == null) {
@@ -415,6 +453,12 @@ public class BaseBundle {
} else if (isParcelled()) { } else if (isParcelled()) {
return mParcelledData.compareData(other.mParcelledData) == 0; return mParcelledData.compareData(other.mParcelledData) == 0;
} else { } 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); return mMap.equals(other.mMap);
} }
} }
@@ -453,7 +497,7 @@ public class BaseBundle {
final int N = fromMap.size(); final int N = fromMap.size();
mMap = new ArrayMap<>(N); mMap = new ArrayMap<>(N);
for (int i = 0; i < N; i++) { 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 { } else {
@@ -526,7 +570,7 @@ public class BaseBundle {
@Nullable @Nullable
public Object get(String key) { public Object get(String key) {
unparcel(); unparcel();
return mMap.get(key); return getValue(key);
} }
/** /**
@@ -1001,7 +1045,7 @@ public class BaseBundle {
*/ */
char getChar(String key, char defaultValue) { char getChar(String key, char defaultValue) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return defaultValue; return defaultValue;
} }
@@ -1266,7 +1310,7 @@ public class BaseBundle {
@Nullable @Nullable
Serializable getSerializable(@Nullable String key) { Serializable getSerializable(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1289,7 +1333,7 @@ public class BaseBundle {
@Nullable @Nullable
ArrayList<Integer> getIntegerArrayList(@Nullable String key) { ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1312,7 +1356,7 @@ public class BaseBundle {
@Nullable @Nullable
ArrayList<String> getStringArrayList(@Nullable String key) { ArrayList<String> getStringArrayList(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1335,7 +1379,7 @@ public class BaseBundle {
@Nullable @Nullable
ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) { ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1404,7 +1448,7 @@ public class BaseBundle {
@Nullable @Nullable
short[] getShortArray(@Nullable String key) { short[] getShortArray(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1427,7 +1471,7 @@ public class BaseBundle {
@Nullable @Nullable
char[] getCharArray(@Nullable String key) { char[] getCharArray(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1496,7 +1540,7 @@ public class BaseBundle {
@Nullable @Nullable
float[] getFloatArray(@Nullable String key) { float[] getFloatArray(@Nullable String key) {
unparcel(); unparcel();
Object o = mMap.get(key); Object o = getValue(key);
if (o == null) { if (o == null) {
return null; return null;
} }
@@ -1585,7 +1629,7 @@ public class BaseBundle {
void writeToParcelInner(Parcel parcel, int flags) { 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 the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
if (parcel.hasReadWriteHelper()) { if (parcel.hasReadWriteHelper()) {
unparcel(); unparcel(/* itemwise */ true);
} }
// Keep implementation in sync with writeToParcel() in // Keep implementation in sync with writeToParcel() in
// frameworks/native/libs/binder/PersistableBundle.cpp. // frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -1660,10 +1704,13 @@ public class BaseBundle {
} }
if (parcel.hasReadWriteHelper()) { if (parcel.hasReadWriteHelper()) {
// If the parcel has a read-write helper, then we can't lazily-unparcel it, so just // If the parcel has a read-write helper, it's better to deserialize immediately
// unparcel right away. // 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) { synchronized (this) {
initializeFromParcelLocked(parcel, /*recycleParcel=*/ false, isNativeBundle); initializeFromParcelLocked(parcel, /*recycleParcel=*/ false, isNativeBundle);
unparcel(/* itemwise */ true);
} }
return; return;
} }

View File

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

View File

@@ -26,6 +26,7 @@ import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.ExceptionUtils; import android.util.ExceptionUtils;
import android.util.Log; import android.util.Log;
import android.util.MathUtils;
import android.util.Size; import android.util.Size;
import android.util.SizeF; import android.util.SizeF;
import android.util.Slog; import android.util.Slog;
@@ -56,7 +57,9 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
/** /**
* Container for a message (data and object references) that can * Container for a message (data and object references) that can
@@ -648,6 +651,65 @@ public final class Parcel {
return nativeHasFileDescriptors(mNativePtr); return nativeHasFileDescriptors(mNativePtr);
} }
/**
* Check if the object used in {@link #readValue(ClassLoader)} / {@link #writeValue(Object)}
* has file descriptors.
*
* <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method
* for that.
*
* @throws IllegalArgumentException if you provide any object not supported by above methods.
* Most notably, if you pass {@link Parcel}, this method will throw, for that check
* {@link Parcel#hasFileDescriptors()}
*
* @hide
*/
public static boolean hasFileDescriptors(Object value) {
getValueType(value); // Will throw if value is not supported
if (value instanceof LazyValue) {
return ((LazyValue) value).hasFileDescriptors();
} else if (value instanceof Parcelable) {
if ((((Parcelable) value).describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
return true;
}
} else if (value instanceof Parcelable[]) {
Parcelable[] array = (Parcelable[]) value;
for (int n = array.length - 1; n >= 0; n--) {
Parcelable p = array[n];
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
return true;
}
}
} else if (value instanceof SparseArray<?>) {
SparseArray<?> array = (SparseArray<?>) value;
for (int n = array.size() - 1; n >= 0; n--) {
Object object = array.valueAt(n);
if (object instanceof Parcelable) {
Parcelable p = (Parcelable) object;
if (p != null && (p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
return true;
}
}
}
} else if (value instanceof ArrayList<?>) {
ArrayList<?> array = (ArrayList<?>) value;
for (int n = array.size() - 1; n >= 0; n--) {
Object object = array.get(n);
if (object instanceof Parcelable) {
Parcelable p = (Parcelable) object;
if (p != null && ((p.describeContents()
& Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
return true;
}
}
}
}
return false;
}
/** /**
* Store or read an IBinder interface token in the parcel at the current * Store or read an IBinder interface token in the parcel at the current
* {@link #dataPosition}. This is used to validate that the marshalled * {@link #dataPosition}. This is used to validate that the marshalled
@@ -1795,108 +1857,206 @@ public final class Parcel {
* should be used).</p> * should be used).</p>
*/ */
public final void writeValue(@Nullable Object v) { 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) { if (v == null) {
writeInt(VAL_NULL); return VAL_NULL;
} else if (v instanceof String) { } else if (v instanceof String) {
writeInt(VAL_STRING); return VAL_STRING;
writeString((String) v);
} else if (v instanceof Integer) { } else if (v instanceof Integer) {
writeInt(VAL_INTEGER); return VAL_INTEGER;
writeInt((Integer) v);
} else if (v instanceof Map) { } else if (v instanceof Map) {
writeInt(VAL_MAP); return VAL_MAP;
writeMap((Map) v);
} else if (v instanceof Bundle) { } else if (v instanceof Bundle) {
// Must be before Parcelable // Must be before Parcelable
writeInt(VAL_BUNDLE); return VAL_BUNDLE;
writeBundle((Bundle) v);
} else if (v instanceof PersistableBundle) { } else if (v instanceof PersistableBundle) {
writeInt(VAL_PERSISTABLEBUNDLE); // Must be before Parcelable
writePersistableBundle((PersistableBundle) v); return VAL_PERSISTABLEBUNDLE;
} else if (v instanceof SizeF) {
// Must be before Parcelable
return VAL_SIZEF;
} else if (v instanceof Parcelable) { } else if (v instanceof Parcelable) {
// IMPOTANT: cases for classes that implement Parcelable must // 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. // types will be written.
writeInt(VAL_PARCELABLE); return VAL_PARCELABLE;
writeParcelable((Parcelable) v, 0);
} else if (v instanceof Short) { } else if (v instanceof Short) {
writeInt(VAL_SHORT); return VAL_SHORT;
writeInt(((Short) v).intValue());
} else if (v instanceof Long) { } else if (v instanceof Long) {
writeInt(VAL_LONG); return VAL_LONG;
writeLong((Long) v);
} else if (v instanceof Float) { } else if (v instanceof Float) {
writeInt(VAL_FLOAT); return VAL_FLOAT;
writeFloat((Float) v);
} else if (v instanceof Double) { } else if (v instanceof Double) {
writeInt(VAL_DOUBLE); return VAL_DOUBLE;
writeDouble((Double) v);
} else if (v instanceof Boolean) { } else if (v instanceof Boolean) {
writeInt(VAL_BOOLEAN); return VAL_BOOLEAN;
writeInt((Boolean) v ? 1 : 0);
} else if (v instanceof CharSequence) { } else if (v instanceof CharSequence) {
// Must be after String // Must be after String
writeInt(VAL_CHARSEQUENCE); return VAL_CHARSEQUENCE;
writeCharSequence((CharSequence) v);
} else if (v instanceof List) { } else if (v instanceof List) {
writeInt(VAL_LIST); return VAL_LIST;
writeList((List) v);
} else if (v instanceof SparseArray) { } else if (v instanceof SparseArray) {
writeInt(VAL_SPARSEARRAY); return VAL_SPARSEARRAY;
writeSparseArray((SparseArray) v);
} else if (v instanceof boolean[]) { } else if (v instanceof boolean[]) {
writeInt(VAL_BOOLEANARRAY); return VAL_BOOLEANARRAY;
writeBooleanArray((boolean[]) v);
} else if (v instanceof byte[]) { } else if (v instanceof byte[]) {
writeInt(VAL_BYTEARRAY); return VAL_BYTEARRAY;
writeByteArray((byte[]) v);
} else if (v instanceof String[]) { } else if (v instanceof String[]) {
writeInt(VAL_STRINGARRAY); return VAL_STRINGARRAY;
writeStringArray((String[]) v);
} else if (v instanceof CharSequence[]) { } else if (v instanceof CharSequence[]) {
// Must be after String[] and before Object[] // Must be after String[] and before Object[]
writeInt(VAL_CHARSEQUENCEARRAY); return VAL_CHARSEQUENCEARRAY;
writeCharSequenceArray((CharSequence[]) v);
} else if (v instanceof IBinder) { } else if (v instanceof IBinder) {
writeInt(VAL_IBINDER); return VAL_IBINDER;
writeStrongBinder((IBinder) v);
} else if (v instanceof Parcelable[]) { } else if (v instanceof Parcelable[]) {
writeInt(VAL_PARCELABLEARRAY); return VAL_PARCELABLEARRAY;
writeParcelableArray((Parcelable[]) v, 0);
} else if (v instanceof int[]) { } else if (v instanceof int[]) {
writeInt(VAL_INTARRAY); return VAL_INTARRAY;
writeIntArray((int[]) v);
} else if (v instanceof long[]) { } else if (v instanceof long[]) {
writeInt(VAL_LONGARRAY); return VAL_LONGARRAY;
writeLongArray((long[]) v);
} else if (v instanceof Byte) { } else if (v instanceof Byte) {
writeInt(VAL_BYTE); return VAL_BYTE;
writeInt((Byte) v);
} else if (v instanceof Size) { } else if (v instanceof Size) {
writeInt(VAL_SIZE); return VAL_SIZE;
writeSize((Size) v);
} else if (v instanceof SizeF) {
writeInt(VAL_SIZEF);
writeSizeF((SizeF) v);
} else if (v instanceof double[]) { } else if (v instanceof double[]) {
writeInt(VAL_DOUBLEARRAY); return VAL_DOUBLEARRAY;
writeDoubleArray((double[]) v);
} else { } else {
Class<?> clazz = v.getClass(); Class<?> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) { if (clazz.isArray() && clazz.getComponentType() == Object.class) {
// Only pure Object[] are written here, Other arrays of non-primitive types are // Only pure Object[] are written here, Other arrays of non-primitive types are
// handled by serialization as this does not record the component type. // handled by serialization as this does not record the component type.
writeInt(VAL_OBJECTARRAY); return VAL_OBJECTARRAY;
writeArray((Object[]) v);
} else if (v instanceof Serializable) { } else if (v instanceof Serializable) {
// Must be last // Must be last
writeInt(VAL_SERIALIZABLE); return VAL_SERIALIZABLE;
writeSerializable((Serializable) v);
} else { } 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 * Flatten the name of the class of the Parcelable and its contents
@@ -3167,7 +3327,180 @@ public final class Parcel {
@Nullable @Nullable
public final Object readValue(@Nullable ClassLoader loader) { public final Object readValue(@Nullable ClassLoader loader) {
int type = readInt(); 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) { switch (type) {
case VAL_NULL: case VAL_NULL:
return null; return null;
@@ -3266,6 +3599,20 @@ public final class Parcel {
} }
} }
private boolean isLengthPrefixed(int type) {
switch (type) {
case VAL_PARCELABLE:
case VAL_PARCELABLEARRAY:
case VAL_LIST:
case VAL_SPARSEARRAY:
case VAL_BUNDLE:
case VAL_SERIALIZABLE:
return true;
default:
return false;
}
}
/** /**
* Read and return a new Parcelable from the parcel. The given class loader * 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 * will be used to load any enclosed Parcelables. If it is null, the default
@@ -3564,49 +3911,49 @@ public final class Parcel {
} }
} }
/* package */ void readArrayMapInternal(@NonNull ArrayMap outVal, int N, /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
@Nullable ClassLoader loader) { int size, @Nullable ClassLoader loader) {
if (DEBUG_ARRAY_MAP) { readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
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 readArrayMapSafelyInternal(@NonNull ArrayMap outVal, int N, /**
@Nullable ClassLoader loader) { * Reads a map into {@code map}.
if (DEBUG_ARRAY_MAP) { *
RuntimeException here = new RuntimeException("here"); * @param sorted Whether the keys are sorted by their hashes, if so we use an optimized path.
here.fillInStackTrace(); * @param lazy Whether to populate the map with lazy {@link Supplier} objects for
Log.d(TAG, "Reading safely " + N + " ArrayMap entries", here); * length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
} * details.
while (N > 0) { * @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(); String key = readString();
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read safe #" + (N-1) + ": key=0x" Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
+ (key != null ? key.hashCode() : 0) + " " + key); if (value instanceof LazyValue) {
Object value = readValue(loader); recycle = false;
outVal.put(key, value); }
N--; if (sorted) {
map.append(key, value);
} else {
map.put(key, value);
}
size--;
} }
if (sorted) {
map.validate();
}
return recycle;
} }
/** /**
* @hide For testing only. * @hide For testing only.
*/ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @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(); final int N = readInt();
if (N < 0) { if (N < 0) {
return; return;
@@ -3691,4 +4038,38 @@ public final class Parcel {
public long getBlobAshmemSize() { public long getBlobAshmemSize() {
return nativeGetBlobAshmemSize(mNativePtr); 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 + ")";
}
}
} }