Merge changes from topic "fix-builtin-serializers" into androidx-main

* changes:
  Make JavaSerializableSerializer and ParcelableSerializer abstract
  Add platform-specific encoding and decoding to SavedState codec tests
  Remove generic from CharSequenceSerializer
diff --git a/savedstate/savedstate-samples/src/main/java/androidx/savedstate/SavedStateCodecSamples.kt b/savedstate/savedstate-samples/src/main/java/androidx/savedstate/SavedStateCodecSamples.kt
index 8fd4b86..616baa6 100644
--- a/savedstate/savedstate-samples/src/main/java/androidx/savedstate/SavedStateCodecSamples.kt
+++ b/savedstate/savedstate-samples/src/main/java/androidx/savedstate/SavedStateCodecSamples.kt
@@ -18,6 +18,8 @@
 
 package androidx.savedstate
 
+import android.os.Parcel
+import android.os.Parcelable
 import androidx.annotation.Sampled
 import androidx.savedstate.serialization.decodeFromSavedState
 import androidx.savedstate.serialization.encodeToSavedState
@@ -96,7 +98,6 @@
     val uuid = decodeFromSavedState(UUIDSerializer(), uuidSavedState)
 }
 
-@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") // The lint warning does not show up for external users.
 @Sampled
 fun savedStateSerializer() {
     @Serializable
@@ -125,20 +126,36 @@
     )
 }
 
+private class MyJavaSerializable : java.io.Serializable
+
+private class MyJavaSerializableSerializer : JavaSerializableSerializer<MyJavaSerializable>()
+
 @Sampled
 fun serializableSerializer() {
     @Serializable
     data class MyModel(
-        @Serializable(with = JavaSerializableSerializer::class)
-        val serializable: java.io.Serializable
+        @Serializable(with = MyJavaSerializableSerializer::class)
+        val serializable: MyJavaSerializable
     )
 }
 
+private class MyParcelable : Parcelable {
+    override fun describeContents(): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        TODO("Not yet implemented")
+    }
+}
+
+private class MyParcelableSerializer : ParcelableSerializer<MyParcelable>()
+
 @Sampled
 fun parcelableSerializer() {
     @Serializable
     data class MyModel(
-        @Serializable(with = ParcelableSerializer::class) val parcelable: android.os.Parcelable
+        @Serializable(with = MyParcelableSerializer::class) val parcelable: MyParcelable
     )
 }
 
@@ -172,13 +189,11 @@
 fun charSequenceListSerializer() {
     @Serializable
     class MyModel(
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
         @Serializable(with = CharSequenceListSerializer::class)
         val charSequenceList: List<CharSequence>
     )
 }
 
-@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
 @Sampled
 fun parcelableListSerializer() {
     @Serializable
diff --git a/savedstate/savedstate/api/current.txt b/savedstate/savedstate/api/current.txt
index a3e7083..8a63bfe 100644
--- a/savedstate/savedstate/api/current.txt
+++ b/savedstate/savedstate/api/current.txt
@@ -195,12 +195,12 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class CharSequenceSerializer<T extends java.lang.CharSequence> implements kotlinx.serialization.KSerializer<T> {
+  public final class CharSequenceSerializer implements kotlinx.serialization.KSerializer<java.lang.CharSequence> {
     ctor public CharSequenceSerializer();
-    method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
-    method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
-    method public final void serialize(kotlinx.serialization.encoding.Encoder encoder, T value);
-    property public final kotlinx.serialization.descriptors.SerialDescriptor descriptor;
+    method public CharSequence deserialize(kotlinx.serialization.encoding.Decoder decoder);
+    method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
+    method public void serialize(kotlinx.serialization.encoding.Encoder encoder, CharSequence value);
+    property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
   public final class IBinderSerializer implements kotlinx.serialization.KSerializer<android.os.IBinder> {
@@ -211,7 +211,7 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class JavaSerializableSerializer<T extends java.io.Serializable> implements kotlinx.serialization.KSerializer<T> {
+  public abstract class JavaSerializableSerializer<T extends java.io.Serializable> implements kotlinx.serialization.KSerializer<T> {
     ctor public JavaSerializableSerializer();
     method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
     method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
@@ -235,7 +235,7 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class ParcelableSerializer<T extends android.os.Parcelable> implements kotlinx.serialization.KSerializer<T> {
+  public abstract class ParcelableSerializer<T extends android.os.Parcelable> implements kotlinx.serialization.KSerializer<T> {
     ctor public ParcelableSerializer();
     method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
     method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
diff --git a/savedstate/savedstate/api/restricted_current.txt b/savedstate/savedstate/api/restricted_current.txt
index eb7a636..0f65840 100644
--- a/savedstate/savedstate/api/restricted_current.txt
+++ b/savedstate/savedstate/api/restricted_current.txt
@@ -220,12 +220,12 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class CharSequenceSerializer<T extends java.lang.CharSequence> implements kotlinx.serialization.KSerializer<T> {
+  public final class CharSequenceSerializer implements kotlinx.serialization.KSerializer<java.lang.CharSequence> {
     ctor public CharSequenceSerializer();
-    method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
-    method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
-    method public final void serialize(kotlinx.serialization.encoding.Encoder encoder, T value);
-    property public final kotlinx.serialization.descriptors.SerialDescriptor descriptor;
+    method public CharSequence deserialize(kotlinx.serialization.encoding.Decoder decoder);
+    method public kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
+    method public void serialize(kotlinx.serialization.encoding.Encoder encoder, CharSequence value);
+    property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
   public final class IBinderSerializer implements kotlinx.serialization.KSerializer<android.os.IBinder> {
@@ -236,7 +236,7 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class JavaSerializableSerializer<T extends java.io.Serializable> implements kotlinx.serialization.KSerializer<T> {
+  public abstract class JavaSerializableSerializer<T extends java.io.Serializable> implements kotlinx.serialization.KSerializer<T> {
     ctor public JavaSerializableSerializer();
     method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
     method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
@@ -260,7 +260,7 @@
     property public kotlinx.serialization.descriptors.SerialDescriptor descriptor;
   }
 
-  public class ParcelableSerializer<T extends android.os.Parcelable> implements kotlinx.serialization.KSerializer<T> {
+  public abstract class ParcelableSerializer<T extends android.os.Parcelable> implements kotlinx.serialization.KSerializer<T> {
     ctor public ParcelableSerializer();
     method public final T deserialize(kotlinx.serialization.encoding.Decoder decoder);
     method public final kotlinx.serialization.descriptors.SerialDescriptor getDescriptor();
diff --git a/savedstate/savedstate/src/androidInstrumentedTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt b/savedstate/savedstate/src/androidInstrumentedTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt
new file mode 100644
index 0000000..edf79cc
--- /dev/null
+++ b/savedstate/savedstate/src/androidInstrumentedTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 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.savedstate
+
+import android.os.Parcel
+
+actual fun platformEncodeDecode(savedState: SavedState): SavedState {
+    val parcel =
+        Parcel.obtain().apply {
+            savedState.writeToParcel(this, 0)
+            setDataPosition(0)
+        }
+    return SavedState.CREATOR.createFromParcel(parcel)
+}
diff --git a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
index f0dacc5..071f4c1 100644
--- a/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
+++ b/savedstate/savedstate/src/androidMain/kotlin/androidx/savedstate/serialization/serializers/BuiltInSerializer.android.kt
@@ -107,10 +107,10 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-public open class CharSequenceSerializer<T : CharSequence> : KSerializer<T> {
-    final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CharSequence")
+public class CharSequenceSerializer : KSerializer<CharSequence> {
+    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CharSequence")
 
-    final override fun serialize(encoder: Encoder, value: T) {
+    override fun serialize(encoder: Encoder, value: CharSequence) {
         require(encoder is SavedStateEncoder) {
             encoderErrorMessage(descriptor.serialName, encoder)
         }
@@ -118,17 +118,18 @@
     }
 
     @Suppress("UNCHECKED_CAST")
-    final override fun deserialize(decoder: Decoder): T {
+    override fun deserialize(decoder: Decoder): CharSequence {
         require(decoder is SavedStateDecoder) {
             decoderErrorMessage(descriptor.serialName, decoder)
         }
-        return decoder.run { savedState.read { getCharSequence(key) as T } }
+        return decoder.run { savedState.read { getCharSequence(key) } }
     }
 }
 
 /**
  * A serializer for [java.io.Serializable]. This serializer uses [SavedState]'s API directly to
- * save/load a [java.io.Serializable].
+ * save/load a [java.io.Serializable]. You must extend this serializer for each of your
+ * [java.io.Serializable] subclasses.
  *
  * Note that this serializer should be used with [SavedStateEncoder] or [SavedStateDecoder] only.
  * Using it with other Encoders/Decoders may throw [IllegalArgumentException].
@@ -138,7 +139,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-public open class JavaSerializableSerializer<T : JavaSerializable> : KSerializer<T> {
+public abstract class JavaSerializableSerializer<T : JavaSerializable> : KSerializer<T> {
     final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("JavaSerializable")
 
     final override fun serialize(encoder: Encoder, value: T) {
@@ -159,7 +160,7 @@
 
 /**
  * A serializer for [Parcelable]. This serializer uses [SavedState]'s API directly to save/load a
- * [Parcelable].
+ * [Parcelable]. You must extend this serializer for each of your [Parcelable] subclasses.
  *
  * Note that this serializer should be used with [SavedStateEncoder] or [SavedStateDecoder] only.
  * Using it with other Encoders/Decoders may throw [IllegalArgumentException].
@@ -169,7 +170,7 @@
  * @see androidx.savedstate.serialization.decodeFromSavedState
  */
 @OptIn(ExperimentalSerializationApi::class)
-public open class ParcelableSerializer<T : Parcelable> : KSerializer<T> {
+public abstract class ParcelableSerializer<T : Parcelable> : KSerializer<T> {
     final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Parcelable")
 
     final override fun serialize(encoder: Encoder, value: T) {
diff --git a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecAndroidTest.android.kt b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecAndroidTest.android.kt
index 2c34ddb..743c3e4 100644
--- a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecAndroidTest.android.kt
+++ b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecAndroidTest.android.kt
@@ -26,7 +26,6 @@
 import android.util.SizeF
 import android.util.SparseArray
 import androidx.core.os.bundleOf
-import androidx.core.util.forEach
 import androidx.kruth.assertThat
 import androidx.kruth.assertThrows
 import androidx.savedstate.SavedStateCodecTestUtils.encodeDecode
@@ -59,7 +58,6 @@
 import kotlinx.serialization.encoding.Encoder
 import kotlinx.serialization.encoding.decodeStructure
 import kotlinx.serialization.encoding.encodeStructure
-import kotlinx.serialization.serializer
 
 @ExperimentalSerializationApi
 internal class SavedStateCodecAndroidTest : RobolectricTest() {
@@ -100,18 +98,7 @@
             "SERIALIZER_TYPE_INCOMPATIBLE"
         ) // The lint warning does not show up for external users.
         @Serializable
-        class MyClass(@Serializable(with = SavedStateSerializer::class) val s: Bundle) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as MyClass
-                return s.read { contentDeepEquals(other.s) }
-            }
-
-            override fun hashCode(): Int {
-                return s.read { contentDeepHashCode() }
-            }
-        }
+        class MyClass(@Serializable(with = SavedStateSerializer::class) val s: Bundle)
         MyClass(
                 bundleOf(
                     "i" to 1,
@@ -120,19 +107,24 @@
                     "ss" to bundleOf("s" to "bar")
                 )
             )
-            .encodeDecode {
-                assertThat(size()).isEqualTo(1)
-                getSavedState("s").read {
-                    assertThat(size()).isEqualTo(4)
-                    assertThat(getInt("i")).isEqualTo(1)
-                    assertThat(getString("s")).isEqualTo("foo")
-                    assertThat(getIntArray("a")).isEqualTo(intArrayOf(1, 3, 5))
-                    getSavedState("ss").read {
-                        assertThat(size()).isEqualTo(1)
-                        assertThat(getString("s")).isEqualTo("bar")
+            .encodeDecode(
+                checkDecoded = { decoded, original ->
+                    assertThat(decoded.s.read { contentDeepEquals(original.s) }).isTrue()
+                },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    getSavedState("s").read {
+                        assertThat(size()).isEqualTo(4)
+                        assertThat(getInt("i")).isEqualTo(1)
+                        assertThat(getString("s")).isEqualTo("foo")
+                        assertThat(getIntArray("a")).isEqualTo(intArrayOf(1, 3, 5))
+                        getSavedState("ss").read {
+                            assertThat(size()).isEqualTo(1)
+                            assertThat(getString("s")).isEqualTo("bar")
+                        }
                     }
                 }
-            }
+            )
 
         // Bundle at root.
         val origin = bundleOf("i" to 3, "s" to "foo", "d" to 3.14)
@@ -180,7 +172,8 @@
 
         @Serializable
         data class SerializableContainer(
-            @Serializable(with = JavaSerializableSerializer::class) val value: java.io.Serializable
+            @Serializable(with = CustomJavaSerializableSerializer::class)
+            val value: java.io.Serializable
         )
         val myJavaSerializable = MyJavaSerializable(3, "foo", 3.14)
         SerializableContainer(myJavaSerializable).encodeDecode {
@@ -191,7 +184,7 @@
 
         @Serializable
         data class ParcelableContainer(
-            @Serializable(with = ParcelableSerializer::class) val value: Parcelable
+            @Serializable(with = CustomParcelableSerializer::class) val value: Parcelable
         )
         val myParcelable = MyParcelable(3, "foo", 3.14)
         ParcelableContainer(myParcelable).encodeDecode {
@@ -257,19 +250,9 @@
             error("VERSION.SDK_INT < Q")
         }
 
+        @Suppress("ArrayInDataClass")
         @Serializable
-        data class CharSequenceArrayContainer(val value: Array<out CharSequence>) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as CharSequenceArrayContainer
-                return value.contentEquals(other.value)
-            }
-
-            override fun hashCode(): Int {
-                return value.contentHashCode()
-            }
-        }
+        data class CharSequenceArrayContainer(val value: Array<out CharSequence>)
         assertThrows<SerializationException> {
                 CharSequenceArrayContainer(arrayOf("foo", "bar")).encodeDecode {}
             }
@@ -281,20 +264,10 @@
 
     @Test
     fun concreteTypesInsteadOfInterfaceTypes() {
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
-        @Serializable
-        data class CharSequenceContainer(
-            @Serializable(with = CharSequenceSerializer::class) val value: String
-        )
-        CharSequenceContainer("foo").encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getCharSequence("value")).isEqualTo("foo")
-        }
-
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
         @Serializable
         data class SerializableContainer(
-            @Serializable(with = JavaSerializableSerializer::class) val value: MyJavaSerializable
+            @Serializable(with = MyJavaSerializableAsJavaSerializableSerializer::class)
+            val value: MyJavaSerializable
         )
         val myJavaSerializable = MyJavaSerializable(3, "foo", 3.14)
         SerializableContainer(myJavaSerializable).encodeDecode {
@@ -303,10 +276,9 @@
                 .isEqualTo(myJavaSerializable)
         }
 
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
         @Serializable
         data class ParcelableContainer(
-            @Serializable(with = ParcelableSerializer::class) val value: MyParcelable
+            @Serializable(with = MyParcelableAsParcelableSerializer::class) val value: MyParcelable
         )
         val myParcelable = MyParcelable(3, "foo", 3.14)
         ParcelableContainer(myParcelable).encodeDecode {
@@ -315,10 +287,11 @@
         }
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
             @Serializable
             data class IBinderContainer(
-                @Serializable(with = IBinderSerializer::class) val value: Binder
+                @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+                @Serializable(with = IBinderSerializer::class)
+                val value: Binder
             )
             val binder = Binder("foo")
             IBinderContainer(binder).encodeDecode {
@@ -333,47 +306,36 @@
     @Test
     fun collectionTypes() {
         @Serializable
+        @Suppress("ArrayInDataClass")
         data class CharSequenceArrayContainer(
             @Serializable(with = CharSequenceArraySerializer::class)
             val value: Array<out CharSequence>
-        ) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as CharSequenceArrayContainer
-                return value.contentEquals(other.value)
-            }
-
-            override fun hashCode(): Int {
-                return value.contentHashCode()
-            }
-        }
-        val myCharSequenceArray = arrayOf("foo", "bar")
-        CharSequenceArrayContainer(myCharSequenceArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getCharSequenceArray("value")).isEqualTo(myCharSequenceArray)
-        }
+        )
+        val myCharSequenceArray = arrayOf(StringBuilder("foo"), StringBuilder("bar"))
+        CharSequenceArrayContainer(myCharSequenceArray)
+            .encodeDecode(
+                checkDecoded = { decoded, original -> decoded.value.contentEquals(original.value) },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    assertThat(getCharSequenceArray("value")).isEqualTo(myCharSequenceArray)
+                }
+            )
 
         @Serializable
+        @Suppress("ArrayInDataClass")
         data class ParcelableArrayContainer(
             @Serializable(with = ParcelableArraySerializer::class) val value: Array<out Parcelable>
-        ) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as ParcelableArrayContainer
-                return value.contentEquals(other.value)
-            }
-
-            override fun hashCode(): Int {
-                return value.contentHashCode()
-            }
-        }
+        )
         val myParcelableArray = arrayOf(MyParcelable(3, "foo", 3.14), MyParcelable(4, "bar", 1.73))
-        ParcelableArrayContainer(myParcelableArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getParcelableArray<MyParcelable>("value")).isEqualTo(myParcelableArray)
-        }
+        ParcelableArrayContainer(myParcelableArray)
+            .encodeDecode(
+                checkDecoded = { decoded, original -> decoded.value.contentEquals(original.value) },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    assertThat(getParcelableArray<MyParcelable>("value"))
+                        .isEqualTo(myParcelableArray)
+                }
+            )
 
         @Serializable
         data class CharSequenceListContainer(
@@ -406,76 +368,96 @@
                 append(1, MyParcelable(3, "foo", 3.14))
                 append(3, MyParcelable(4, "bar", 1.73))
             }
-        SparseParcelableArrayContainer(mySparseParcelableArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getSparseParcelableArray<Parcelable>("value"))
-                .isEqualTo(mySparseParcelableArray)
-        }
+        SparseParcelableArrayContainer(mySparseParcelableArray)
+            .encodeDecode(
+                checkDecoded = { decoded, original ->
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                        decoded.value.contentEquals(original.value)
+                    } else {
+                        error("VERSION.SDK_INT < S")
+                    }
+                },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    assertThat(getSparseParcelableArray<Parcelable>("value"))
+                        .isEqualTo(mySparseParcelableArray)
+                }
+            )
     }
 
     @Test
     fun collectionTypesWithConcreteElement() {
+        @Suppress("ArrayInDataClass")
         @Serializable
         data class CharSequenceArrayContainer(
-            @Serializable(with = CharSequenceArraySerializer::class) val value: Array<String>
-        ) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as CharSequenceArrayContainer
-                return value.contentEquals(other.value)
-            }
-
-            override fun hashCode(): Int {
-                return value.contentHashCode()
+            @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+            @Serializable(with = CharSequenceArraySerializer::class)
+            val value: Array<@Serializable(with = CharSequenceSerializer::class) StringBuilder>
+        )
+        val myCharSequenceArray = arrayOf<StringBuilder>(StringBuilder("foo"), StringBuilder("bar"))
+        // `Bundle.getCharSequenceArray()` returns a `CharSequence[]` and the actual element type
+        // is not being retained after parcel/unparcel so the plugin-generated serializer will
+        // get `ClassCastException` when trying to cast it back to `Array<StringBuilder>`.
+        assertThrows(ClassCastException::class) {
+            CharSequenceArrayContainer(myCharSequenceArray).encodeDecode {
+                assertThat(size()).isEqualTo(1)
+                assertThat(getCharSequenceArray("value")).isEqualTo(myCharSequenceArray)
             }
         }
-        val myCharSequenceArray = arrayOf("foo", "bar")
-        CharSequenceArrayContainer(myCharSequenceArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getCharSequenceArray("value")).isEqualTo(myCharSequenceArray)
-        }
 
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+        @Suppress("ArrayInDataClass")
         @Serializable
         data class ParcelableArrayContainer(
             @Serializable(with = ParcelableArraySerializer::class)
             // Here the serializer for the element is actually not used, but leaving it out leads
             // to SERIALIZER_NOT_FOUND compile error.
-            val value: Array<@Serializable(with = ParcelableSerializer::class) MyParcelable>
-        ) {
-            override fun equals(other: Any?): Boolean {
-                if (this === other) return true
-                if (javaClass != other?.javaClass) return false
-                other as ParcelableArrayContainer
-                return value.contentEquals(other.value)
-            }
-
-            override fun hashCode(): Int {
-                return value.contentHashCode()
-            }
-        }
+            val value:
+                Array<@Serializable(with = MyParcelableAsParcelableSerializer::class) MyParcelable>
+        )
         val myParcelableArray = arrayOf(MyParcelable(3, "foo", 3.14), MyParcelable(4, "bar", 1.73))
-        ParcelableArrayContainer(myParcelableArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getParcelableArray<MyParcelable>("value")).isEqualTo(myParcelableArray)
+        // Even though `Bundle` does retain the actual `Parcelable` type there's no way for us to
+        // specify this `Parcelable` element type for the array, so the restored array is still of
+        // type `Array<Parcelable>` and the plugin-generated serializer will get
+        // `ClassCastException` when trying to cast it back to `Array<MyParcelable>`.
+        assertThrows(ClassCastException::class) {
+            ParcelableArrayContainer(myParcelableArray).encodeDecode {
+                assertThat(size()).isEqualTo(1)
+                assertThat(getParcelableArray<MyParcelable>("value")).isEqualTo(myParcelableArray)
+            }
         }
 
         @Serializable
         data class CharSequenceListContainer(
-            @Serializable(with = CharSequenceListSerializer::class) val value: List<String>
+            @Serializable(with = CharSequenceListSerializer::class)
+            @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+            val value: List<@Serializable(with = CharSequenceSerializer::class) StringBuilder>
         )
-        val myCharSequenceList = arrayListOf("foo", "bar")
-        CharSequenceListContainer(myCharSequenceList).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getCharSequenceList("value")).isEqualTo(myCharSequenceList)
-        }
+        val myCharSequenceList = arrayListOf(StringBuilder("foo"), StringBuilder("bar"))
 
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+        CharSequenceListContainer(myCharSequenceList)
+            .encodeDecode(
+                checkDecoded = { decoded, original ->
+                    assertThat(original.value[0]::class).isEqualTo(StringBuilder::class)
+                    // This is similar to the `CharSequenceArray` case where the element type of the
+                    // restored List after parcel/unparcel is of `String` instead of
+                    // `StringBuilder`. However, since the element type of Lists is erased no
+                    // `CastCastException` is thrown when the plugin-generated serializer tried to
+                    // assign the restored list back to `List<StringBuilder>`.
+                    assertThat(decoded.value[0]::class).isEqualTo(String::class)
+                },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    assertThat(getCharSequenceList("value")).isEqualTo(myCharSequenceList)
+                }
+            )
+
         @Serializable
         data class ParcelableListContainer(
+            // Unlike arrays this works as `List`s can be down-casted, e.g.
+            // a `List<Parcelable>` can be casted to `List<MyParcelable>`.
             @Serializable(with = ParcelableListSerializer::class)
-            val value: List<@Serializable(with = ParcelableSerializer::class) MyParcelable>
+            val value:
+                List<@Serializable(with = MyParcelableAsParcelableSerializer::class) MyParcelable>
         )
         val myParcelableList =
             arrayListOf(MyParcelable(3, "foo", 3.14), MyParcelable(4, "bar", 1.73))
@@ -484,57 +466,37 @@
             assertThat(getParcelableList<MyParcelable>("value")).isEqualTo(myParcelableList)
         }
 
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
         @Serializable
         data class SparseParcelableArrayContainer(
+            // Unlike arrays this works as `SparseArray`s can be down-casted, e.g.
+            // a `SparseArray<Parcelable>` can be casted to `SparseArray<MyParcelable>`.
             @Serializable(with = SparseParcelableArraySerializer::class)
-            val value: SparseArray<@Serializable(with = ParcelableSerializer::class) MyParcelable>
+            val value:
+                SparseArray<
+                    @Serializable(with = MyParcelableAsParcelableSerializer::class)
+                    MyParcelable
+                >
         )
         val mySparseParcelableArray =
             SparseArray<MyParcelable>().apply {
                 append(1, MyParcelable(3, "foo", 3.14))
                 append(3, MyParcelable(4, "bar", 1.73))
             }
-        SparseParcelableArrayContainer(mySparseParcelableArray).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getSparseParcelableArray<Parcelable>("value"))
-                .isEqualTo(mySparseParcelableArray)
-        }
-    }
-
-    @Test
-    fun concreteTypeSerializers() {
-        // No need to suppress SERIALIZER_TYPE_INCOMPATIBLE with these serializers.
-        @Serializable
-        data class CharSequenceContainer(
-            @Serializable(with = StringAsCharSequenceSerializer::class) val value: String
-        )
-        CharSequenceContainer("foo").encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getCharSequence("value")).isEqualTo("foo")
-        }
-
-        @Serializable
-        data class SerializableContainer(
-            @Serializable(with = MyJavaSerializableAsJavaSerializableSerializer::class)
-            val value: MyJavaSerializable
-        )
-        val myJavaSerializable = MyJavaSerializable(3, "foo", 3.14)
-        SerializableContainer(myJavaSerializable).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getJavaSerializable<MyJavaSerializable>("value"))
-                .isEqualTo(myJavaSerializable)
-        }
-
-        @Serializable
-        data class ParcelableContainer(
-            @Serializable(with = MyParcelableAsParcelableSerializer::class) val value: MyParcelable
-        )
-        val myParcelable = MyParcelable(3, "foo", 3.14)
-        ParcelableContainer(myParcelable).encodeDecode {
-            assertThat(size()).isEqualTo(1)
-            assertThat(getParcelable<MyParcelable>("value")).isEqualTo(myParcelable)
-        }
+        SparseParcelableArrayContainer(mySparseParcelableArray)
+            .encodeDecode(
+                checkDecoded = { decoded, original ->
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                        assertThat(decoded.value.contentEquals(original.value))
+                    } else {
+                        error("VERSION.SDK_INT < S")
+                    }
+                },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    assertThat(getSparseParcelableArray<Parcelable>("value"))
+                        .isEqualTo(mySparseParcelableArray)
+                }
+            )
     }
 }
 
@@ -610,45 +572,11 @@
     }
 }
 
-private object CharArrayAsStringSerializer : KSerializer<Array<Char>> {
-    private val delegateSerializer = serializer<String>()
-    override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("Array<Char>", PrimitiveKind.STRING)
-
-    override fun deserialize(decoder: Decoder): Array<Char> {
-        val s = decoder.decodeSerializableValue(delegateSerializer)
-        val result = Array(s.length) { s[it] }
-        return result
-    }
-
-    override fun serialize(encoder: Encoder, value: Array<Char>) {
-        val charArray = CharArray(value.size)
-        value.forEachIndexed { index, c -> charArray[index] = c }
-        encoder.encodeSerializableValue(delegateSerializer, String(charArray))
-    }
-}
-
-@OptIn(ExperimentalSerializationApi::class)
-private object SparseStringArrayAsMapSerializer : KSerializer<SparseArray<String>> {
-    private val delegateSerializer = serializer<Map<Int, String>>()
-    override val descriptor = SerialDescriptor("SparseArray<String>", delegateSerializer.descriptor)
-
-    override fun deserialize(decoder: Decoder): SparseArray<String> {
-        val m = decoder.decodeSerializableValue(delegateSerializer)
-        val result = SparseArray<String>()
-        m.forEach { (k, v) -> result.append(k, v) }
-        return result
-    }
-
-    override fun serialize(encoder: Encoder, value: SparseArray<String>) {
-        val map = buildMap { value.forEach { k, v -> put(k, v) } }
-        encoder.encodeSerializableValue(delegateSerializer, map)
-    }
-}
-
-private class StringAsCharSequenceSerializer : CharSequenceSerializer<String>()
-
 private class MyJavaSerializableAsJavaSerializableSerializer :
     JavaSerializableSerializer<MyJavaSerializable>()
 
 private class MyParcelableAsParcelableSerializer : ParcelableSerializer<MyParcelable>()
+
+private class CustomJavaSerializableSerializer : JavaSerializableSerializer<java.io.Serializable>()
+
+private class CustomParcelableSerializer : ParcelableSerializer<Parcelable>()
diff --git a/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt
new file mode 100644
index 0000000..edf79cc
--- /dev/null
+++ b/savedstate/savedstate/src/androidUnitTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.android.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2025 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.savedstate
+
+import android.os.Parcel
+
+actual fun platformEncodeDecode(savedState: SavedState): SavedState {
+    val parcel =
+        Parcel.obtain().apply {
+            savedState.writeToParcel(this, 0)
+            setDataPosition(0)
+        }
+    return SavedState.CREATOR.createFromParcel(parcel)
+}
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTest.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTest.kt
index 86adf0b..89d6548 100644
--- a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTest.kt
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTest.kt
@@ -464,19 +464,24 @@
                     putSavedState("ss", savedState { putString("s", "bar") })
                 }
             )
-            .encodeDecode {
-                assertThat(size()).isEqualTo(1)
-                getSavedState("s").read {
-                    assertThat(size()).isEqualTo(4)
-                    assertThat(getInt("i")).isEqualTo(1)
-                    assertThat(getString("s")).isEqualTo("foo")
-                    assertThat(getIntArray("a")).isEqualTo(intArrayOf(1, 3, 5))
-                    getSavedState("ss").read {
-                        assertThat(size()).isEqualTo(1)
-                        assertThat(getString("s")).isEqualTo("bar")
+            .encodeDecode(
+                checkDecoded = { decoded, original ->
+                    assertThat(decoded.s.read { contentDeepEquals(original.s) })
+                },
+                checkEncoded = {
+                    assertThat(size()).isEqualTo(1)
+                    getSavedState("s").read {
+                        assertThat(size()).isEqualTo(4)
+                        assertThat(getInt("i")).isEqualTo(1)
+                        assertThat(getString("s")).isEqualTo("foo")
+                        assertThat(getIntArray("a")).isEqualTo(intArrayOf(1, 3, 5))
+                        getSavedState("ss").read {
+                            assertThat(size()).isEqualTo(1)
+                            assertThat(getString("s")).isEqualTo("bar")
+                        }
                     }
                 }
-            }
+            )
 
         val origin = savedState {
             putInt("i", 1)
diff --git a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.kt b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.kt
index e9ac161..67930cd 100644
--- a/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.kt
+++ b/savedstate/savedstate/src/commonTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.kt
@@ -23,16 +23,30 @@
 import kotlinx.serialization.serializer
 
 internal object SavedStateCodecTestUtils {
+    /* Test the following steps: 1. encode `T` to a `SavedState`, 2. parcelize it to a `Parcel`,
+     * 3. un-parcelize it back to a `SavedState`, and 4. decode it back to a `T`. Step 2 and 3
+     * are only performed on Android. Here's the whole process:
+     *
+     * (A)Serializable -1-> (B)SavedState -2-> (C)Parcel -3-> (D)SavedState -4-> (E)Serializable
+     *
+     * `checkEncoded` can be used to check the content of "B", and `checkDecoded` can be
+     *  used to compare the instances of "E" and "A".
+     */
     inline fun <reified T : Any> T.encodeDecode(
         serializer: KSerializer<T> = serializer<T>(),
-        checkContent: SavedStateReader.() -> Unit = { assertThat(size()).isEqualTo(0) }
+        checkDecoded: (T, T) -> Unit = { decoded, original ->
+            assertThat(decoded).isEqualTo(original)
+        },
+        checkEncoded: SavedStateReader.() -> Unit = { assertThat(size()).isEqualTo(0) }
     ) {
-        assertThat(
-                decodeFromSavedState(
-                    serializer,
-                    encodeToSavedState(serializer, this).apply { read { checkContent() } }
-                )
-            )
-            .isEqualTo(this)
+        val encoded = encodeToSavedState(serializer, this)
+        encoded.read { checkEncoded() }
+
+        val restored = platformEncodeDecode(encoded)
+
+        val decoded = decodeFromSavedState(serializer, restored)
+        checkDecoded(decoded, this)
     }
 }
+
+expect fun platformEncodeDecode(savedState: SavedState): SavedState
diff --git a/savedstate/savedstate/src/nonAndroidTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.nonAndroid.kt b/savedstate/savedstate/src/nonAndroidTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.nonAndroid.kt
new file mode 100644
index 0000000..023701a8
--- /dev/null
+++ b/savedstate/savedstate/src/nonAndroidTest/kotlin/androidx/savedstate/SavedStateCodecTestUtils.nonAndroid.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2025 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.savedstate
+
+// No parceling in non-Android platforms.
+actual fun platformEncodeDecode(savedState: SavedState): SavedState = savedState