Replaces GSON with kotlinx.serialization

Test: Updated unit tests.
Relnote: JSON serialization/deserialization within the library is now handled by kotlinx.serialization instead of GSON.
Bug: 374810938
Change-Id: Ia4337631f5f5b52b04446e6f6e00eb4baff25bfe
diff --git a/security/security-state/build.gradle b/security/security-state/build.gradle
index 3ed2917..027680e 100644
--- a/security/security-state/build.gradle
+++ b/security/security-state/build.gradle
@@ -27,13 +27,13 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("org.jetbrains.kotlin.android")
+    alias(libs.plugins.kotlinSerialization)
 }
 
 dependencies {
     api("androidx.annotation:annotation:1.8.1")
-    api(libs.kotlinStdlib)
     api(libs.kotlinCoroutinesCore)
-    implementation("com.google.code.gson:gson:2.10.1")
+    implementation(libs.kotlinSerializationJson)
     implementation("androidx.core:core:1.12.0")
     implementation("androidx.webkit:webkit:1.11.0")
 
@@ -56,9 +56,6 @@
 android {
     compileSdk 35
     namespace "androidx.security.state"
-    defaultConfig {
-        consumerProguardFiles 'proguard-rules.pro'
-    }
 }
 
 androidx {
diff --git a/security/security-state/proguard-rules.pro b/security/security-state/proguard-rules.pro
deleted file mode 100644
index be33693..0000000
--- a/security/security-state/proguard-rules.pro
+++ /dev/null
@@ -1 +0,0 @@
--keepclassmembers class androidx.security.state.SecurityPatchState$** { *; }
\ No newline at end of file
diff --git a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
index 4496a2a..d45c24b 100644
--- a/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
+++ b/security/security-state/src/main/java/androidx/security/state/SecurityPatchState.kt
@@ -24,15 +24,16 @@
 import androidx.security.state.SecurityStateManager.Companion.KEY_KERNEL_VERSION
 import androidx.security.state.SecurityStateManager.Companion.KEY_SYSTEM_SPL
 import androidx.security.state.SecurityStateManager.Companion.KEY_VENDOR_SPL
-import com.google.gson.Gson
-import com.google.gson.JsonSyntaxException
-import com.google.gson.annotations.SerializedName
 import java.text.ParseException
 import java.text.SimpleDateFormat
 import java.util.Calendar
 import java.util.Date
 import java.util.Locale
 import java.util.regex.Pattern
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.Json
 
 /**
  * Provides methods to access and manage security state information for various components within a
@@ -308,20 +309,21 @@
         public fun getBuildVersion(): Int = buildVersion
     }
 
+    @Serializable
     private data class VulnerabilityReport(
         /* Key is the SPL date yyyy-MM-dd */
-        @SerializedName("vulnerabilities")
         val vulnerabilities: Map<String, List<VulnerabilityGroup>>,
 
         /* Key is the SPL date yyyy-MM-dd, values are kernel versions */
-        @SerializedName("kernel_lts_versions") val kernelLtsVersions: Map<String, List<String>>
+        @SerialName("kernel_lts_versions") val kernelLtsVersions: Map<String, List<String>>
     )
 
+    @Serializable
     private data class VulnerabilityGroup(
-        @SerializedName("cve_identifiers") val cveIdentifiers: List<String>,
-        @SerializedName("asb_identifiers") val asbIdentifiers: List<String>,
-        @SerializedName("severity") val severity: String,
-        @SerializedName("components") val components: List<String>
+        @SerialName("cve_identifiers") val cveIdentifiers: List<String>,
+        @SerialName("asb_identifiers") val asbIdentifiers: List<String>,
+        val severity: String,
+        val components: List<String>
     )
 
     /**
@@ -354,8 +356,9 @@
         val result: VulnerabilityReport
 
         try {
-            result = Gson().fromJson(jsonString, VulnerabilityReport::class.java)
-        } catch (e: JsonSyntaxException) {
+            val json = Json { ignoreUnknownKeys = true }
+            result = json.decodeFromString<VulnerabilityReport>(jsonString)
+        } catch (e: SerializationException) {
             throw IllegalArgumentException("Malformed JSON input: ${e.message}")
         }
 
@@ -794,7 +797,6 @@
 
         val updates = mutableListOf<UpdateInfo>()
         val contentResolver = context.contentResolver
-        val gson = Gson()
 
         updateInfoProviders.forEach { providerUri ->
             val cursor = contentResolver.query(providerUri, arrayOf("json"), null, null, null)
@@ -802,7 +804,9 @@
                 while (it.moveToNext()) {
                     val json = it.getString(it.getColumnIndexOrThrow("json"))
                     try {
-                        val updateInfo = gson.fromJson(json, UpdateInfo::class.java) ?: continue
+                        val serializableUpdateInfo =
+                            Json.decodeFromString<SerializableUpdateInfo>(json)
+                        val updateInfo: UpdateInfo = serializableUpdateInfo.toUpdateInfo()
                         val component = updateInfo.component
                         val deviceSpl = getDeviceSecurityPatchLevel(component)
 
diff --git a/security/security-state/src/main/java/androidx/security/state/UpdateInfo.kt b/security/security-state/src/main/java/androidx/security/state/UpdateInfo.kt
index 8ef484f..49b29b45 100644
--- a/security/security-state/src/main/java/androidx/security/state/UpdateInfo.kt
+++ b/security/security-state/src/main/java/androidx/security/state/UpdateInfo.kt
@@ -16,8 +16,39 @@
 
 package androidx.security.state
 
+import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.Objects
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+
+private object DateSerializer : KSerializer<Date> {
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
+
+    private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
+
+    override fun serialize(encoder: Encoder, value: Date): Unit =
+        encoder.encodeString(dateFormat.format(value))
+
+    override fun deserialize(decoder: Decoder): Date = dateFormat.parse(decoder.decodeString())!!
+}
+
+@Serializable
+internal class SerializableUpdateInfo(
+    private val uri: String,
+    private val component: String,
+    private val securityPatchLevel: String,
+    @Serializable(with = DateSerializer::class) private val publishedDate: Date
+) {
+    internal fun toUpdateInfo(): UpdateInfo =
+        UpdateInfo(uri, component, securityPatchLevel, publishedDate)
+}
 
 /** Represents information about an available update for a component. */
 public class UpdateInfo(
@@ -33,6 +64,10 @@
     /** Date when the available update was published. */
     public val publishedDate: Date
 ) {
+
+    internal fun toSerializableUpdateInfo(): SerializableUpdateInfo =
+        SerializableUpdateInfo(uri, component, securityPatchLevel, publishedDate)
+
     /**
      * Returns a string representation of the update information.
      *
diff --git a/security/security-state/src/main/java/androidx/security/state/UpdateInfoProvider.kt b/security/security-state/src/main/java/androidx/security/state/UpdateInfoProvider.kt
index 7dec3e4..1c6c6e6 100644
--- a/security/security-state/src/main/java/androidx/security/state/UpdateInfoProvider.kt
+++ b/security/security-state/src/main/java/androidx/security/state/UpdateInfoProvider.kt
@@ -22,7 +22,7 @@
 import android.database.Cursor
 import android.database.MatrixCursor
 import android.net.Uri
-import com.google.gson.Gson
+import kotlinx.serialization.json.Json
 
 /**
  * A content provider for managing and serving update information for system components. This class
@@ -179,7 +179,11 @@
         val sharedPreferences = context.getSharedPreferences(updateInfoPrefs, Context.MODE_PRIVATE)
         val editor = sharedPreferences?.edit()
         val key = getKeyForUpdateInfo(updateInfo)
-        val json = Gson().toJson(updateInfo)
+        val json =
+            Json.encodeToString(
+                SerializableUpdateInfo.serializer(),
+                updateInfo.toSerializableUpdateInfo()
+            )
         editor?.putString(key, json)
         editor?.apply()
     }
@@ -218,7 +222,8 @@
         for ((_, value) in allEntries) {
             val json = value as? String
             if (json != null) {
-                val updateInfo: UpdateInfo = Gson().fromJson(json, UpdateInfo::class.java)
+                val serializableUpdateInfo: SerializableUpdateInfo = Json.decodeFromString(json)
+                val updateInfo: UpdateInfo = serializableUpdateInfo.toUpdateInfo()
                 allUpdates.add(updateInfo)
             }
         }
diff --git a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
index d3a8f6fd..7ab74c2 100644
--- a/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
+++ b/security/security-state/src/test/java/androidx/security/state/SecurityPatchStateTest.kt
@@ -28,10 +28,10 @@
 import android.os.Bundle
 import androidx.annotation.RequiresApi
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.gson.Gson
 import java.time.LocalDate
 import java.time.ZoneOffset
 import java.util.Date
+import kotlinx.serialization.json.Json
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -56,6 +56,11 @@
             .setSecurityPatchLevel("2022-01-01")
             .setPublishedDate(Date.from(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC)))
             .build()
+    private val updateInfoJson =
+        Json.encodeToString(
+            SerializableUpdateInfo.serializer(),
+            updateInfo.toSerializableUpdateInfo()
+        )
     private val mockEmptyEditor: SharedPreferences.Editor = mock<SharedPreferences.Editor> {}
     private val mockEditor: SharedPreferences.Editor =
         mock<SharedPreferences.Editor> {
@@ -65,7 +70,7 @@
     private val mockPrefs: SharedPreferences =
         mock<SharedPreferences> {
             on { edit() } doReturn mockEditor
-            on { all } doReturn mapOf(Pair("key", Gson().toJson(updateInfo)))
+            on { all } doReturn mapOf(Pair("key", updateInfoJson))
         }
     private val mockPackageManager: PackageManager =
         mock<PackageManager> {
@@ -77,7 +82,7 @@
         mock<Cursor> {
             on { moveToNext() } doReturn true doReturn false doReturn true doReturn false
             on { getColumnIndexOrThrow(Mockito.eq("json")) } doReturn 123
-            on { getString(Mockito.eq(123)) } doReturn Gson().toJson(updateInfo)
+            on { getString(Mockito.eq(123)) } doReturn updateInfoJson
         }
     private val mockContentResolver: ContentResolver =
         mock<ContentResolver> {
diff --git a/security/security-state/src/test/java/androidx/security/state/UpdateInfoProviderTest.kt b/security/security-state/src/test/java/androidx/security/state/UpdateInfoProviderTest.kt
index ec7c5f8..6d3aff3 100644
--- a/security/security-state/src/test/java/androidx/security/state/UpdateInfoProviderTest.kt
+++ b/security/security-state/src/test/java/androidx/security/state/UpdateInfoProviderTest.kt
@@ -23,10 +23,10 @@
 import android.net.Uri
 import androidx.security.state.SecurityPatchState.Companion.COMPONENT_SYSTEM
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.gson.Gson
 import java.time.LocalDate
 import java.time.ZoneOffset
 import java.util.Date
+import kotlinx.serialization.json.Json
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
@@ -63,7 +63,11 @@
             .setSecurityPatchLevel("2022-01-01")
             .setPublishedDate(publishedDate)
             .build()
-    private val expectedJson = Gson().toJson(updateInfo)
+    private val expectedJson =
+        Json.encodeToString(
+            SerializableUpdateInfo.serializer(),
+            updateInfo.toSerializableUpdateInfo()
+        )
     private val mockEmptyEditor: SharedPreferences.Editor = mock<SharedPreferences.Editor> {}
     private val mockEditor: SharedPreferences.Editor =
         mock<SharedPreferences.Editor> {
@@ -73,7 +77,7 @@
     private val mockPrefs: SharedPreferences =
         mock<SharedPreferences> {
             on { edit() } doReturn mockEditor
-            on { all } doReturn mapOf(Pair("key", Gson().toJson(updateInfo)))
+            on { all } doReturn mapOf(Pair("key", expectedJson))
         }
     private val mockContext: Context =
         mock<Context> {