Add platform-specific encoding and decoding to SavedState codec tests

Add platform-specific encoding and decoding (parcelling and unparcelling on Android and no-op on other platforms) to SavedState codec tests for increased coverage.

After this change, we got `ClassCastException` when restoring arrays of sub-type of `Parcelable`s. This is because the array restored is of type `Array<Parcelable>` and we cannot assign it to an array of Parcelable's subtype, e.g. `Array<MyParcelable>`. I haven't found a way to create instances of `Array<subtype of Parcelable>` in our serializer so I changed the tests to reflect that using `ParcelableArraySerializer` with `Array<subtype of Parcelable>` will lead to `ClassCastException`. Now in all our built-in serializers for collection types, only `ParcelableListSerializer` and `SparseParcelableArraySerializer` can be used with subtypes.

A `checkDecoded` parameter is added to `encodeDecode()`. This can be used to check that the decoded Serializable is essentially the same as the original Serializable when its `equals()` doesn't work.

Bug: 387270265
Test: SavedStateCodecAndroidTest.android.kt↲
Change-Id: I31beee5105f1a6a5495e24f6c9e643e8550bc6cc
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 54e07b7..0f013b0 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
@@ -25,6 +25,7 @@
 import android.util.SparseArray
 import androidx.savedstate.SavedState
 import androidx.savedstate.read
+import androidx.savedstate.savedState
 import androidx.savedstate.serialization.SavedStateDecoder
 import androidx.savedstate.serialization.SavedStateEncoder
 import androidx.savedstate.write
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 0536f05..ce4bdb9 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 {
@@ -306,7 +279,7 @@
         @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 {
@@ -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(
@@ -443,29 +405,24 @@
             }
         }
 
-        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+        @Suppress("SERIALIZER_TYPE_INCOMPATIBLE", "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 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
@@ -496,6 +453,8 @@
         @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
         @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>
         )
@@ -509,6 +468,8 @@
         @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>
         )
@@ -517,46 +478,21 @@
                 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)
+                }
+            )
     }
 }
 
@@ -632,45 +568,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