Add compat methods for new Bundle and Intent APIs
We have introduced new, safer, APIs for Parcel, Bundle and Intent in T
that we want to encourage developers to use. However the APIs have a bug
(b/232589966) which could hinder adoption. As such, we are adding
previously planned Compat methods that developers can use in all Andorid
versios, that will not use the T Apis until U where the bug is addressed.
There are subtle differences in the implementations of the platform
APIs, and so the behaviour of the compat methods:
1. Get Parcelable
public static <T> T getParcelable(@NonNull Bundle in, @Nullable String
key, @NonNull Class<T> clazz)
public static <T> T getParcelableExtra(@NonNull Intent in, @Nullable
String name, @NonNull Class<T> clazz)
These methods match the method signature and behaviour of the post-T,
type-safe APIs - return the value if it exists and is of the `clazz`
type, otherwise return null.
2. Get Collection
public static <T> ArrayList<T> getParcelableArrayList(@NonNull Bundle
in, @Nullable String key, @NonNull Class<? extends T> clazz)
public static <T> SparseArray<T> getSparseParcelableArray(@NonNull
Bundle in, @Nullable String key, @NonNull Class<? extends T> clazz)
public static <T> ArrayList<T> getParcelableArrayListExtra(@NonNull
Intent in, @Nullable String name, @NonNull Class<? extends T>clazz)
On these methods, the method signature matches the post-T APIs, but type
checking is only done on U+ devices, on first deserialisation. Checking
outside of this scenario requires a linear traversal of the array on each
call, and so is not done for performance (see b/222087511). Initial
benchmarking shows a 400x difference for large arrays.
3. Get Array
public static Parcelable[] getParcelableArray(@NonNull Bundle in,
@Nullable String key, @NonNull Class<?> clazz)
public static Parcelable[] getParcelableArrayExtra(@NonNull Intent in,
@Nullable String name, @NonNull Class<?> clazz)
Due to Java generics erasure at runtime, the post-T method signature can
not be safely used on pre-U devices, as the pre-T platform APIs return a
Parcelable[] rather than T[], which can not be implicitely cast to a
subtype even if all items conform. The cost of manually casting is large
(see above). Therefore, we always return Parcelable[] on all device
versions, and we only type-check on U+ devices.
The ParcelCompat methods have been updated to match this behaviour. For
readArray, this is a source breaking API change. But
the current method signature would fail on any current devices if
assigned to anything other than an Object[] - succeeding
at compile time, but failing with a ClassCastException at runtime.
For readParcelableArray, it has been deprecated and replaced with
readParcelableArrayTyped, as updating the type signature would be a ABI
breaking change for libraries.
Bug: 242048899
Test: atest ParcelCompat BundleCompat IntentCompat on T and U devices
Test: Check binary compatibility manually with a test library.
Relnote: "Adds compatibility methods for new APIs introduced in Android
13 for Parcels, Bundles, and Intents.
Note: Some ParcelCompat method signatures have been updated, and may
require a source change on upgrade to confirm to the new signature."
Change-Id: I57e94c6efcc674173d201205fb175cef495bcf82
diff --git a/core/core/api/current.ignore b/core/core/api/current.ignore
new file mode 100644
index 0000000..3486258
--- /dev/null
+++ b/core/core/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.core.os.ParcelCompat#readArray(android.os.Parcel, ClassLoader, Class<T>):
+ Method androidx.core.os.ParcelCompat.readArray has changed return type from T[] to Object[]
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index cce42f2..87c7c28 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1052,6 +1052,9 @@
public final class IntentCompat {
method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
+ method public static android.os.Parcelable![]? getParcelableArrayExtra(android.content.Intent, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayListExtra(android.content.Intent, String?, Class<? extends T>);
+ method public static <T> T? getParcelableExtra(android.content.Intent, String?, Class<T!>);
method public static android.content.Intent makeMainSelectorActivity(String, String);
field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
@@ -1773,6 +1776,13 @@
method @Deprecated @ChecksSdkIntAtLeast(api=31, codename="S") public static boolean isAtLeastS();
}
+ public final class BundleCompat {
+ method public static <T> T? getParcelable(android.os.Bundle, String?, Class<T!>);
+ method public static android.os.Parcelable![]? getParcelableArray(android.os.Bundle, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayList(android.os.Bundle, String?, Class<? extends T>);
+ method public static <T> android.util.SparseArray<T!>? getSparseParcelableArray(android.os.Bundle, String?, Class<? extends T>);
+ }
+
public final class CancellationSignal {
ctor public CancellationSignal();
method public void cancel();
@@ -1835,14 +1845,15 @@
}
public final class ParcelCompat {
- method public static <T> T![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> Object![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
method public static <T> java.util.ArrayList<T!>? readArrayList(android.os.Parcel, ClassLoader?, Class<? extends T>);
method public static boolean readBoolean(android.os.Parcel);
method public static <K, V> java.util.HashMap<K!,V!>? readHashMap(android.os.Parcel, ClassLoader?, Class<? extends K>, Class<? extends V>);
method public static <T> void readList(android.os.Parcel, java.util.List<? super T>, ClassLoader?, Class<T!>);
method public static <K, V> void readMap(android.os.Parcel, java.util.Map<? super K,? super V>, ClassLoader?, Class<K!>, Class<V!>);
method public static <T extends android.os.Parcelable> T? readParcelable(android.os.Parcel, ClassLoader?, Class<T!>);
- method public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @Deprecated public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.os.Parcelable![]? readParcelableArrayTyped(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(30) public static <T> android.os.Parcelable.Creator<T!>? readParcelableCreator(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(api=android.os.Build.VERSION_CODES.Q) public static <T> java.util.List<T!> readParcelableList(android.os.Parcel, java.util.List<T!>, ClassLoader?, Class<T!>);
method public static <T extends java.io.Serializable> T? readSerializable(android.os.Parcel, ClassLoader?, Class<T!>);
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 381de1d..2920156 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1052,6 +1052,9 @@
public final class IntentCompat {
method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
+ method public static android.os.Parcelable![]? getParcelableArrayExtra(android.content.Intent, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayListExtra(android.content.Intent, String?, Class<? extends T>);
+ method public static <T> T? getParcelableExtra(android.content.Intent, String?, Class<T!>);
method public static android.content.Intent makeMainSelectorActivity(String, String);
field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
@@ -1779,6 +1782,13 @@
@RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface BuildCompat.PrereleaseSdkCheck {
}
+ public final class BundleCompat {
+ method public static <T> T? getParcelable(android.os.Bundle, String?, Class<T!>);
+ method public static android.os.Parcelable![]? getParcelableArray(android.os.Bundle, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayList(android.os.Bundle, String?, Class<? extends T>);
+ method public static <T> android.util.SparseArray<T!>? getSparseParcelableArray(android.os.Bundle, String?, Class<? extends T>);
+ }
+
public final class CancellationSignal {
ctor public CancellationSignal();
method public void cancel();
@@ -1841,14 +1851,15 @@
}
public final class ParcelCompat {
- method public static <T> T![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> Object![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
method public static <T> java.util.ArrayList<T!>? readArrayList(android.os.Parcel, ClassLoader?, Class<? extends T>);
method public static boolean readBoolean(android.os.Parcel);
method public static <K, V> java.util.HashMap<K!,V!>? readHashMap(android.os.Parcel, ClassLoader?, Class<? extends K>, Class<? extends V>);
method public static <T> void readList(android.os.Parcel, java.util.List<? super T>, ClassLoader?, Class<T!>);
method public static <K, V> void readMap(android.os.Parcel, java.util.Map<? super K,? super V>, ClassLoader?, Class<K!>, Class<V!>);
method public static <T extends android.os.Parcelable> T? readParcelable(android.os.Parcel, ClassLoader?, Class<T!>);
- method public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @Deprecated public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.os.Parcelable![]? readParcelableArrayTyped(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(30) public static <T> android.os.Parcelable.Creator<T!>? readParcelableCreator(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(api=android.os.Build.VERSION_CODES.Q) public static <T> java.util.List<T!> readParcelableList(android.os.Parcel, java.util.List<T!>, ClassLoader?, Class<T!>);
method public static <T extends java.io.Serializable> T? readSerializable(android.os.Parcel, ClassLoader?, Class<T!>);
diff --git a/core/core/api/restricted_current.ignore b/core/core/api/restricted_current.ignore
new file mode 100644
index 0000000..3486258
--- /dev/null
+++ b/core/core/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.core.os.ParcelCompat#readArray(android.os.Parcel, ClassLoader, Class<T>):
+ Method androidx.core.os.ParcelCompat.readArray has changed return type from T[] to Object[]
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index ba0259a..6ba0ed7e 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1161,6 +1161,9 @@
public final class IntentCompat {
method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
+ method public static android.os.Parcelable![]? getParcelableArrayExtra(android.content.Intent, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayListExtra(android.content.Intent, String?, Class<? extends T>);
+ method public static <T> T? getParcelableExtra(android.content.Intent, String?, Class<T!>);
method public static android.content.Intent makeMainSelectorActivity(String, String);
field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
@@ -2108,6 +2111,13 @@
method @Deprecated @ChecksSdkIntAtLeast(api=31, codename="S") public static boolean isAtLeastS();
}
+ public final class BundleCompat {
+ method public static <T> T? getParcelable(android.os.Bundle, String?, Class<T!>);
+ method public static android.os.Parcelable![]? getParcelableArray(android.os.Bundle, String?, Class<? extends android.os.Parcelable>);
+ method public static <T> java.util.ArrayList<T!>? getParcelableArrayList(android.os.Bundle, String?, Class<? extends T>);
+ method public static <T> android.util.SparseArray<T!>? getSparseParcelableArray(android.os.Bundle, String?, Class<? extends T>);
+ }
+
public final class CancellationSignal {
ctor public CancellationSignal();
method public void cancel();
@@ -2170,14 +2180,15 @@
}
public final class ParcelCompat {
- method public static <T> T![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> Object![]? readArray(android.os.Parcel, ClassLoader?, Class<T!>);
method public static <T> java.util.ArrayList<T!>? readArrayList(android.os.Parcel, ClassLoader?, Class<? extends T>);
method public static boolean readBoolean(android.os.Parcel);
method public static <K, V> java.util.HashMap<K!,V!>? readHashMap(android.os.Parcel, ClassLoader?, Class<? extends K>, Class<? extends V>);
method public static <T> void readList(android.os.Parcel, java.util.List<? super T>, ClassLoader?, Class<T!>);
method public static <K, V> void readMap(android.os.Parcel, java.util.Map<? super K,? super V>, ClassLoader?, Class<K!>, Class<V!>);
method public static <T extends android.os.Parcelable> T? readParcelable(android.os.Parcel, ClassLoader?, Class<T!>);
- method public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method @Deprecated public static <T> T![]? readParcelableArray(android.os.Parcel, ClassLoader?, Class<T!>);
+ method public static <T> android.os.Parcelable![]? readParcelableArrayTyped(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(30) public static <T> android.os.Parcelable.Creator<T!>? readParcelableCreator(android.os.Parcel, ClassLoader?, Class<T!>);
method @RequiresApi(api=android.os.Build.VERSION_CODES.Q) public static <T> java.util.List<T!> readParcelableList(android.os.Parcel, java.util.List<T!>, ClassLoader?, Class<T!>);
method public static <T extends java.io.Serializable> T? readSerializable(android.os.Parcel, ClassLoader?, Class<T!>);
diff --git a/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
index 2f43fb4..ec062a6 100644
--- a/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
@@ -30,6 +30,9 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -38,18 +41,26 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.core.os.BuildCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
+import com.google.common.collect.Lists;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Objects;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -188,4 +199,117 @@
() -> IntentCompat.createManageUnusedAppRestrictionsIntent(
mContext, PACKAGE_NAME));
}
+
+ @Test
+ public void getParcelableExtra() {
+ Intent intent = new Intent();
+ Signature signature = new Signature("");
+ intent.putExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertEquals(Signature.class, Objects.requireNonNull(
+ IntentCompat.getParcelableExtra(intent, "extra", Signature.class))
+ .getClass());
+ }
+
+ @Test
+ public void getParcelableExtra_returnsNullOnClassMismatch() {
+ Intent intent = new Intent();
+ Signature signature = new Signature("");
+ intent.putExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertNull(IntentCompat.getParcelableExtra(intent, "extra", Intent.class));
+ }
+
+ @Test
+ public void getParcelableArrayExtra_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Intent intent = new Intent();
+ Signature[] signature = new Signature[] { new Signature("") };
+ intent.putExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertEquals(Signature[].class, Objects.requireNonNull(
+ IntentCompat.getParcelableArrayExtra(intent, "extra",
+ Signature.class)).getClass());
+ }
+
+ @Test
+ public void getParcelableArrayExtra_returnsNullOnClassMismatch_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Intent intent = new Intent();
+ Signature[] signature = new Signature[] { new Signature("") };
+ intent.putExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertNull(IntentCompat.getParcelableArrayExtra(intent, "extra", Intent.class));
+ }
+
+ @Test
+ public void getParcelableArrayExtra_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Intent intent = new Intent();
+ Signature[] signature = new Signature[] { new Signature("") };
+ intent.putExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertEquals(Parcelable[].class, Objects.requireNonNull(
+ IntentCompat.getParcelableArrayExtra(intent, "extra",
+ Signature.class)).getClass());
+
+ assertNotEquals(Signature[].class, Objects.requireNonNull(
+ IntentCompat.getParcelableArrayExtra(intent, "extra",
+ Signature.class)).getClass());
+
+ // We do not check clazz Pre-U
+ assertEquals(Parcelable[].class, Objects.requireNonNull(
+ IntentCompat.getParcelableArrayExtra(intent, "extra",
+ Intent.class)).getClass());
+ }
+
+ @Test
+ public void getParcelableArrayListExtra() {
+ Intent intent = new Intent();
+ ArrayList<Signature> signature = Lists.newArrayList(new Signature(""));
+ intent.putParcelableArrayListExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertEquals(Signature.class, Objects.requireNonNull(
+ IntentCompat.getParcelableArrayListExtra(intent, "extra",
+ Signature.class)).get(0).getClass());
+ }
+
+ @Test
+ public void getParcelableArrayListExtra_returnsNullOnClassMismatch_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Intent intent = new Intent();
+ ArrayList<Signature> signature = Lists.newArrayList(new Signature(""));
+ intent.putParcelableArrayListExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ assertNull(IntentCompat.getParcelableArrayListExtra(intent, "extra", Intent.class));
+ }
+
+ @Test
+ public void getParcelableArrayListExtra_noTypeCheck_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Intent intent = new Intent();
+ ArrayList<Signature> signature = Lists.newArrayList(new Signature(""));
+ intent.putParcelableArrayListExtra("extra", signature);
+ parcelAndUnparcel(intent);
+
+ Object extra = Objects.requireNonNull(
+ IntentCompat.getParcelableArrayListExtra(intent, "extra",
+ Intent.class)).get(0);
+ assertEquals(Signature.class, extra.getClass());
+ }
+
+ private void parcelAndUnparcel(Intent intent) {
+ Parcel p = Parcel.obtain();
+ intent.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ intent.readFromParcel(p);
+ p.recycle();
+ }
}
diff --git a/core/core/src/androidTest/java/androidx/core/os/BundleCompatTest.java b/core/core/src/androidTest/java/androidx/core/os/BundleCompatTest.java
new file mode 100644
index 0000000..d8cd469
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/os/BundleCompatTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 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 androidx.core.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.content.pm.Signature;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BundleCompatTest {
+
+ @Test
+ public void getParcelable() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("parcelable", new Intent());
+ parcelAndUnparcel(bundle);
+
+ assertEquals(Intent.class, Objects.requireNonNull(
+ BundleCompat.getParcelable(bundle, "parcelable", Intent.class)).getClass());
+ }
+
+ @Test
+ public void getParcelable_returnsNullOnClassMismatch() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("parcelable", new Intent());
+ parcelAndUnparcel(bundle);
+
+ assertNull(BundleCompat.getParcelable(bundle, "parcelable", Signature.class));
+ }
+
+ @Test
+ public void getParcelableArray_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArray("array", new Intent[] { new Intent() });
+ parcelAndUnparcel(bundle);
+
+ assertEquals(Intent[].class, Objects.requireNonNull(
+ BundleCompat.getParcelableArray(bundle, "array", Intent.class)).getClass());
+ }
+
+ @Test
+ public void getParcelableArray_returnsNullOnClassMismatch_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArray("array", new Intent[] { new Intent() });
+ parcelAndUnparcel(bundle);
+
+ assertNull(BundleCompat.getParcelableArray(bundle, "array", Signature.class));
+ }
+
+ @Test
+ public void getParcelableArray_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArray("array", new Intent[] { new Intent() });
+ parcelAndUnparcel(bundle);
+
+ assertEquals(Parcelable[].class, Objects.requireNonNull(
+ BundleCompat.getParcelableArray(bundle, "array", Intent.class)).getClass());
+
+ assertNotEquals(Intent[].class, Objects.requireNonNull(
+ BundleCompat.getParcelableArray(bundle, "array", Intent.class)).getClass());
+
+ // We do not check clazz Pre-U
+ assertEquals(Parcelable[].class, Objects.requireNonNull(
+ BundleCompat.getParcelableArray(bundle, "array", Signature.class)).getClass());
+ }
+
+ @Test
+ public void getParcelableArrayList() {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList("array", Lists.newArrayList(new Intent()));
+ parcelAndUnparcel(bundle);
+
+ assertEquals(Intent.class, Objects.requireNonNull(
+ BundleCompat.getParcelableArrayList(bundle, "array", Intent.class))
+ .get(0).getClass());
+ }
+
+ @Test
+ public void getParcelableArrayList_returnsNullOnClassMismatch_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList("array", Lists.newArrayList(new Intent()));
+ parcelAndUnparcel(bundle);
+
+ assertNull(BundleCompat.getParcelableArrayList(bundle, "array", Signature.class));
+ }
+
+ @Test
+ public void getParcelableArrayList_noTypeCheck_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList("array", Lists.newArrayList(new Intent()));
+ parcelAndUnparcel(bundle);
+
+ Object intent = Objects.requireNonNull(
+ BundleCompat.getParcelableArrayList(bundle, "array", Signature.class))
+ .get(0);
+ assertEquals(Intent.class, intent.getClass());
+ }
+
+ @Test
+ public void getSparseParcelableArray() {
+ Bundle bundle = new Bundle();
+ SparseArray<Intent> array = new SparseArray<>();
+ array.put(0, new Intent());
+ bundle.putSparseParcelableArray("array", array);
+ parcelAndUnparcel(bundle);
+
+ assertEquals(Intent.class, Objects.requireNonNull(
+ BundleCompat.getSparseParcelableArray(bundle, "array", Intent.class))
+ .get(0).getClass());
+ }
+
+ @Test
+ @SdkSuppress(codeName = "UpsideDownCake")
+ public void getSparseParcelableArray_returnsNullOnClassMismatch_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ SparseArray<Intent> array = new SparseArray<>();
+ array.put(0, new Intent());
+ bundle.putSparseParcelableArray("array", array);
+ parcelAndUnparcel(bundle);
+
+ assertNull(BundleCompat.getSparseParcelableArray(bundle, "array", Signature.class));
+ }
+
+ @Test
+ public void getSparseParcelableArray_noTypeCheck_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Bundle bundle = new Bundle();
+ SparseArray<Intent> array = new SparseArray<>();
+ array.put(0, new Intent());
+ bundle.putSparseParcelableArray("array", array);
+ parcelAndUnparcel(bundle);
+
+ Object intent = Objects.requireNonNull(
+ BundleCompat.getSparseParcelableArray(bundle, "array", Signature.class))
+ .get(0);
+ assertEquals(Intent.class, intent.getClass());
+ }
+
+ private void parcelAndUnparcel(Bundle bundle) {
+ Parcel p = Parcel.obtain();
+ bundle.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ bundle.readFromParcel(p);
+ }
+}
diff --git a/core/core/src/androidTest/java/androidx/core/os/ParcelCompatTest.java b/core/core/src/androidTest/java/androidx/core/os/ParcelCompatTest.java
index 873a9c2..de31f9f 100644
--- a/core/core/src/androidTest/java/androidx/core/os/ParcelCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/os/ParcelCompatTest.java
@@ -16,13 +16,18 @@
package androidx.core.os;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import android.content.Intent;
import android.content.pm.Signature;
import android.graphics.Rect;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,9 +42,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.Objects;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -56,7 +59,7 @@
}
@Test
- public void readParcelable2Arg() {
+ public void readParcelable() {
Rect r = new Rect(0, 0, 10, 10);
Parcel p = Parcel.obtain();
p.writeParcelable(r, 0);
@@ -64,10 +67,14 @@
p.setDataPosition(0);
Rect r2 = ParcelCompat.readParcelable(p, Rect.class.getClassLoader(), Rect.class);
assertEquals(r, r2);
+
+ p.setDataPosition(0);
+ assertThrows(BadParcelableException.class, () -> ParcelCompat.readParcelable(p,
+ Rect.class.getClassLoader(), Intent.class));
}
@Test
- public void readArrayInT() {
+ public void readArray() {
Parcel p = Parcel.obtain();
Signature[] s = {new Signature("1234"),
@@ -78,7 +85,7 @@
p.setDataPosition(0);
Object[] objects = ParcelCompat.readArray(p, Signature.class.getClassLoader(),
Signature.class);
- assertTrue(Arrays.equals(s, objects));
+ assertArrayEquals(s, objects);
p.setDataPosition(0);
p.recycle();
@@ -86,7 +93,7 @@
@RequiresApi(api = Build.VERSION_CODES.S)
@Test
- public void readSparseArrayInT() {
+ public void readSparseArray() {
Parcel p = Parcel.obtain();
SparseArray<Signature> s = new SparseArray<>();
@@ -101,18 +108,17 @@
assertEquals(s.size(), s1.size());
for (int index = 0; index < s.size(); index++) {
int key = s.keyAt(index);
- assertTrue(Objects.equals(s.valueAt(index), s1.get(key)));
+ assertEquals(s.valueAt(index), s1.get(key));
}
p.recycle();
}
@Test
- @SuppressWarnings("unchecked")
- public void readListInT() {
+ public void readList() {
Parcel p = Parcel.obtain();
- ArrayList<Signature> s = new ArrayList();
- ArrayList<Signature> s2 = new ArrayList();
+ ArrayList<Signature> s = new ArrayList<>();
+ ArrayList<Signature> s2 = new ArrayList<>();
s.add(new Signature("1234567890abcdef"));
s.add(new Signature("abcdef1234567890"));
@@ -127,7 +133,7 @@
}
@Test
- public void readArrayListInT() {
+ public void readArrayList() {
Parcel p = Parcel.obtain();
ArrayList<Signature> s = new ArrayList<>();
@@ -145,7 +151,7 @@
}
@Test
- public void readMapInT() {
+ public void readMap() {
Parcel p = Parcel.obtain();
ClassLoader loader = getClass().getClassLoader();
HashMap<String, Signature> map = new HashMap<>();
@@ -163,7 +169,7 @@
}
@Test
- public void readHashMapInT() {
+ public void readHashMap() {
Parcel p = Parcel.obtain();
ClassLoader loader = getClass().getClassLoader();
HashMap<String, Signature> map = new HashMap<>();
@@ -181,7 +187,7 @@
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
@Test
- public void readParcelableCreatorInT() {
+ public void readParcelableCreator() {
final String signatureString = "1234567890abcdef";
Signature s = new Signature(signatureString);
@@ -196,7 +202,7 @@
}
@Test
- public void readParcelableArrayInT() {
+ public void readParcelableArray() {
Parcel p = Parcel.obtain();
Signature[] s = {new Signature("1234"),
null,
@@ -206,13 +212,71 @@
p.setDataPosition(0);
Parcelable[] s1 = ParcelCompat.readParcelableArray(p, Signature.class.getClassLoader(),
Signature.class);
- assertTrue(Arrays.equals(s, s1));
+ assertArrayEquals(s, s1);
+ assertEquals(Signature[].class, s1.getClass());
+
+ p.setDataPosition(0);
+ Parcelable[] s2 = ParcelCompat.readParcelableArray(p, Parcelable.class.getClassLoader(),
+ Parcelable.class);
+ assertArrayEquals(s, s2);
+ assertEquals(Parcelable[].class, s2.getClass());
+
+ p.setDataPosition(0);
+ assertThrows(BadParcelableException.class, () -> ParcelCompat.readParcelableArray(p,
+ Signature.class.getClassLoader(), Intent.class));
+
+ p.recycle();
+ }
+
+ @Test
+ public void readParcelableArrayTyped_postU() {
+ if (!BuildCompat.isAtLeastU()) return;
+ Parcel p = Parcel.obtain();
+ Signature[] s = {new Signature("1234"),
+ null,
+ new Signature("abcd")
+ };
+ p.writeParcelableArray(s, 0);
+ p.setDataPosition(0);
+ Parcelable[] s1 = ParcelCompat.readParcelableArrayTyped(p, Signature.class.getClassLoader(),
+ Signature.class);
+ assertArrayEquals(s, s1);
+ assertEquals(Signature[].class, s1.getClass());
+
+ p.setDataPosition(0);
+ assertThrows(BadParcelableException.class, () -> ParcelCompat.readParcelableArrayTyped(p,
+ Signature.class.getClassLoader(), Intent.class));
+
+ p.recycle();
+ }
+
+ @Test
+ public void readParcelableArrayTyped_preU() {
+ if (BuildCompat.isAtLeastU()) return;
+ Parcel p = Parcel.obtain();
+ Signature[] s = {new Signature("1234"),
+ null,
+ new Signature("abcd")
+ };
+ p.writeParcelableArray(s, 0);
+ p.setDataPosition(0);
+ Parcelable[] s1 = ParcelCompat.readParcelableArrayTyped(p, Signature.class.getClassLoader(),
+ Signature.class);
+ assertArrayEquals(s, s1);
+ assertNotEquals(Signature[].class, s1.getClass());
+
+ // Type not checked pre-U
+ p.setDataPosition(0);
+ s1 = ParcelCompat.readParcelableArrayTyped(p, Signature.class.getClassLoader(),
+ Intent.class);
+ assertArrayEquals(s, s1);
+
p.recycle();
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
- public void readParcelableListInT() {
+ public void readParcelableList() {
final Parcel p = Parcel.obtain();
ArrayList<Signature> list = new ArrayList<>();
ArrayList<Signature> list1 = new ArrayList<>();
diff --git a/core/core/src/main/java/androidx/core/content/IntentCompat.java b/core/core/src/main/java/androidx/core/content/IntentCompat.java
index ad7e5d57..8647f13 100644
--- a/core/core/src/main/java/androidx/core/content/IntentCompat.java
+++ b/core/core/src/main/java/androidx/core/content/IntentCompat.java
@@ -29,10 +29,16 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
+import android.os.Parcelable;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
+
+import java.util.ArrayList;
/**
* Helper for accessing features in {@link Intent}.
@@ -202,6 +208,104 @@
}
}
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, the object type is checked after deserialization.
+ * </ul>
+ *
+ * @param in The intent to retrieve from.
+ * @param name The name of the desired item.
+ * @param clazz The type of the object expected.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Parcelable value was found.
+ *
+ * @see Intent#putExtra(String, Parcelable)
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation", "unchecked"})
+ public static <T> T getParcelableExtra(@NonNull Intent in, @Nullable String name,
+ @NonNull Class<T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelableExtra(in, name, clazz);
+ } else {
+ T extra = in.getParcelableExtra(name);
+ return clazz.isInstance(extra) ? extra : null;
+ }
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
+ * @param in The intent to retrieve from.
+ * @param name The name of the desired item.
+ * @param clazz The type of the items inside the array. This is only verified when unparceling.
+ *
+ * @return the value of an item previously added with putExtra(),
+ * or null if no Parcelable[] value was found.
+ *
+ * @see Intent#putExtra(String, Parcelable[])
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation"})
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ public static Parcelable[] getParcelableArrayExtra(@NonNull Intent in, @Nullable String name,
+ @NonNull Class<? extends Parcelable> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelableArrayExtra(in, name, clazz);
+ } else {
+ return in.getParcelableArrayExtra(name);
+ }
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
+ * @param in The intent to retrieve from.
+ * @param name The name of the desired item.
+ * @param clazz The type of the items inside the array list. This is only verified when
+ * unparceling.
+ *
+ * @return the value of an item previously added with
+ * putParcelableArrayListExtra(), or null if no
+ * ArrayList<Parcelable> value was found.
+ *
+ * @see Intent#putParcelableArrayListExtra(String, ArrayList)
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation", "unchecked"})
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ public static <T> ArrayList<T> getParcelableArrayListExtra(
+ @NonNull Intent in, @Nullable String name, @NonNull Class<? extends T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelableArrayListExtra(in, name, clazz);
+ } else {
+ return (ArrayList<T>) in.getParcelableArrayListExtra(name);
+ }
+ }
+
@RequiresApi(15)
static class Api15Impl {
private Api15Impl() {
@@ -213,4 +317,29 @@
return Intent.makeMainSelectorActivity(selectorAction, selectorCategory);
}
}
+
+ @RequiresApi(33)
+ static class Api33Impl {
+ private Api33Impl() {
+ // This class is non-instantiable.
+ }
+
+ @DoNotInline
+ static <T> T getParcelableExtra(@NonNull Intent in, @Nullable String name,
+ @NonNull Class<T> clazz) {
+ return in.getParcelableExtra(name, clazz);
+ }
+
+ @DoNotInline
+ static <T> T[] getParcelableArrayExtra(@NonNull Intent in, @Nullable String name,
+ @NonNull Class<T> clazz) {
+ return in.getParcelableArrayExtra(name, clazz);
+ }
+
+ @DoNotInline
+ static <T> ArrayList<T> getParcelableArrayListExtra(@NonNull Intent in,
+ @Nullable String name, @NonNull Class<? extends T> clazz) {
+ return in.getParcelableArrayListExtra(name, clazz);
+ }
+ }
}
diff --git a/core/core/src/main/java/androidx/core/os/BundleCompat.java b/core/core/src/main/java/androidx/core/os/BundleCompat.java
new file mode 100644
index 0000000..15bb312
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/os/BundleCompat.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 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 androidx.core.os;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
+import androidx.annotation.RequiresApi;
+
+import java.util.ArrayList;
+
+/**
+ * Helper for accessing features in {@link Bundle}.
+ */
+public final class BundleCompat {
+ private BundleCompat() {
+ /* Hide constructor */
+ }
+
+ /**
+ * 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 Bundle#setClassLoader(ClassLoader)} with the proper {@link ClassLoader}
+ * first.
+ * Otherwise, this method might throw an exception or return {@code null}.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, the object type is checked after deserialization.
+ * </ul>
+ *
+ *
+ * @param in The bundle to retrieve from.
+ * @param key a String, or {@code null}
+ * @param clazz The type of the object expected
+ * @return a Parcelable value, or {@code null}
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation", "unchecked"})
+ public static <T> T getParcelable(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelable(in, key, clazz);
+ } else {
+ T parcelable = in.getParcelable(key);
+ return clazz.isInstance(parcelable) ? parcelable : null;
+ }
+ }
+
+ /**
+ * 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 Bundle#setClassLoader(ClassLoader)} with the proper {@link ClassLoader}
+ * first.
+ * Otherwise, this method might throw an exception or return {@code null}.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
+ * @param in The bundle to retrieve from.
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array. This is only verified when unparceling.
+ * @return a Parcelable[] value, or {@code null}
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation"})
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ public static Parcelable[] getParcelableArray(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<? extends Parcelable> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelableArray(in, key, clazz);
+ } else {
+ return in.getParcelableArray(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 Bundle#setClassLoader(ClassLoader)} with the proper {@link ClassLoader}
+ * first.
+ * Otherwise, this method might throw an exception or return {@code null}.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the list elements' types.
+ * </ul>
+ *
+ * @param in The bundle to retrieve from.
+ * @param key a String, or {@code null}
+ * @param clazz The type of the items inside the array list. This is only verified when
+ * unparceling.
+ * @return an ArrayList<T> value, or {@code null}
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation", "unchecked"})
+ @SuppressLint({"ConcreteCollection", "NullableCollection"})
+ public static <T> ArrayList<T> getParcelableArrayList(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<? extends T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getParcelableArrayList(in, key, clazz);
+ } else {
+ return (ArrayList<T>) in.getParcelableArrayList(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>
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
+ * @param in The bundle to retrieve from.
+ * @param key a String, or null
+ * @param clazz The type of the items inside the sparse array. This is only verified when
+ * unparceling.
+ * @return a SparseArray of T values, or null
+ */
+ @Nullable
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation", "unchecked"})
+ public static <T> SparseArray<T> getSparseParcelableArray(@NonNull Bundle in,
+ @Nullable String key, @NonNull Class<? extends T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.getSparseParcelableArray(in, key, clazz);
+ } else {
+ return (SparseArray<T>) in.getSparseParcelableArray(key);
+ }
+ }
+
+ @RequiresApi(33)
+ static class Api33Impl {
+ private Api33Impl() {
+ // This class is non-instantiable.
+ }
+
+ @DoNotInline
+ static <T> T getParcelable(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<T> clazz) {
+ return in.getParcelable(key, clazz);
+ }
+
+ @DoNotInline
+ static <T> T[] getParcelableArray(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<T> clazz) {
+ return in.getParcelableArray(key, clazz);
+ }
+
+ @DoNotInline
+ static <T> ArrayList<T> getParcelableArrayList(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<? extends T> clazz) {
+ return in.getParcelableArrayList(key, clazz);
+ }
+
+ @DoNotInline
+ static <T> SparseArray<T> getSparseParcelableArray(@NonNull Bundle in, @Nullable String key,
+ @NonNull Class<? extends T> clazz) {
+ return in.getSparseParcelableArray(key, clazz);
+ }
+ }
+}
diff --git a/core/core/src/main/java/androidx/core/os/ParcelCompat.java b/core/core/src/main/java/androidx/core/os/ParcelCompat.java
index 8fa56eb..7de5a60 100644
--- a/core/core/src/main/java/androidx/core/os/ParcelCompat.java
+++ b/core/core/src/main/java/androidx/core/os/ParcelCompat.java
@@ -17,6 +17,7 @@
package androidx.core.os;
import android.annotation.SuppressLint;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,6 +30,7 @@
import androidx.annotation.RequiresApi;
import java.io.Serializable;
+import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -47,7 +49,7 @@
}
/**
- * Write a boolean value into the parcel at the current {@link Parcel#dataPosition()},
+ * Write a boolean value into the parcel at the current f{@link Parcel#dataPosition()},
* growing {@link Parcel#dataCapacity()} if needed.
*
* <p>Note: This method currently delegates to {@link Parcel#writeInt} with a value of 1 or 0
@@ -61,6 +63,13 @@
* Same as {@link Parcel#readList(List, ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the list elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -69,8 +78,8 @@
@SuppressWarnings("deprecation")
public static <T> void readList(@NonNull Parcel in, @NonNull List<? super T> outVal,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- TiramisuImpl.readList(in, outVal, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ Api33Impl.readList(in, outVal, loader, clazz);
} else {
in.readList(outVal, loader);
}
@@ -80,6 +89,13 @@
* Same as {@link Parcel#readArrayList(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the list elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -90,8 +106,8 @@
@Nullable
public static <T> ArrayList<T> readArrayList(@NonNull Parcel in, @Nullable ClassLoader loader,
@NonNull Class<? extends T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readArrayList(in, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readArrayList(in, loader, clazz);
} else {
return in.readArrayList(loader);
}
@@ -101,6 +117,13 @@
* Same as {@link Parcel#readArray(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -109,12 +132,12 @@
@SuppressWarnings({"deprecation", "unchecked"})
@SuppressLint({"ArrayReturn", "NullableCollection"})
@Nullable
- public static <T> T[] readArray(@NonNull Parcel in, @Nullable ClassLoader loader,
+ public static <T> Object[] readArray(@NonNull Parcel in, @Nullable ClassLoader loader,
@NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readArray(in, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readArray(in, loader, clazz);
} else {
- return (T[]) in.readArray(loader);
+ return in.readArray(loader);
}
}
@@ -122,6 +145,13 @@
* Same as {@link Parcel#readSparseArray(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -130,20 +160,25 @@
@SuppressWarnings("deprecation")
@Nullable
public static <T> SparseArray<T> readSparseArray(@NonNull Parcel in,
- @Nullable ClassLoader loader,
- @NonNull Class<? extends T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readSparseArray(in, loader, clazz);
+ @Nullable ClassLoader loader, @NonNull Class<? extends T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readSparseArray(in, loader, clazz);
} else {
return in.readSparseArray(loader);
}
}
-
/**
* Same as {@link Parcel#readMap(Map, ClassLoader)} but accepts {@code clazzKey} and
* {@code clazzValue} parameter as the types required for each key and value pair.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the map entries' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException If the item to be deserialized is not an
* instance of that class or any of its children class.
*/
@@ -152,8 +187,8 @@
public static <K, V> void readMap(@NonNull Parcel in, @NonNull Map<? super K, ? super V> outVal,
@Nullable ClassLoader loader, @NonNull Class<K> clazzKey,
@NonNull Class<V> clazzValue) {
- if (BuildCompat.isAtLeastT()) {
- TiramisuImpl.readMap(in, outVal, loader, clazzKey, clazzValue);
+ if (BuildCompat.isAtLeastU()) {
+ Api33Impl.readMap(in, outVal, loader, clazzKey, clazzValue);
} else {
in.readMap(outVal, loader);
}
@@ -163,6 +198,13 @@
* Same as {@link Parcel#readHashMap(ClassLoader)} but accepts {@code clazzKey} and
* {@code clazzValue} parameter as the types required for each key and value pair.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the map entries' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException if the item to be deserialized is not an
* instance of that class or any of its children class.
*/
@@ -172,8 +214,8 @@
@Nullable
public static <K, V> HashMap<K, V> readHashMap(@NonNull Parcel in, @Nullable ClassLoader loader,
@NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readHashMap(in, loader, clazzKey, clazzValue);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readHashMap(in, loader, clazzKey, clazzValue);
} else {
return in.readHashMap(loader);
}
@@ -183,6 +225,13 @@
* Same as {@link Parcel#readParcelable(ClassLoader)} but accepts {@code clazz} parameter as
* the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, the object type is checked after deserialization.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -192,10 +241,16 @@
@Nullable
public static <T extends Parcelable> T readParcelable(@NonNull Parcel in,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readParcelable(in, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readParcelable(in, loader, clazz);
} else {
- return in.readParcelable(loader);
+ T parcelable = in.readParcelable(loader);
+ if (!clazz.isInstance(parcelable)) {
+ throw new BadParcelableException("Parcelable " + parcelable.getClass() + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ return parcelable;
}
}
@@ -203,6 +258,13 @@
* Same as {@link Parcel#readParcelableCreator(ClassLoader)} but accepts {@code clazz} parameter
* as the required type.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the creator's type.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there
* there was an error trying to read the {@link Parcelable.Creator}.
@@ -213,8 +275,8 @@
@RequiresApi(30)
public static <T> Parcelable.Creator<T> readParcelableCreator(@NonNull Parcel in,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readParcelableCreator(in, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readParcelableCreator(in, loader, clazz);
} else {
return (Parcelable.Creator<T>) Api30Impl.readParcelableCreator(in, loader);
}
@@ -224,20 +286,76 @@
* Same as {@link Parcel#readParcelableArray(ClassLoader)} but accepts {@code clazz} parameter
* as the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
+ *
+ * @deprecated This method incurs a performance penalty on SDK 33 and below. Use
+ * {@link #readParcelableArrayTyped} instead.
*/
@OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
@SuppressWarnings({"deprecation", "unchecked"})
@SuppressLint({"ArrayReturn", "NullableCollection"})
@Nullable
+ @Deprecated
public static <T> T[] readParcelableArray(@NonNull Parcel in, @Nullable ClassLoader loader,
@NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readParcelableArray(in, loader, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readParcelableArray(in, loader, clazz);
} else {
- return (T[]) in.readParcelableArray(loader);
+ // The array type is always Parcelable[]. Cast to clazz[] for compatibility if needed.
+ Parcelable[] parcelables = in.readParcelableArray(loader);
+ if (clazz.isAssignableFrom(Parcelable.class)) {
+ return (T[]) parcelables;
+ }
+
+ T[] typedParcelables = (T[]) Array.newInstance(clazz, parcelables.length);
+ for (int i = 0; i < parcelables.length; i++) {
+ try {
+ typedParcelables[i] = clazz.cast(parcelables[i]);
+ } catch (ClassCastException e) {
+ throw new BadParcelableException("Parcelable at index " + i + " is not "
+ + "a subclass of required class " + clazz.getName()
+ + " provided in the parameter");
+ }
+ }
+
+ return typedParcelables;
+ }
+ }
+
+ /**
+ * Same as {@link Parcel#readParcelableArray(ClassLoader)} but accepts {@code clazz} parameter
+ * as the type required for each item.
+ *
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the array elements' types.
+ * </ul>
+ *
+ * @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
+ * deserialized is not an instance of that class or any of its children classes or there was
+ * an error trying to instantiate an element.
+ */
+ @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
+ @SuppressWarnings({"deprecation"})
+ @SuppressLint({"ArrayReturn", "NullableCollection"})
+ @Nullable
+ public static <T> Parcelable[] readParcelableArrayTyped(@NonNull Parcel in,
+ @Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+ if (BuildCompat.isAtLeastU()) {
+ return (Parcelable[]) Api33Impl.readParcelableArray(in, loader, clazz);
+ } else {
+ return in.readParcelableArray(loader);
}
}
@@ -245,6 +363,13 @@
* Same as {@link Parcel#readParcelableList(List, ClassLoader)} but accepts {@code clazz}
* parameter as the type required for each item.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>{@link BuildCompat#isAtLeastU() Android U and later}, this method matches platform
+ * behavior.
+ * <li>SDK 33 and below, this method will not check the list elements' types.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children classes or there was
* an error trying to instantiate an element.
@@ -255,8 +380,8 @@
@RequiresApi(api = Build.VERSION_CODES.Q)
public static <T> List<T> readParcelableList(@NonNull Parcel in, @NonNull List<T> list,
@Nullable ClassLoader cl, @NonNull Class<T> clazz) {
- if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readParcelableList(in, list, cl, clazz);
+ if (BuildCompat.isAtLeastU()) {
+ return Api33Impl.readParcelableList(in, list, cl, clazz);
} else {
return Api29Impl.readParcelableList(in, (List) list, cl);
}
@@ -267,6 +392,13 @@
* as the primary classLoader for resolving the Serializable class; and {@code clazz} parameter
* as the required type.
*
+ * Compatibility behavior:
+ * <ul>
+ * <li>SDK 33 and later, this method matches platform
+ * behavior.
+ * <li>SDK 32 and below, this method will not check the item's type.
+ * </ul>
+ *
* @throws android.os.BadParcelableException Throws BadParcelableException if the item to be
* deserialized is not an instance of that class or any of its children class or there there
* was an error deserializing the object.
@@ -277,7 +409,7 @@
public static <T extends Serializable> T readSerializable(@NonNull Parcel in,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
if (BuildCompat.isAtLeastT()) {
- return TiramisuImpl.readSerializable(in, loader, clazz);
+ return Api33Impl.readSerializable(in, loader, clazz);
} else {
return (T) in.readSerializable();
}
@@ -292,7 +424,7 @@
}
@DoNotInline
- static final <T extends Parcelable> List<T> readParcelableList(@NonNull Parcel in,
+ static <T extends Parcelable> List<T> readParcelableList(@NonNull Parcel in,
@NonNull List<T> list, @Nullable ClassLoader cl) {
return in.readParcelableList(list, cl);
}
@@ -305,15 +437,15 @@
}
@DoNotInline
- static final Parcelable.Creator<?> readParcelableCreator(@NonNull Parcel in,
+ static Parcelable.Creator<?> readParcelableCreator(@NonNull Parcel in,
@Nullable ClassLoader loader) {
return in.readParcelableCreator(loader);
}
}
@RequiresApi(33)
- static class TiramisuImpl {
- private TiramisuImpl() {
+ static class Api33Impl {
+ private Api33Impl() {
// This class is non-instantiable.
}
@@ -330,7 +462,7 @@
}
@DoNotInline
- public static <T> Parcelable.Creator<T> readParcelableCreator(Parcel in, ClassLoader loader,
+ static <T> Parcelable.Creator<T> readParcelableCreator(Parcel in, ClassLoader loader,
Class<T> clazz) {
return in.readParcelableCreator(loader, clazz);
}
@@ -348,36 +480,36 @@
}
@DoNotInline
- public static <T> void readList(@NonNull Parcel in, @NonNull List<? super T> outVal,
+ static <T> void readList(@NonNull Parcel in, @NonNull List<? super T> outVal,
@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
in.readList(outVal, loader, clazz);
}
@DoNotInline
- public static <T> ArrayList<T> readArrayList(Parcel in, ClassLoader loader,
+ static <T> ArrayList<T> readArrayList(Parcel in, ClassLoader loader,
Class<? extends T> clazz) {
return in.readArrayList(loader, clazz);
}
@DoNotInline
- public static <T> T[] readArray(Parcel in, ClassLoader loader, Class<T> clazz) {
+ static <T> T[] readArray(Parcel in, ClassLoader loader, Class<T> clazz) {
return in.readArray(loader, clazz);
}
@DoNotInline
- public static <T> SparseArray<T> readSparseArray(Parcel in, ClassLoader loader,
+ static <T> SparseArray<T> readSparseArray(Parcel in, ClassLoader loader,
Class<? extends T> clazz) {
return in.readSparseArray(loader, clazz);
}
@DoNotInline
- public static <K, V> void readMap(Parcel in, Map<? super K, ? super V> outVal,
+ static <K, V> void readMap(Parcel in, Map<? super K, ? super V> outVal,
ClassLoader loader, Class<K> clazzKey, Class<V> clazzValue) {
in.readMap(outVal, loader, clazzKey, clazzValue);
}
@DoNotInline
- public static <V, K> HashMap<K, V> readHashMap(Parcel in, ClassLoader loader,
+ static <V, K> HashMap<K, V> readHashMap(Parcel in, ClassLoader loader,
Class<? extends K> clazzKey, Class<? extends V> clazzValue) {
return in.readHashMap(loader, clazzKey, clazzValue);
}