diff options
author | 2022-02-22 15:40:19 +0000 | |
---|---|---|
committer | 2022-02-22 15:40:19 +0000 | |
commit | c95042682ff2bf76d499bf565ff0fec14236682f (patch) | |
tree | 11114c0b638467583f3edf6c036e48a8c694bdad | |
parent | f9514c51522320c8825c5fb219df98d6c2bf6996 (diff) | |
parent | 854cd54abdf517ef53e758dc6458d193fd0841af (diff) |
Merge "Add safer Bundle APIs and deprecated old ones" am: c5589d2c92 am: ce09bba3fc am: aed366dc04 am: 854cd54abd
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1988908
Change-Id: I797488b82ad5471d90483a8fa657ce2bcdf846e9
-rw-r--r-- | core/api/current.txt | 17 | ||||
-rw-r--r-- | core/java/android/os/BadTypeParcelableException.java | 30 | ||||
-rw-r--r-- | core/java/android/os/BaseBundle.java | 147 | ||||
-rw-r--r-- | core/java/android/os/Bundle.java | 152 | ||||
-rw-r--r-- | core/java/android/os/Parcel.java | 205 | ||||
-rw-r--r-- | core/java/com/android/internal/util/ArrayUtils.java | 9 |
6 files changed, 424 insertions, 136 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index f0cc128ee2cf..5dd85c0d9ac4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -30697,7 +30697,7 @@ package android.os { public class BaseBundle { method public void clear(); method public boolean containsKey(String); - method @Nullable public Object get(String); + method @Deprecated @Nullable public Object get(String); method public boolean getBoolean(String); method public boolean getBoolean(String, boolean); method @Nullable public boolean[] getBooleanArray(@Nullable String); @@ -30939,16 +30939,21 @@ package android.os { method public float getFloat(String, float); method @Nullable public float[] getFloatArray(@Nullable String); method @Nullable public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String); - method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String); - method @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Deprecated @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String); + method @Nullable public <T> T getParcelable(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); + method @Nullable public <T> T[] getParcelableArray(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String); + method @Nullable public <T> java.util.ArrayList<T> getParcelableArrayList(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Nullable public <T extends java.io.Serializable> T getSerializable(@Nullable String, @NonNull Class<T>); method public short getShort(String); method public short getShort(String, short); method @Nullable public short[] getShortArray(@Nullable String); method @Nullable public android.util.Size getSize(@Nullable String); method @Nullable public android.util.SizeF getSizeF(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String); + method @Deprecated @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String); + method @Nullable public <T> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String, @NonNull Class<T>); method @Nullable public java.util.ArrayList<java.lang.String> getStringArrayList(@Nullable String); method public boolean hasFileDescriptors(); method public void putAll(android.os.Bundle); diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java new file mode 100644 index 000000000000..2ca3bd2adca1 --- /dev/null +++ b/core/java/android/os/BadTypeParcelableException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** Used by Parcel to signal that the type on the payload was not expected by the caller. */ +class BadTypeParcelableException extends BadParcelableException { + BadTypeParcelableException(String msg) { + super(msg); + } + BadTypeParcelableException(Exception cause) { + super(cause); + } + BadTypeParcelableException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 244335d3aa06..45812e551618 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -16,6 +16,8 @@ package android.os; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -31,7 +33,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Set; -import java.util.function.Function; +import java.util.function.BiFunction; /** * A mapping from String keys to values of various types. In most cases, you @@ -254,8 +256,8 @@ public class BaseBundle { } try { return getValueAt(0, String.class); - } catch (ClassCastException | BadParcelableException e) { - typeWarning("getPairValue()", /* value */ null, "String", e); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning("getPairValue()", "String", e); return null; } } @@ -320,28 +322,46 @@ public class BaseBundle { * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. * + * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in + * other deprecated APIs. + * * @hide */ + @Deprecated + @Nullable final Object getValue(String key) { return getValue(key, /* clazz */ null); } + /** Same as {@link #getValue(String, Class, Class[])} with no item types. */ + @Nullable + final <T> T getValue(String key, @Nullable Class<T> clazz) { + // Avoids allocating Class[0] array + return getValue(key, clazz, (Class<?>[]) null); + } + /** - * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code + * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code * null} for no type check). * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. + * * 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) { + @Nullable + final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) { int i = mMap.indexOfKey(key); - return (i >= 0) ? getValueAt(i, clazz) : null; + return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null; } /** - * Returns the value for a certain position in the array map. + * Returns the value for a certain position in the array map for expected return type {@code + * clazz} (or pass {@code null} for no type check). + * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. * * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. @@ -349,11 +369,12 @@ public class BaseBundle { * @hide */ @SuppressWarnings("unchecked") - final <T> T getValueAt(int i, @Nullable Class<T> clazz) { + @Nullable + final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) { Object object = mMap.valueAt(i); - if (object instanceof Function<?, ?>) { + if (object instanceof BiFunction<?, ?, ?>) { try { - object = ((Function<Class<?>, ?>) object).apply(clazz); + object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); @@ -615,7 +636,11 @@ public class BaseBundle { * * @param key a String key * @return an Object, or null + * + * @deprecated Use the type-safe specific APIs depending on the type of the item to be + * retrieved, eg. {@link #getString(String)}. */ + @Deprecated @Nullable public Object get(String key) { unparcel(); @@ -623,6 +648,32 @@ public class BaseBundle { } /** + * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <p>Use the more specific APIs where possible, especially in the case of containers such as + * lists, since those APIs allow you to specify the type of the items. + * + * @param key String key + * @param clazz The type of the object expected + * @return an Object, or null + */ + @Nullable + <T> T get(@Nullable String key, @NonNull Class<T> clazz) { + unparcel(); + try { + return getValue(key, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName(), e); + return null; + } + } + + /** * Removes any entry with the given key from the mapping of this Bundle. * * @param key a String key @@ -1006,7 +1057,7 @@ public class BaseBundle { sb.append(" but value was a "); sb.append(value.getClass().getName()); } else { - sb.append(" but value was of a different type "); + sb.append(" but value was of a different type"); } sb.append(". The default value "); sb.append(defaultValue); @@ -1019,6 +1070,10 @@ public class BaseBundle { typeWarning(key, value, className, "<null>", e); } + void typeWarning(String key, String className, RuntimeException e) { + typeWarning(key, /* value */ null, className, "<null>", e); + } + /** * Returns the value associated with the given key, or defaultValue if * no mapping of the desired type exists for the given key. @@ -1358,7 +1413,11 @@ public class BaseBundle { * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in + * other deprecated APIs. */ + @Deprecated @Nullable Serializable getSerializable(@Nullable String key) { unparcel(); @@ -1375,6 +1434,36 @@ public class BaseBundle { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) { + return get(key, clazz); + } + + + @SuppressWarnings("unchecked") + @Nullable + <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<T> clazz) { + unparcel(); + try { + return getValue(key, ArrayList.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. @@ -1384,17 +1473,7 @@ public class BaseBundle { */ @Nullable ArrayList<Integer> getIntegerArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<Integer>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<Integer>", e); - return null; - } + return getArrayList(key, Integer.class); } /** @@ -1407,17 +1486,7 @@ public class BaseBundle { */ @Nullable ArrayList<String> getStringArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<String>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<String>", e); - return null; - } + return getArrayList(key, String.class); } /** @@ -1430,17 +1499,7 @@ public class BaseBundle { */ @Nullable ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<CharSequence>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<CharSequence>", e); - return null; - } + return getArrayList(key, CharSequence.class); } /** diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 2b13f20ffe6d..edbbb59ac77d 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Size; @@ -876,7 +877,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { @Nullable public Bundle getBundle(@Nullable String key) { unparcel(); - Object o = getValue(key); + Object o = mMap.get(key); if (o == null) { return null; } @@ -899,7 +900,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> T getParcelable(@Nullable String key) { unparcel(); @@ -916,30 +921,28 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** - * 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. + * Returns the value associated with the given key or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> * * <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. + * @param clazz The type of the object expected * @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; - } + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return get(key, clazz); } /** @@ -953,7 +956,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable[] value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public Parcelable[] getParcelableArray(@Nullable String key) { unparcel(); @@ -970,6 +977,39 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <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 items inside the array + * @return a Parcelable[] value, or {@code null} + */ + @SuppressLint({"ArrayReturn", "NullableCollection"}) + @SuppressWarnings("unchecked") + @Nullable + public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting. + return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName() + "[]", 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 {@code null} * value is explicitly associated with the key. @@ -980,7 +1020,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return an ArrayList<T> value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) { unparcel(); @@ -997,14 +1041,43 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <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 items inside the array list + * @return an ArrayList<T> value, or {@code null} + */ + @SuppressLint("NullableCollection") + @SuppressWarnings("unchecked") + @Nullable + public <T> ArrayList<T> getParcelableArrayList(@Nullable String key, @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return getArrayList(key, clazz); + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * * @param key a String, or null - * * @return a SparseArray of T values, or null + * + * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) { unparcel(); @@ -1021,13 +1094,44 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @return a SparseArray of T values, or null + */ + @SuppressWarnings("unchecked") + @Nullable + public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key, + @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Override @Nullable public Serializable getSerializable(@Nullable String key) { @@ -1035,6 +1139,24 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + public <T extends Serializable> T getSerializable(@Nullable String key, + @NonNull Class<T> clazz) { + return super.getSerializable(key, requireNonNull(clazz)); + } + + /** * Returns the value associated with the given key, or 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 ae923530b651..09cfb6e70cba 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -16,6 +16,8 @@ package android.os; +import static com.android.internal.util.Preconditions.checkArgument; + import static java.util.Objects.requireNonNull; import android.annotation.IntDef; @@ -65,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -287,26 +290,26 @@ public final class Parcel { private static final int VAL_NULL = -1; private static final int VAL_STRING = 0; private static final int VAL_INTEGER = 1; - private static final int VAL_MAP = 2; + private static final int VAL_MAP = 2; // length-prefixed private static final int VAL_BUNDLE = 3; - private static final int VAL_PARCELABLE = 4; + private static final int VAL_PARCELABLE = 4; // length-prefixed private static final int VAL_SHORT = 5; private static final int VAL_LONG = 6; private static final int VAL_FLOAT = 7; private static final int VAL_DOUBLE = 8; private static final int VAL_BOOLEAN = 9; private static final int VAL_CHARSEQUENCE = 10; - private static final int VAL_LIST = 11; - private static final int VAL_SPARSEARRAY = 12; + private static final int VAL_LIST = 11; // length-prefixed + private static final int VAL_SPARSEARRAY = 12; // length-prefixed private static final int VAL_BYTEARRAY = 13; private static final int VAL_STRINGARRAY = 14; private static final int VAL_IBINDER = 15; - private static final int VAL_PARCELABLEARRAY = 16; - private static final int VAL_OBJECTARRAY = 17; + private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed + private static final int VAL_OBJECTARRAY = 17; // length-prefixed private static final int VAL_INTARRAY = 18; private static final int VAL_LONGARRAY = 19; private static final int VAL_BYTE = 20; - private static final int VAL_SERIALIZABLE = 21; + private static final int VAL_SERIALIZABLE = 21; // length-prefixed private static final int VAL_SPARSEBOOLEANARRAY = 22; private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; @@ -3179,8 +3182,7 @@ public final class Parcel { */ @Deprecated public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { - int n = readInt(); - readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null); + readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3195,8 +3197,7 @@ public final class Parcel { @NonNull Class<V> clazzValue) { Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); - int n = readInt(); - readMapInternal(outVal, n, loader, clazzKey, clazzValue); + readMapInternal(outVal, loader, clazzKey, clazzValue); } /** @@ -3245,13 +3246,7 @@ public final class Parcel { @Deprecated @Nullable public HashMap readHashMap(@Nullable ClassLoader loader) { - int n = readInt(); - if (n < 0) { - return null; - } - HashMap m = new HashMap(n); - readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null); - return m; + return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3267,13 +3262,7 @@ public final class Parcel { @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); - int n = readInt(); - if (n < 0) { - return null; - } - HashMap<K, V> map = new HashMap<>(n); - readMapInternal(map, n, loader, clazzKey, clazzValue); - return map; + return readHashMapInternal(loader, clazzKey, clazzValue); } /** @@ -4296,16 +4285,17 @@ public final class Parcel { /** - * @param clazz The type of the object expected or {@code null} for performing no checks. + * @see #readValue(int, ClassLoader, Class, Class[]) */ @Nullable - private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) { + private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz, + @Nullable Class<?>... itemTypes) { int type = readInt(); final T object; if (isLengthPrefixed(type)) { int length = readInt(); int start = dataPosition(); - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); int actual = dataPosition() - start; if (actual != length) { Slog.wtfStack(TAG, @@ -4313,25 +4303,26 @@ public final class Parcel { + " consumed " + actual + " bytes, but " + length + " expected."); } } else { - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); } return object; } /** - * 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. + * This will return a {@link BiFunction} for length-prefixed types that deserializes the object + * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link + * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it + * will return the object itself. * - * <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 + * <p>After calling {@link BiFunction#apply} 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 function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two * function objects are equal if either of the following is true: * <ul> - * <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: + * <li>{@link BiFunction#apply} has been called on both and both objects returned are equal. + * <li>{@link BiFunction#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. @@ -4358,7 +4349,7 @@ public final class Parcel { } - private static final class LazyValue implements Function<Class<?>, Object> { + private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** * | 4B | 4B | * mSource = Parcel{... | type | length | object | ...} @@ -4390,7 +4381,7 @@ public final class Parcel { } @Override - public Object apply(@Nullable Class<?> clazz) { + public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) { Parcel source = mSource; if (source != null) { synchronized (source) { @@ -4399,7 +4390,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz); + mObject = source.readValue(mLoader, clazz, itemTypes); } finally { source.setDataPosition(restore); } @@ -4479,14 +4470,25 @@ public final class Parcel { } } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ + private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { + // Avoids allocating Class[0] array + return readValue(type, loader, clazz, (Class<?>[]) null); + } + /** * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the * type first. + * * @param clazz The type of the object expected or {@code null} for performing no checks. + * @param itemTypes If the value is a container, these represent the item types (eg. for a list + * it's the item type, for a map, it's the key type, followed by the value + * type). */ @SuppressWarnings("unchecked") @Nullable - private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { + private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz, + @Nullable Class<?>... itemTypes) { final Object object; switch (type) { case VAL_NULL: @@ -4502,7 +4504,11 @@ public final class Parcel { break; case VAL_MAP: - object = readHashMap(loader); + checkTypeToUnparcel(clazz, HashMap.class); + Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0); + Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1); + checkArgument((keyType == null) == (valueType == null)); + object = readHashMapInternal(loader, keyType, valueType); break; case VAL_PARCELABLE: @@ -4533,10 +4539,12 @@ public final class Parcel { object = readCharSequence(); break; - case VAL_LIST: - object = readArrayList(loader); + case VAL_LIST: { + checkTypeToUnparcel(clazz, ArrayList.class); + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readArrayListInternal(loader, itemType); break; - + } case VAL_BOOLEANARRAY: object = createBooleanArray(); break; @@ -4557,10 +4565,12 @@ public final class Parcel { object = readStrongBinder(); break; - case VAL_OBJECTARRAY: - object = readArray(loader); + case VAL_OBJECTARRAY: { + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class); + object = readArrayInternal(loader, itemType); break; - + } case VAL_INTARRAY: object = createIntArray(); break; @@ -4577,14 +4587,18 @@ public final class Parcel { object = readSerializableInternal(loader, clazz); break; - case VAL_PARCELABLEARRAY: - object = readParcelableArray(loader); + case VAL_PARCELABLEARRAY: { + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class); + object = readParcelableArrayInternal(loader, itemType); break; - - case VAL_SPARSEARRAY: - object = readSparseArray(loader); + } + case VAL_SPARSEARRAY: { + checkTypeToUnparcel(clazz, SparseArray.class); + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readSparseArrayInternal(loader, itemType); break; - + } case VAL_SPARSEBOOLEANARRAY: object = readSparseBooleanArray(); break; @@ -4632,7 +4646,7 @@ public final class Parcel { + " at offset " + off); } if (object != null && clazz != null && !clazz.isInstance(object)) { - throw new BadParcelableException("Unparcelled object " + object + throw new BadTypeParcelableException("Unparcelled object " + object + " is not an instance of required class " + clazz.getName() + " provided in the parameter"); } @@ -4659,6 +4673,38 @@ public final class Parcel { } /** + * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of + * {@code requiredArrayType}. + */ + private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType, + Class<?> componentTypeToUnparcel) { + if (requiredArrayType != null) { + // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check + Class<?> requiredComponentType = requiredArrayType.getComponentType(); + if (requiredComponentType == null) { + throw new BadTypeParcelableException( + "About to unparcel an array but type " + + requiredArrayType.getCanonicalName() + + " required by caller is not an array."); + } + checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel); + } + } + + /** + * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code + * requiredType} is not {@code null}. + */ + private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) { + if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) { + throw new BadTypeParcelableException( + "About to unparcel a " + typeToUnparcel.getCanonicalName() + + ", which is not a subtype of type " + requiredType.getCanonicalName() + + " required by caller."); + } + } + + /** * 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 * class loader will be used. @@ -4788,7 +4834,7 @@ public final class Parcel { if (clazz != null) { Class<?> parcelableClass = creator.getClass().getEnclosingClass(); if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4811,7 +4857,7 @@ public final class Parcel { } if (clazz != null) { if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4872,15 +4918,7 @@ public final class Parcel { @Deprecated @Nullable public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - Parcelable[] p = new Parcelable[N]; - for (int i = 0; i < N; i++) { - p[i] = readParcelable(loader); - } - return p; + return readParcelableArrayInternal(loader, /* clazz */ null); } /** @@ -4892,14 +4930,20 @@ public final class Parcel { * trying to instantiate an element. */ @SuppressLint({"ArrayReturn", "NullableCollection"}) - @SuppressWarnings("unchecked") @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + return readParcelableArrayInternal(loader, requireNonNull(clazz)); + } + + @SuppressWarnings("unchecked") + @Nullable + private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader, + @Nullable Class<T> clazz) { int n = readInt(); if (n < 0) { return null; } - T[] p = (T[]) Array.newInstance(clazz, n); + T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n)); for (int i = 0; i < n; i++) { p[i] = readParcelableInternal(loader, clazz); } @@ -4962,7 +5006,7 @@ public final class Parcel { // the class the same way as ObjectInputStream, using the provided classloader. Class<?> cl = Class.forName(name, false, loader); if (!clazz.isAssignableFrom(cl)) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + cl.getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4987,7 +5031,7 @@ public final class Parcel { // the deserialized object, as we cannot resolve the class the same way as // ObjectInputStream. if (!clazz.isAssignableFrom(object.getClass())) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + object.getClass().getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -5097,7 +5141,26 @@ public final class Parcel { readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null); } - /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n, + @Nullable + private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader, + @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { + int n = readInt(); + if (n < 0) { + return null; + } + HashMap<K, V> map = new HashMap<>(n); + readMapInternal(map, n, loader, clazzKey, clazzValue); + return map; + } + + private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, + @Nullable ClassLoader loader, @Nullable Class<K> clazzKey, + @Nullable Class<V> clazzValue) { + int n = readInt(); + readMapInternal(outVal, n, loader, clazzKey, clazzValue); + } + + private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n, @Nullable ClassLoader loader, @Nullable Class<K> clazzKey, @Nullable Class<V> clazzValue) { while (n > 0) { @@ -5108,7 +5171,7 @@ public final class Parcel { } } - /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, + private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, int size, @Nullable ClassLoader loader) { readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 9bdcddf38c61..1fd04109ae45 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -888,6 +888,15 @@ public class ArrayUtils { } } + /** + * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code + * null}, otherwise returns {@code null}. + */ + @Nullable + public static <T> T getOrNull(@Nullable T[] items, int i) { + return (items != null && items.length > i) ? items[i] : null; + } + public static @Nullable <T> T firstOrNull(T[] items) { return items.length > 0 ? items[0] : null; } |