Add @hide Bundle.getParcelable() with explicit type

Provider a type-safer API getParcelable() that takes a Class<?>
parameter.

Test: Manual (tested downstream)
Bug: 213169612
Bug: 212804042
Bug: 212803946
Bug: 210885162
Merged-In: Ieebc044043e0776e71d35c1cc11be9299f972c45
Change-Id: I76f9558bfa1525877bec15cfc43abdc43cba0f24
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index ad3de25..244335d 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -31,7 +31,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Set;
-import java.util.function.Supplier;
+import java.util.function.Function;
 
 /**
  * A mapping from String keys to values of various types. In most cases, you
@@ -252,11 +252,10 @@
         if (size == 0) {
             return null;
         }
-        Object o = getValueAt(0);
         try {
-            return (String) o;
-        } catch (ClassCastException e) {
-            typeWarning("getPairValue()", o, "String", e);
+            return getValueAt(0, String.class);
+        } catch (ClassCastException | BadParcelableException e) {
+            typeWarning("getPairValue()", /* value */ null, "String", e);
             return null;
         }
     }
@@ -309,7 +308,7 @@
                 }
                 for (int i = 0, n = mMap.size(); i < n; i++) {
                     // Triggers deserialization of i-th item, if needed
-                    getValueAt(i);
+                    getValueAt(i, /* clazz */ null);
                 }
             }
         }
@@ -324,8 +323,21 @@
      * @hide
      */
     final Object getValue(String key) {
+        return getValue(key, /* clazz */ null);
+    }
+
+    /**
+     * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code
+     * null} for no type check).
+     *
+     * This call should always be made after {@link #unparcel()} or inside a lock after making sure
+     * {@code mMap} is not null.
+     *
+     * @hide
+     */
+    final <T> T getValue(String key, @Nullable Class<T> clazz) {
         int i = mMap.indexOfKey(key);
-        return (i >= 0) ? getValueAt(i) : null;
+        return (i >= 0) ? getValueAt(i, clazz) : null;
     }
 
     /**
@@ -336,11 +348,12 @@
      *
      * @hide
      */
-    final Object getValueAt(int i) {
+    @SuppressWarnings("unchecked")
+    final <T> T getValueAt(int i, @Nullable Class<T> clazz) {
         Object object = mMap.valueAt(i);
-        if (object instanceof Supplier<?>) {
+        if (object instanceof Function<?, ?>) {
             try {
-                object = ((Supplier<?>) object).get();
+                object = ((Function<Class<?>, ?>) object).apply(clazz);
             } catch (BadParcelableException e) {
                 if (sShouldDefuse) {
                     Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e);
@@ -351,7 +364,7 @@
             }
             mMap.setValueAt(i, object);
         }
-        return object;
+        return (clazz != null) ? clazz.cast(object) : (T) object;
     }
 
     private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
@@ -528,7 +541,7 @@
         } 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
+            // deserialized yet, it's a function object (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).
@@ -982,15 +995,19 @@
     }
 
     // Log a message if the value was non-null but not of the expected type
-    void typeWarning(String key, Object value, String className,
-            Object defaultValue, ClassCastException e) {
+    void typeWarning(String key, @Nullable Object value, String className,
+            Object defaultValue, RuntimeException e) {
         StringBuilder sb = new StringBuilder();
         sb.append("Key ");
         sb.append(key);
         sb.append(" expected ");
         sb.append(className);
-        sb.append(" but value was a ");
-        sb.append(value.getClass().getName());
+        if (value != null) {
+            sb.append(" but value was a ");
+            sb.append(value.getClass().getName());
+        } else {
+            sb.append(" but value was of a different type ");
+        }
         sb.append(".  The default value ");
         sb.append(defaultValue);
         sb.append(" was returned.");
@@ -998,8 +1015,7 @@
         Log.w(TAG, "Attempt to cast generated internal exception:", e);
     }
 
-    void typeWarning(String key, Object value, String className,
-            ClassCastException e) {
+    void typeWarning(String key, @Nullable Object value, String className, RuntimeException e) {
         typeWarning(key, value, className, "<null>", e);
     }
 
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index b2bbfd6..2b13f20 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ArrayMap;
@@ -914,6 +917,33 @@
 
     /**
      * Returns the value associated with the given key, or {@code null} if
+     * no mapping of the desired type exists for the given key or a {@code null}
+     * value is explicitly associated with the key.
+     *
+     * <p><b>Note: </b> if the expected value is not a class provided by the Android platform,
+     * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first.
+     * Otherwise, this method might throw an exception or return {@code null}.
+     *
+     * @param key a String, or {@code null}
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     * @return a Parcelable value, or {@code null}
+     *
+     * @hide
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) {
+        unparcel();
+        try {
+            return getValue(key, requireNonNull(clazz));
+        } catch (ClassCastException | BadParcelableException e) {
+            typeWarning(key, /* value */ null, "Parcelable", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or {@code null} if
      * no mapping of the desired type exists for the given key or a null
      * value is explicitly associated with the key.
      *
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 13d1d96..32cee94 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4312,18 +4312,19 @@
     }
 
     /**
-     * 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.
+     * This will return a {@link Function} for length-prefixed types that deserializes the object
+     * when {@link Function#apply} is called with the expected class of the return object (or {@code
+     * null} for no type check), 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
+     * <p>After calling {@link Function#apply(Object)} 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:
+     * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two
+     * function objects 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:
+     *   <li>{@link Function#apply} has been called on both and both objects returned are equal.
+     *   <li>{@link Function#apply} 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.
@@ -4350,7 +4351,7 @@
     }
 
 
-    private static final class LazyValue implements Supplier<Object> {
+    private static final class LazyValue implements Function<Class<?>, Object> {
         /**
          *                      |   4B   |   4B   |
          * mSource = Parcel{... |  type  | length | object | ...}
@@ -4382,7 +4383,7 @@
         }
 
         @Override
-        public Object get() {
+        public Object apply(@Nullable Class<?> clazz) {
             Parcel source = mSource;
             if (source != null) {
                 synchronized (source) {
@@ -4391,7 +4392,7 @@
                         int restore = source.dataPosition();
                         try {
                             source.setDataPosition(mPosition);
-                            mObject = source.readValue(mLoader);
+                            mObject = source.readValue(mLoader, clazz);
                         } finally {
                             source.setDataPosition(restore);
                         }