Merge "Lazy bundle"
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1692921f..6da02f5 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -31,6 +31,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * A mapping from String keys to values of various types. In most cases, you
@@ -38,7 +39,8 @@
  * {@link PersistableBundle} subclass.
  */
 public class BaseBundle {
-    private static final String TAG = "Bundle";
+    /** @hide */
+    protected static final String TAG = "Bundle";
     static final boolean DEBUG = false;
 
     // Keep them in sync with frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -95,7 +97,7 @@
     Parcel mParcelledData = null;
 
     /**
-     * Whether {@link #mParcelledData} was generated by native coed or not.
+     * Whether {@link #mParcelledData} was generated by native code or not.
      */
     private boolean mParcelledByNative;
 
@@ -198,7 +200,7 @@
         if (size == 0) {
             return null;
         }
-        Object o = mMap.valueAt(0);
+        Object o = getValueAt(0);
         try {
             return (String) o;
         } catch (ClassCastException e) {
@@ -229,7 +231,12 @@
      * using the currently assigned class loader.
      */
     @UnsupportedAppUsage
-    /* package */ void unparcel() {
+    final void unparcel() {
+        unparcel(/* itemwise */ false);
+    }
+
+    /** Deserializes the underlying data and each item if {@code itemwise} is true. */
+    final void unparcel(boolean itemwise) {
         synchronized (this) {
             final Parcel source = mParcelledData;
             if (source != null) {
@@ -241,9 +248,42 @@
                             + ": no parcelled data");
                 }
             }
+            if (itemwise) {
+                for (int i = 0, n = mMap.size(); i < n; i++) {
+                    // Triggers deserialization of i-th item, if needed
+                    getValueAt(i);
+                }
+            }
         }
     }
 
+    /**
+     * Returns the value for key {@code key}.
+     *
+     * @hide
+     */
+    final Object getValue(String key) {
+        int i = mMap.indexOfKey(key);
+        return (i >= 0) ? getValueAt(i) : null;
+    }
+
+    /**
+     * Returns the value for a certain position in the array map.
+     *
+     * @hide
+     */
+    final Object getValueAt(int i) {
+        Object object = mMap.valueAt(i);
+        if (object instanceof Supplier<?>) {
+            Supplier<?> supplier = (Supplier<?>) object;
+            synchronized (this) {
+                object = supplier.get();
+            }
+            mMap.setValueAt(i, object);
+        }
+        return object;
+    }
+
     private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
             boolean parcelledByNative) {
         if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
@@ -282,15 +322,8 @@
             map.ensureCapacity(count);
         }
         try {
-            if (parcelledByNative) {
-                // If it was parcelled by native code, then the array map keys aren't sorted
-                // by their hash codes, so use the safe (slow) one.
-                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
-            } else {
-                // If parcelled by Java, we know the contents are sorted properly,
-                // so we can use ArrayMap.append().
-                parcelledData.readArrayMapInternal(map, count, mClassLoader);
-            }
+            recycleParcel &= parcelledData.readArrayMap(map, count, !parcelledByNative,
+                    /* lazy */ true, mClassLoader);
         } catch (BadParcelableException e) {
             if (sShouldDefuse) {
                 Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
@@ -342,7 +375,7 @@
 
     /** @hide */
     ArrayMap<String, Object> getMap() {
-        unparcel();
+        unparcel(/* itemwise */ true);
         return mMap;
     }
 
@@ -400,7 +433,12 @@
     }
 
     /**
-     * @hide This kind-of does an equality comparison.  Kind-of.
+     * Performs a loose equality check, which means there can be false negatives but if the method
+     * returns true than both objects are guaranteed to be equal.
+     *
+     * The point is that this method is a light-weight check in performance terms.
+     *
+     * @hide
      */
     public boolean kindofEquals(BaseBundle other) {
         if (other == null) {
@@ -415,6 +453,12 @@
         } else if (isParcelled()) {
             return mParcelledData.compareData(other.mParcelledData) == 0;
         } else {
+            // Following semantic above of failing in case we get a serialized value vs a
+            // deserialized one, we'll compare the map. If a certain element hasn't been
+            // deserialized yet, it's a Supplier (or more specifically a LazyValue, but let's
+            // pretend we don't know that here :P), we'll use that element's equality comparison as
+            // map naturally does. That will takes care of comparing the payload if needed (see
+            // Parcel.readLazyValue() for details).
             return mMap.equals(other.mMap);
         }
     }
@@ -453,7 +497,7 @@
                     final int N = fromMap.size();
                     mMap = new ArrayMap<>(N);
                     for (int i = 0; i < N; i++) {
-                        mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
+                        mMap.append(fromMap.keyAt(i), deepCopyValue(from.getValueAt(i)));
                     }
                 }
             } else {
@@ -526,7 +570,7 @@
     @Nullable
     public Object get(String key) {
         unparcel();
-        return mMap.get(key);
+        return getValue(key);
     }
 
     /**
@@ -1001,7 +1045,7 @@
      */
     char getChar(String key, char defaultValue) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return defaultValue;
         }
@@ -1266,7 +1310,7 @@
     @Nullable
     Serializable getSerializable(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1289,7 +1333,7 @@
     @Nullable
     ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1312,7 +1356,7 @@
     @Nullable
     ArrayList<String> getStringArrayList(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1335,7 +1379,7 @@
     @Nullable
     ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1404,7 +1448,7 @@
     @Nullable
     short[] getShortArray(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1427,7 +1471,7 @@
     @Nullable
     char[] getCharArray(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1496,7 +1540,7 @@
     @Nullable
     float[] getFloatArray(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1585,7 +1629,7 @@
     void writeToParcelInner(Parcel parcel, int flags) {
         // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
         if (parcel.hasReadWriteHelper()) {
-            unparcel();
+            unparcel(/* itemwise */ true);
         }
         // Keep implementation in sync with writeToParcel() in
         // frameworks/native/libs/binder/PersistableBundle.cpp.
@@ -1660,10 +1704,13 @@
         }
 
         if (parcel.hasReadWriteHelper()) {
-            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
-            // unparcel right away.
+            // If the parcel has a read-write helper, it's better to deserialize immediately
+            // otherwise the helper would have to either maintain valid state long after the bundle
+            // had been constructed with parcel or to make sure they trigger deserialization of the
+            // bundle immediately; neither of which is obvious.
             synchronized (this) {
                 initializeFromParcelLocked(parcel, /*recycleParcel=*/ false, isNativeBundle);
+                unparcel(/* itemwise */ true);
             }
             return;
         }
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 1c1f5c0..5626bde 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -330,47 +330,9 @@
                 // It's been unparcelled, so we need to walk the map
                 for (int i=mMap.size()-1; i>=0; i--) {
                     Object obj = mMap.valueAt(i);
-                    if (obj instanceof Parcelable) {
-                        if ((((Parcelable)obj).describeContents()
-                                & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
-                            fdFound = true;
-                            break;
-                        }
-                    } else if (obj instanceof Parcelable[]) {
-                        Parcelable[] array = (Parcelable[]) obj;
-                        for (int n = array.length - 1; n >= 0; n--) {
-                            Parcelable p = array[n];
-                            if (p != null && ((p.describeContents()
-                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
-                                fdFound = true;
-                                break;
-                            }
-                        }
-                    } else if (obj instanceof SparseArray) {
-                        SparseArray<? extends Parcelable> array =
-                                (SparseArray<? extends Parcelable>) obj;
-                        for (int n = array.size() - 1; n >= 0; n--) {
-                            Parcelable p = array.valueAt(n);
-                            if (p != null && (p.describeContents()
-                                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
-                                fdFound = true;
-                                break;
-                            }
-                        }
-                    } else if (obj instanceof ArrayList) {
-                        ArrayList array = (ArrayList) obj;
-                        // an ArrayList here might contain either Strings or
-                        // Parcelables; only look inside for Parcelables
-                        if (!array.isEmpty() && (array.get(0) instanceof Parcelable)) {
-                            for (int n = array.size() - 1; n >= 0; n--) {
-                                Parcelable p = (Parcelable) array.get(n);
-                                if (p != null && ((p.describeContents()
-                                        & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
-                                    fdFound = true;
-                                    break;
-                                }
-                            }
-                        }
+                    if (Parcel.hasFileDescriptors(obj)) {
+                        fdFound = true;
+                        break;
                     }
                 }
             }
@@ -391,7 +353,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Bundle filterValues() {
-        unparcel();
+        unparcel(/* itemwise */ true);
         Bundle bundle = this;
         if (mMap != null) {
             ArrayMap<String, Object> map = mMap;
@@ -972,7 +934,7 @@
     @Nullable
     public Bundle getBundle(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -999,7 +961,7 @@
     @Nullable
     public <T extends Parcelable> T getParcelable(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1026,7 +988,7 @@
     @Nullable
     public Parcelable[] getParcelableArray(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1053,7 +1015,7 @@
     @Nullable
     public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1077,7 +1039,7 @@
     @Nullable
     public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
         unparcel();
-        Object o = mMap.get(key);
+        Object o = getValue(key);
         if (o == null) {
             return null;
         }
@@ -1300,7 +1262,7 @@
     public void writeToParcel(Parcel parcel, int flags) {
         final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
         try {
-            super.writeToParcelInner(parcel, flags);
+            writeToParcelInner(parcel, flags);
         } finally {
             parcel.restoreAllowFds(oldAllowFds);
         }
@@ -1312,7 +1274,7 @@
      * @param parcel The parcel to overwrite this bundle from.
      */
     public void readFromParcel(Parcel parcel) {
-        super.readFromParcelInner(parcel);
+        readFromParcelInner(parcel);
         mFlags = FLAG_ALLOW_FDS;
         maybePrefillHasFds();
     }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6acdcc4..575eb37 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -26,6 +26,7 @@
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Size;
 import android.util.SizeF;
 import android.util.Slog;
@@ -56,7 +57,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /**
  * Container for a message (data and object references) that can
@@ -649,6 +652,65 @@
     }
 
     /**
+     * Check if the object used in {@link #readValue(ClassLoader)} / {@link #writeValue(Object)}
+     * has file descriptors.
+     *
+     * <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method
+     * for that.
+     *
+     * @throws IllegalArgumentException if you provide any object not supported by above methods.
+     *         Most notably, if you pass {@link Parcel}, this method will throw, for that check
+     *         {@link Parcel#hasFileDescriptors()}
+     *
+     * @hide
+     */
+    public static boolean hasFileDescriptors(Object value) {
+        getValueType(value); // Will throw if value is not supported
+        if (value instanceof LazyValue) {
+            return ((LazyValue) value).hasFileDescriptors();
+        } else if (value instanceof Parcelable) {
+            if ((((Parcelable) value).describeContents()
+                    & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                return true;
+            }
+        } else if (value instanceof Parcelable[]) {
+            Parcelable[] array = (Parcelable[]) value;
+            for (int n = array.length - 1; n >= 0; n--) {
+                Parcelable p = array[n];
+                if (p != null && ((p.describeContents()
+                        & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
+                    return true;
+                }
+            }
+        } else if (value instanceof SparseArray<?>) {
+            SparseArray<?> array = (SparseArray<?>) value;
+            for (int n = array.size() - 1; n >= 0; n--) {
+                Object object = array.valueAt(n);
+                if (object instanceof Parcelable) {
+                    Parcelable p = (Parcelable) object;
+                    if (p != null && (p.describeContents()
+                            & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0) {
+                        return true;
+                    }
+                }
+            }
+        } else if (value instanceof ArrayList<?>) {
+            ArrayList<?> array = (ArrayList<?>) value;
+            for (int n = array.size() - 1; n >= 0; n--) {
+                Object object = array.get(n);
+                if (object instanceof Parcelable) {
+                    Parcelable p = (Parcelable) object;
+                    if (p != null && ((p.describeContents()
+                            & Parcelable.CONTENTS_FILE_DESCRIPTOR) != 0)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
      * Store or read an IBinder interface token in the parcel at the current
      * {@link #dataPosition}.  This is used to validate that the marshalled
      * transaction is intended for the target interface.
@@ -1795,108 +1857,206 @@
      * should be used).</p>
      */
     public final void writeValue(@Nullable Object v) {
+        if (v instanceof LazyValue) {
+            LazyValue value = (LazyValue) v;
+            value.writeToParcel(this);
+            return;
+        }
+        int type = getValueType(v);
+        writeInt(type);
+        if (isLengthPrefixed(type)) {
+            // Length
+            int length = dataPosition();
+            writeInt(-1); // Placeholder
+            // Object
+            int start = dataPosition();
+            writeValue(type, v);
+            int end = dataPosition();
+            // Backpatch length
+            setDataPosition(length);
+            writeInt(end - start);
+            setDataPosition(end);
+        } else {
+            writeValue(type, v);
+        }
+    }
+
+    /** @hide */
+    public static int getValueType(@Nullable Object v) {
         if (v == null) {
-            writeInt(VAL_NULL);
+            return VAL_NULL;
         } else if (v instanceof String) {
-            writeInt(VAL_STRING);
-            writeString((String) v);
+            return VAL_STRING;
         } else if (v instanceof Integer) {
-            writeInt(VAL_INTEGER);
-            writeInt((Integer) v);
+            return VAL_INTEGER;
         } else if (v instanceof Map) {
-            writeInt(VAL_MAP);
-            writeMap((Map) v);
+            return VAL_MAP;
         } else if (v instanceof Bundle) {
             // Must be before Parcelable
-            writeInt(VAL_BUNDLE);
-            writeBundle((Bundle) v);
+            return VAL_BUNDLE;
         } else if (v instanceof PersistableBundle) {
-            writeInt(VAL_PERSISTABLEBUNDLE);
-            writePersistableBundle((PersistableBundle) v);
+            // Must be before Parcelable
+            return VAL_PERSISTABLEBUNDLE;
+        } else if (v instanceof SizeF) {
+            // Must be before Parcelable
+            return VAL_SIZEF;
         } else if (v instanceof Parcelable) {
             // IMPOTANT: cases for classes that implement Parcelable must
-            // come before the Parcelable case, so that their specific VAL_*
+            // come before the Parcelable case, so that their speci fic VAL_*
             // types will be written.
-            writeInt(VAL_PARCELABLE);
-            writeParcelable((Parcelable) v, 0);
+            return VAL_PARCELABLE;
         } else if (v instanceof Short) {
-            writeInt(VAL_SHORT);
-            writeInt(((Short) v).intValue());
+            return VAL_SHORT;
         } else if (v instanceof Long) {
-            writeInt(VAL_LONG);
-            writeLong((Long) v);
+            return VAL_LONG;
         } else if (v instanceof Float) {
-            writeInt(VAL_FLOAT);
-            writeFloat((Float) v);
+            return VAL_FLOAT;
         } else if (v instanceof Double) {
-            writeInt(VAL_DOUBLE);
-            writeDouble((Double) v);
+            return VAL_DOUBLE;
         } else if (v instanceof Boolean) {
-            writeInt(VAL_BOOLEAN);
-            writeInt((Boolean) v ? 1 : 0);
+            return VAL_BOOLEAN;
         } else if (v instanceof CharSequence) {
             // Must be after String
-            writeInt(VAL_CHARSEQUENCE);
-            writeCharSequence((CharSequence) v);
+            return VAL_CHARSEQUENCE;
         } else if (v instanceof List) {
-            writeInt(VAL_LIST);
-            writeList((List) v);
+            return VAL_LIST;
         } else if (v instanceof SparseArray) {
-            writeInt(VAL_SPARSEARRAY);
-            writeSparseArray((SparseArray) v);
+            return VAL_SPARSEARRAY;
         } else if (v instanceof boolean[]) {
-            writeInt(VAL_BOOLEANARRAY);
-            writeBooleanArray((boolean[]) v);
+            return VAL_BOOLEANARRAY;
         } else if (v instanceof byte[]) {
-            writeInt(VAL_BYTEARRAY);
-            writeByteArray((byte[]) v);
+            return VAL_BYTEARRAY;
         } else if (v instanceof String[]) {
-            writeInt(VAL_STRINGARRAY);
-            writeStringArray((String[]) v);
+            return VAL_STRINGARRAY;
         } else if (v instanceof CharSequence[]) {
             // Must be after String[] and before Object[]
-            writeInt(VAL_CHARSEQUENCEARRAY);
-            writeCharSequenceArray((CharSequence[]) v);
+            return VAL_CHARSEQUENCEARRAY;
         } else if (v instanceof IBinder) {
-            writeInt(VAL_IBINDER);
-            writeStrongBinder((IBinder) v);
+            return VAL_IBINDER;
         } else if (v instanceof Parcelable[]) {
-            writeInt(VAL_PARCELABLEARRAY);
-            writeParcelableArray((Parcelable[]) v, 0);
+            return VAL_PARCELABLEARRAY;
         } else if (v instanceof int[]) {
-            writeInt(VAL_INTARRAY);
-            writeIntArray((int[]) v);
+            return VAL_INTARRAY;
         } else if (v instanceof long[]) {
-            writeInt(VAL_LONGARRAY);
-            writeLongArray((long[]) v);
+            return VAL_LONGARRAY;
         } else if (v instanceof Byte) {
-            writeInt(VAL_BYTE);
-            writeInt((Byte) v);
+            return VAL_BYTE;
         } else if (v instanceof Size) {
-            writeInt(VAL_SIZE);
-            writeSize((Size) v);
-        } else if (v instanceof SizeF) {
-            writeInt(VAL_SIZEF);
-            writeSizeF((SizeF) v);
+            return VAL_SIZE;
         } else if (v instanceof double[]) {
-            writeInt(VAL_DOUBLEARRAY);
-            writeDoubleArray((double[]) v);
+            return VAL_DOUBLEARRAY;
         } else {
             Class<?> clazz = v.getClass();
             if (clazz.isArray() && clazz.getComponentType() == Object.class) {
                 // Only pure Object[] are written here, Other arrays of non-primitive types are
                 // handled by serialization as this does not record the component type.
-                writeInt(VAL_OBJECTARRAY);
-                writeArray((Object[]) v);
+                return VAL_OBJECTARRAY;
             } else if (v instanceof Serializable) {
                 // Must be last
-                writeInt(VAL_SERIALIZABLE);
-                writeSerializable((Serializable) v);
+                return VAL_SERIALIZABLE;
             } else {
-                throw new RuntimeException("Parcel: unable to marshal value " + v);
+                throw new IllegalArgumentException("Parcel: unknown type for value " + v);
             }
         }
     }
+    /**
+     * Writes value {@code v} in the parcel. This does NOT write the int representing the type
+     * first.
+     *
+     * @hide
+     */
+    public void writeValue(int type, @Nullable Object v) {
+        switch (type) {
+            case VAL_NULL:
+                break;
+            case VAL_STRING:
+                writeString((String) v);
+                break;
+            case VAL_INTEGER:
+                writeInt((Integer) v);
+                break;
+            case VAL_MAP:
+                writeMap((Map) v);
+                break;
+            case VAL_BUNDLE:
+                writeBundle((Bundle) v);
+                break;
+            case VAL_PERSISTABLEBUNDLE:
+                writePersistableBundle((PersistableBundle) v);
+                break;
+            case VAL_PARCELABLE:
+                writeParcelable((Parcelable) v, 0);
+                break;
+            case VAL_SHORT:
+                writeInt(((Short) v).intValue());
+                break;
+            case VAL_LONG:
+                writeLong((Long) v);
+                break;
+            case VAL_FLOAT:
+                writeFloat((Float) v);
+                break;
+            case VAL_DOUBLE:
+                writeDouble((Double) v);
+                break;
+            case VAL_BOOLEAN:
+                writeInt((Boolean) v ? 1 : 0);
+                break;
+            case VAL_CHARSEQUENCE:
+                writeCharSequence((CharSequence) v);
+                break;
+            case VAL_LIST:
+                writeList((List) v);
+                break;
+            case VAL_SPARSEARRAY:
+                writeSparseArray((SparseArray) v);
+                break;
+            case VAL_BOOLEANARRAY:
+                writeBooleanArray((boolean[]) v);
+                break;
+            case VAL_BYTEARRAY:
+                writeByteArray((byte[]) v);
+                break;
+            case VAL_STRINGARRAY:
+                writeStringArray((String[]) v);
+                break;
+            case VAL_CHARSEQUENCEARRAY:
+                writeCharSequenceArray((CharSequence[]) v);
+                break;
+            case VAL_IBINDER:
+                writeStrongBinder((IBinder) v);
+                break;
+            case VAL_PARCELABLEARRAY:
+                writeParcelableArray((Parcelable[]) v, 0);
+                break;
+            case VAL_INTARRAY:
+                writeIntArray((int[]) v);
+                break;
+            case VAL_LONGARRAY:
+                writeLongArray((long[]) v);
+                break;
+            case VAL_BYTE:
+                writeInt((Byte) v);
+                break;
+            case VAL_SIZE:
+                writeSize((Size) v);
+                break;
+            case VAL_SIZEF:
+                writeSizeF((SizeF) v);
+                break;
+            case VAL_DOUBLEARRAY:
+                writeDoubleArray((double[]) v);
+                break;
+            case VAL_OBJECTARRAY:
+                writeArray((Object[]) v);
+                break;
+            case VAL_SERIALIZABLE:
+                writeSerializable((Serializable) v);
+                break;
+            default:
+                throw new RuntimeException("Parcel: unable to marshal value " + v);
+        }
+    }
 
     /**
      * Flatten the name of the class of the Parcelable and its contents
@@ -3167,7 +3327,180 @@
     @Nullable
     public final Object readValue(@Nullable ClassLoader loader) {
         int type = readInt();
+        final Object object;
+        if (isLengthPrefixed(type)) {
+            int length = readInt();
+            int start = dataPosition();
+            object = readValue(type, loader);
+            int actual = dataPosition() - start;
+            if (actual != length) {
+                Log.w(TAG,
+                        "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
+                                + "  consumed " + actual + " bytes, but " + length + " expected.");
+            }
+        } else {
+            object = readValue(type, loader);
+        }
+        return object;
+    }
 
+    /**
+     * This will return a {@link Supplier} for length-prefixed types that deserializes the object
+     * when {@link Supplier#get()} is called, for other types it will return the object itself.
+     *
+     * <p>After calling {@link Supplier#get()} the parcel cursor will not change. Note that you
+     * shouldn't recycle the parcel, not at least until all objects have been retrieved. No
+     * synchronization attempts are made.
+     *
+     * </p>The supplier returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
+     * suppliers are equal if either of the following is true:
+     * <ul>
+     *   <li>{@link Supplier#get()} has been called on both and both objects returned are equal.
+     *   <li>{@link Supplier#get()} hasn't been called on either one and everything below is true:
+     *   <ul>
+     *       <li>The {@code loader} parameters used to retrieve each are equal.
+     *       <li>They both have the same type.
+     *       <li>They have the same payload length.
+     *       <li>Their binary content is the same.
+     *   </ul>
+     * </ul>
+     *
+     * @hide
+     */
+    @Nullable
+    public Object readLazyValue(@Nullable ClassLoader loader) {
+        int start = dataPosition();
+        int type = readInt();
+        if (isLengthPrefixed(type)) {
+            int length = readInt();
+            setDataPosition(MathUtils.addOrThrow(dataPosition(), length));
+            return new LazyValue(this, start, length, type, loader);
+        } else {
+            return readValue(type, loader);
+        }
+    }
+
+    private static final class LazyValue implements Supplier<Object> {
+        private final int mPosition;
+        private final int mLength;
+        private final int mType;
+        @Nullable private final ClassLoader mLoader;
+        @Nullable private Parcel mSource;
+        @Nullable private Object mObject;
+        @Nullable private Parcel mValueParcel;
+
+        LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) {
+            mSource = source;
+            mPosition = position;
+            mLength = length;
+            mType = type;
+            mLoader = loader;
+        }
+
+        @Override
+        public Object get() {
+            if (mObject == null) {
+                int restore = mSource.dataPosition();
+                try {
+                    mSource.setDataPosition(mPosition);
+                    mObject = mSource.readValue(mLoader);
+                } finally {
+                    mSource.setDataPosition(restore);
+                }
+                mSource = null;
+                if (mValueParcel != null) {
+                    mValueParcel.recycle();
+                    mValueParcel = null;
+                }
+            }
+            return mObject;
+        }
+
+        public void writeToParcel(Parcel out) {
+            if (mObject == null) {
+                int restore = mSource.dataPosition();
+                try {
+                    mSource.setDataPosition(mPosition);
+                    out.writeInt(mSource.readInt()); // Type
+                    out.writeInt(mSource.readInt()); // Length
+                    out.appendFrom(mSource, mSource.dataPosition(), mLength);
+                } finally {
+                    mSource.setDataPosition(restore);
+                }
+            } else {
+                out.writeValue(mObject);
+            }
+        }
+
+        public boolean hasFileDescriptors() {
+            return getValueParcel().hasFileDescriptors();
+        }
+
+        @Override
+        public String toString() {
+            return mObject == null
+                    ? "Supplier{" + valueTypeToString(mType) + "@" + mPosition + "+" + mLength + '}'
+                    : "Supplier{" + mObject + "}";
+        }
+
+        /**
+         * We're checking if the *lazy value* is equal to another one, not if the *object*
+         * represented by the lazy value is equal to the other one. So, if there are two lazy values
+         * and one of them has been deserialized but the other hasn't this will always return false.
+         */
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof LazyValue)) {
+                return false;
+            }
+            LazyValue value = (LazyValue) other;
+            // Check if they are either both serialized or both deserialized
+            if ((mObject == null) != (value.mObject == null)) {
+                return false;
+            }
+            // If both are deserialized, compare the live objects
+            if (mObject != null) {
+                return mObject.equals(value.mObject);
+            }
+            // Better safely fail here since this could mean we get different objects
+            if (!Objects.equals(mLoader, value.mLoader)) {
+                return false;
+            }
+            // Otherwise compare metadata prior to comparing payload
+            if (mType != value.mType || mLength != value.mLength) {
+                return false;
+            }
+            // Finally we compare the payload
+            return getValueParcel().compareData(value.getValueParcel()) == 0;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mObject, mLoader, mType, mLength);
+        }
+
+        /** This extracts the parcel section responsible for the object and returns it. */
+        private Parcel getValueParcel() {
+            if (mValueParcel == null) {
+                mValueParcel = Parcel.obtain();
+                // mLength is the length of object representation, excluding the type and length.
+                // mPosition is the position of the entire value container, right before the type.
+                // So, we add 4 bytes for the type + 4 bytes for the length written.
+                mValueParcel.appendFrom(mSource, mPosition, mLength + 8);
+            }
+            return mValueParcel;
+        }
+    }
+
+    /**
+     * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the
+     * type first.
+     */
+    @Nullable
+    private Object readValue(int type, @Nullable ClassLoader loader) {
         switch (type) {
         case VAL_NULL:
             return null;
@@ -3266,6 +3599,20 @@
         }
     }
 
+    private boolean isLengthPrefixed(int type) {
+        switch (type) {
+            case VAL_PARCELABLE:
+            case VAL_PARCELABLEARRAY:
+            case VAL_LIST:
+            case VAL_SPARSEARRAY:
+            case VAL_BUNDLE:
+            case VAL_SERIALIZABLE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     /**
      * Read and return a new Parcelable from the parcel.  The given class loader
      * will be used to load any enclosed Parcelables.  If it is null, the default
@@ -3564,49 +3911,49 @@
         }
     }
 
-    /* package */ void readArrayMapInternal(@NonNull ArrayMap outVal, int N,
-            @Nullable ClassLoader loader) {
-        if (DEBUG_ARRAY_MAP) {
-            RuntimeException here =  new RuntimeException("here");
-            here.fillInStackTrace();
-            Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
-        }
-        int startPos;
-        while (N > 0) {
-            if (DEBUG_ARRAY_MAP) startPos = dataPosition();
-            String key = readString();
-            Object value = readValue(loader);
-            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Read #" + (N-1) + " "
-                    + (dataPosition()-startPos) + " bytes: key=0x"
-                    + Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
-            outVal.append(key, value);
-            N--;
-        }
-        outVal.validate();
+    /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal,
+            int size, @Nullable ClassLoader loader) {
+        readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader);
     }
 
-    /* package */ void readArrayMapSafelyInternal(@NonNull ArrayMap outVal, int N,
-            @Nullable ClassLoader loader) {
-        if (DEBUG_ARRAY_MAP) {
-            RuntimeException here =  new RuntimeException("here");
-            here.fillInStackTrace();
-            Log.d(TAG, "Reading safely " + N + " ArrayMap entries", here);
-        }
-        while (N > 0) {
+    /**
+     * Reads a map into {@code map}.
+     *
+     * @param sorted Whether the keys are sorted by their hashes, if so we use an optimized path.
+     * @param lazy   Whether to populate the map with lazy {@link Supplier} objects for
+     *               length-prefixed values. See {@link Parcel#readLazyValue(ClassLoader)} for more
+     *               details.
+     * @return whether the parcel can be recycled or not.
+     * @hide
+     */
+    boolean readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
+            boolean lazy, @Nullable ClassLoader loader) {
+        boolean recycle = true;
+        while (size > 0) {
             String key = readString();
-            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Read safe #" + (N-1) + ": key=0x"
-                    + (key != null ? key.hashCode() : 0) + " " + key);
-            Object value = readValue(loader);
-            outVal.put(key, value);
-            N--;
+            Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
+            if (value instanceof LazyValue) {
+                recycle = false;
+            }
+            if (sorted) {
+                map.append(key, value);
+            } else {
+                map.put(key, value);
+            }
+            size--;
         }
+        if (sorted) {
+            map.validate();
+        }
+        return recycle;
     }
 
     /**
      * @hide For testing only.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void readArrayMap(@NonNull ArrayMap outVal, @Nullable ClassLoader loader) {
+    public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal,
+            @Nullable ClassLoader loader) {
         final int N = readInt();
         if (N < 0) {
             return;
@@ -3691,4 +4038,38 @@
     public long getBlobAshmemSize() {
         return nativeGetBlobAshmemSize(mNativePtr);
     }
+
+    private static String valueTypeToString(int type) {
+        switch (type) {
+            case VAL_NULL: return "VAL_NULL";
+            case VAL_INTEGER: return "VAL_INTEGER";
+            case VAL_MAP: return "VAL_MAP";
+            case VAL_BUNDLE: return "VAL_BUNDLE";
+            case VAL_PERSISTABLEBUNDLE: return "VAL_PERSISTABLEBUNDLE";
+            case VAL_PARCELABLE: return "VAL_PARCELABLE";
+            case VAL_SHORT: return "VAL_SHORT";
+            case VAL_LONG: return "VAL_LONG";
+            case VAL_FLOAT: return "VAL_FLOAT";
+            case VAL_DOUBLE: return "VAL_DOUBLE";
+            case VAL_BOOLEAN: return "VAL_BOOLEAN";
+            case VAL_CHARSEQUENCE: return "VAL_CHARSEQUENCE";
+            case VAL_LIST: return "VAL_LIST";
+            case VAL_SPARSEARRAY: return "VAL_SPARSEARRAY";
+            case VAL_BOOLEANARRAY: return "VAL_BOOLEANARRAY";
+            case VAL_BYTEARRAY: return "VAL_BYTEARRAY";
+            case VAL_STRINGARRAY: return "VAL_STRINGARRAY";
+            case VAL_CHARSEQUENCEARRAY: return "VAL_CHARSEQUENCEARRAY";
+            case VAL_IBINDER: return "VAL_IBINDER";
+            case VAL_PARCELABLEARRAY: return "VAL_PARCELABLEARRAY";
+            case VAL_INTARRAY: return "VAL_INTARRAY";
+            case VAL_LONGARRAY: return "VAL_LONGARRAY";
+            case VAL_BYTE: return "VAL_BYTE";
+            case VAL_SIZE: return "VAL_SIZE";
+            case VAL_SIZEF: return "VAL_SIZEF";
+            case VAL_DOUBLEARRAY: return "VAL_DOUBLEARRAY";
+            case VAL_OBJECTARRAY: return "VAL_OBJECTARRAY";
+            case VAL_SERIALIZABLE: return "VAL_SERIALIZABLE";
+            default: return "UNKNOWN(" + type + ")";
+        }
+    }
 }