summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author William Loh <wloh@google.com> 2024-11-19 16:10:11 -0800
committer William Loh <wloh@google.com> 2024-11-20 13:58:30 -0800
commit92e74bc583f64e88e9fc3fdc675848fe84e093d3 (patch)
treedab43cfc9ccb8645d0d1ec01b8a2b1c65428ff8a
parent35e40bd5097471a439a402cf929fd96e4ae52665 (diff)
Update groups after domain verifiation.
When a domain is successfully verified update the group rules if they are included in the assetlinks.json. For the V1 verifier, parsed groups will be first collected in a Room database and updated only after all domains have been verified so that we do not update the groups if domain verification fails due to network errors. For the V2 verifier we can update the groups as each domain is verified. Bug: 307557449 Test: manual by stubbing UrlFetcher.fetch to return a test json Flag: EXEMPT external library Change-Id: Ic57d8b72240a1f94d6961796633253206cbd48d9
-rw-r--r--packages/StatementService/Android.bp2
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/Converters.kt130
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt27
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt36
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt41
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt4
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt21
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt14
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt55
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt46
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt10
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt7
-rw-r--r--packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt19
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java2
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java35
15 files changed, 406 insertions, 43 deletions
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 90e1808b7d44..39b0302beff8 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -38,8 +38,10 @@ android_app {
"StatementServiceParser",
"androidx.appcompat_appcompat",
"androidx.collection_collection-ktx",
+ "androidx.room_room-runtime",
"androidx.work_work-runtime",
"androidx.work_work-runtime-ktx",
"kotlinx-coroutines-android",
],
+ plugins: ["androidx.room_room-compiler-plugin"],
}
diff --git a/packages/StatementService/src/com/android/statementservice/database/Converters.kt b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
new file mode 100644
index 000000000000..21ecc8b4a651
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.database
+
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilterGroup
+import android.util.JsonReader
+import androidx.room.TypeConverter
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.StringReader
+import java.util.ArrayList
+
+class Converters {
+ companion object {
+ private const val ACTION_NAME = "action"
+ private const val FILTERS_NAME = "filters"
+ private const val URI_PART_NAME = "uriPart"
+ private const val PATTERN_TYPE_NAME = "patternType"
+ private const val FILTER_NAME = "filter"
+ }
+
+ @TypeConverter
+ fun groupsToJson(groups: List<UriRelativeFilterGroup>): String {
+ val json = JSONArray()
+ for (group in groups) {
+ json.put(groupToJson(group))
+ }
+ return json.toString()
+ }
+
+ @TypeConverter
+ fun stringToGroups(json: String): List<UriRelativeFilterGroup> {
+ val groups = ArrayList<UriRelativeFilterGroup>()
+ StringReader(json).use { stringReader ->
+ JsonReader(stringReader).use { reader ->
+ reader.beginArray()
+ while (reader.hasNext()) {
+ groups.add(parseGroup(reader))
+ }
+ reader.endArray()
+ }
+ }
+ return groups
+ }
+
+ private fun groupToJson(group: UriRelativeFilterGroup): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(ACTION_NAME, group.action)
+ val filters = JSONArray()
+ for (filter in group.uriRelativeFilters) {
+ filters.put(filterToJson(filter))
+ }
+ jsonObject.put(FILTERS_NAME, filters)
+ return jsonObject
+ }
+
+ private fun filterToJson(filter: UriRelativeFilter): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(URI_PART_NAME, filter.uriPart)
+ jsonObject.put(PATTERN_TYPE_NAME, filter.patternType)
+ jsonObject.put(FILTER_NAME, filter.filter)
+ return jsonObject
+ }
+
+ private fun parseGroup(reader: JsonReader): UriRelativeFilterGroup {
+ val jsonObject = JSONObject()
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ ACTION_NAME -> jsonObject.put(ACTION_NAME, reader.nextInt())
+ FILTERS_NAME -> jsonObject.put(FILTERS_NAME, parseFilters(reader))
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+
+ val group = UriRelativeFilterGroup(jsonObject.getInt(ACTION_NAME))
+ val filters = jsonObject.getJSONArray(FILTERS_NAME)
+ for (i in 0 until filters.length()) {
+ val filter = filters.getJSONObject(i)
+ group.addUriRelativeFilter(UriRelativeFilter(
+ filter.getInt(URI_PART_NAME),
+ filter.getInt(PATTERN_TYPE_NAME),
+ filter.getString(FILTER_NAME)
+ ))
+ }
+ return group
+ }
+
+ private fun parseFilters(reader: JsonReader): JSONArray {
+ val filters = JSONArray()
+ reader.beginArray()
+ while (reader.hasNext()) {
+ filters.put(parseFilter(reader))
+ }
+ reader.endArray()
+ return filters
+ }
+
+ private fun parseFilter(reader: JsonReader): JSONObject {
+ reader.beginObject()
+ val jsonObject = JSONObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ URI_PART_NAME, PATTERN_TYPE_NAME -> jsonObject.put(name, reader.nextInt())
+ FILTER_NAME -> jsonObject.put(name, reader.nextString())
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+ return jsonObject
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
new file mode 100644
index 000000000000..c61666910cb4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.database
+
+import android.content.UriRelativeFilterGroup
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["packageName", "domain"])
+data class DomainGroups(
+ val packageName: String,
+ val domain: String,
+ val groups: List<UriRelativeFilterGroup>
+) \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
new file mode 100644
index 000000000000..3b4dcea48180
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.database
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface DomainGroupsDao {
+ @Query("SELECT * FROM DomainGroups WHERE packageName = :packageName")
+ fun getDomainGroups(packageName: String): List<DomainGroups>
+
+ @Insert
+ fun insertDomainGroups(vararg domainGroups: DomainGroups)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName AND domain = :domain")
+ fun clear(packageName: String, domain: String)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName")
+ fun clear(packageName: String)
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
new file mode 100644
index 000000000000..39833f6bc80b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+
+@Database(entities = [DomainGroups::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class DomainGroupsDatabase : RoomDatabase() {
+ companion object {
+ private const val DATABASE_NAME = "domain-groups"
+ @Volatile
+ private var instance: DomainGroupsDatabase? = null
+
+ fun getInstance(context: Context) = instance ?: synchronized(this) {
+ instance ?: Room.databaseBuilder(
+ context,
+ DomainGroupsDatabase::class.java, DATABASE_NAME
+ ).build().also { instance = it }
+ }
+ }
+ abstract fun domainGroupsDao(): DomainGroupsDao
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
index acb54f6093de..0d7a1fdbcfb8 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.android.statementservice.domain.worker.CollectV1Worker
+import com.android.statementservice.domain.worker.GroupUpdateV1Worker
import com.android.statementservice.domain.worker.SingleV1RequestWorker
/**
@@ -67,7 +68,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
}
}
- //clear sp before enqueue unique work since policy is REPLACE
+ // clear sp before enqueue unique work since policy is REPLACE
val deContext = context.createDeviceProtectedStorageContext()
val editor = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)?.edit()
editor?.clear()?.apply()
@@ -78,6 +79,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
workRequests
)
.then(CollectV1Worker.buildRequest(verificationId, packageName))
+ .then(GroupUpdateV1Worker.buildRequest(packageName))
.enqueue()
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 29f844fb1a5d..6914347544de 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -24,6 +24,7 @@ import androidx.collection.LruCache
import com.android.statementservice.network.retriever.StatementRetriever
import com.android.statementservice.retriever.AbstractAsset
import com.android.statementservice.retriever.AbstractAssetMatcher
+import com.android.statementservice.retriever.Statement
import com.android.statementservice.utils.Result
import com.android.statementservice.utils.StatementUtils
import com.android.statementservice.utils.component1
@@ -87,10 +88,10 @@ class DomainVerifier private constructor(
host: String,
packageName: String,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] }
.takeIf { it!!.isPresent }
- ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER
+ ?: return Triple(WorkResult.failure(), VerifyStatus.FAILURE_PACKAGE_MANAGER, null)
return verifyHost(host, assetMatcher.get(), network)
}
@@ -98,34 +99,34 @@ class DomainVerifier private constructor(
host: String,
assetMatcher: AbstractAssetMatcher,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
var exception: Exception? = null
val resultAndStatus = try {
val sourceAsset = StatementUtils.createWebAssetString(host)
.let(AbstractAsset::create)
val result = retriever.retrieve(sourceAsset, network)
- ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN
+ ?: return Triple(WorkResult.success(), VerifyStatus.FAILURE_UNKNOWN, null)
when (result.responseCode) {
HttpURLConnection.HTTP_MOVED_PERM,
HttpURLConnection.HTTP_MOVED_TEMP -> {
- WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REDIRECT, null)
}
else -> {
- val isVerified = result.statements.any { statement ->
+ val statement = result.statements.firstOrNull { statement ->
(StatementUtils.RELATION.matches(statement.relation) &&
assetMatcher.matches(statement.target))
}
- if (isVerified) {
- WorkResult.success() to VerifyStatus.SUCCESS
+ if (statement != null) {
+ Triple(WorkResult.success(), VerifyStatus.SUCCESS, statement)
} else {
- WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REJECTED_BY_SERVER, statement)
}
}
}
} catch (e: Exception) {
exception = e
- WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN
+ Triple(WorkResult.retry(), VerifyStatus.FAILURE_UNKNOWN, null)
}
if (DEBUG) {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
index a17f9c9186ff..64d2d98b931b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
@@ -17,9 +17,12 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroupsDatabase
import com.android.statementservice.domain.DomainVerifier
abstract class BaseRequestWorker(
@@ -27,8 +30,19 @@ abstract class BaseRequestWorker(
protected val params: WorkerParameters
) : CoroutineWorker(appContext, params) {
+ protected val database = DomainGroupsDatabase.getInstance(appContext).domainGroupsDao()
+
protected val verificationManager =
appContext.getSystemService(DomainVerificationManager::class.java)!!
protected val verifier = DomainVerifier.getInstance(appContext)
+
+ protected fun updateUriRelativeFilterGroups(packageName: String, domainGroupUpdates: Map<String, List<UriRelativeFilterGroup>>) {
+ val verifiedDomains = verificationManager.getDomainVerificationInfo(packageName)?.hostToStateMap?.filterValues {
+ it == DomainVerificationInfo.STATE_SUCCESS || it == DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
+ }?.keys?.toList() ?: emptyList()
+ val domainGroups = verificationManager.getUriRelativeFilterGroups(packageName, verifiedDomains)
+ domainGroupUpdates.forEach { (domain, groups) -> domainGroups[domain] = groups }
+ verificationManager.setUriRelativeFilterGroups(packageName, domainGroups)
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
new file mode 100644
index 000000000000..f53dfc47acaa
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.coroutineScope
+
+class GroupUpdateV1Worker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+
+ private const val PACKAGE_NAME_KEY = "packageName"
+
+ fun buildRequest(packageName: String) = OneTimeWorkRequestBuilder<GroupUpdateV1Worker>()
+ .setInputData(
+ Data.Builder()
+ .putString(PACKAGE_NAME_KEY, packageName)
+ .build()
+ )
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+ updateUriRelativeFilterGroups(packageName)
+ Result.success()
+ }
+
+ private fun updateUriRelativeFilterGroups(packageName: String) {
+ val groupUpdates = database.getDomainGroups(packageName)
+ updateUriRelativeFilterGroups(
+ packageName,
+ groupUpdates.associateBy({it.domain}, {it.groups})
+ )
+ database.clear(packageName)
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
index 61ab2c264e6a..f83601a7807b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -17,10 +17,13 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.NetworkType
import androidx.work.WorkerParameters
import com.android.statementservice.domain.VerifyStatus
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -36,7 +39,13 @@ class RetryRequestWorker(
params: WorkerParameters
) : BaseRequestWorker(appContext, params) {
- data class VerifyResult(val domainSetId: UUID, val host: String, val status: VerifyStatus)
+ data class VerifyResult(
+ val domainSetId: UUID,
+ val host: String,
+ val status: VerifyStatus,
+ val packageName: String,
+ val groups: List<UriRelativeFilterGroup>
+ )
override suspend fun doWork() = coroutineScope {
if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
@@ -49,8 +58,11 @@ class RetryRequestWorker(
.map { (domainSetId, packageName, host) ->
async {
if (isActive && !isStopped) {
- val (_, status) = verifier.verifyHost(host, packageName, params.network)
- VerifyResult(domainSetId, host, status)
+ val (_, status, statement) = verifier.verifyHost(host, packageName, params.network)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ VerifyResult(domainSetId, host, status, packageName, groups)
} else {
// If the job gets cancelled, stop the remaining hosts, but continue the
// job to commit the results for hosts that were already requested.
@@ -60,17 +72,25 @@ class RetryRequestWorker(
}
.awaitAll()
.filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
- .groupBy { it.domainSetId }
- .forEach { (domainSetId, resultsById) ->
- resultsById.groupBy { it.status }
- .mapValues { it.value.map(VerifyResult::host).toSet() }
- .forEach { (status, hosts) ->
- verificationManager.setDomainVerificationStatus(
- domainSetId,
- hosts,
- status.value
- )
+ .groupBy { it.packageName }
+ .forEach { (packageName, resultsByName) ->
+ val groupUpdates = mutableMapOf<String, List<UriRelativeFilterGroup>>()
+ resultsByName.groupBy { it.domainSetId }
+ .forEach { (domainSetId, resultsById) ->
+ resultsById.groupBy { it.status }
+ .forEach { (status, verifyResults) ->
+ val error = verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ verifyResults.map(VerifyResult::host).toSet(),
+ status.value
+ )
+ if (error == DomainVerificationManager.STATUS_OK
+ && status == VerifyStatus.SUCCESS) {
+ verifyResults.forEach { groupUpdates[it.host] = it.groups }
+ }
+ }
}
+ updateUriRelativeFilterGroups(packageName, groupUpdates)
}
// Succeed regardless of results since this retry is best effort and not required
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
index 7a198cb59ca4..253a162a73a2 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -22,7 +22,9 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroups
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
@@ -60,7 +62,9 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ database.clear(packageName, host)
+
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
if (DEBUG) {
Log.d(
@@ -75,6 +79,10 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val deContext = appContext.createDeviceProtectedStorageContext()
val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
sp?.edit()?.putInt("$HOST_SUCCESS_PREFIX$host", status.value)?.apply()
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ database.insertDomainGroups(DomainGroups(packageName, host, groups))
Result.success()
}
is Result.Failure -> {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
index 562b132d36d6..8b1347a69932 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
@@ -22,6 +22,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
import java.util.UUID
@@ -59,9 +60,13 @@ class SingleV2RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
verificationManager.setDomainVerificationStatus(domainSetId, setOf(host), status.value)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ updateUriRelativeFilterGroups(packageName, mapOf(host to groups))
result
}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index ad137400fa86..d10cb0f91c11 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -39,6 +39,11 @@ object StatementParser {
private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
+ private const val COMMENTS_NAME = "comments"
+ private const val EXCLUDE_NAME = "exclude"
+ private const val FRAGMENT_NAME = "#"
+ private const val QUERY_NAME = "?"
+ private const val PATH_NAME = "/"
/**
* Parses a JSON array of statements.
@@ -99,9 +104,7 @@ object StatementParser {
FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
)
val target = AssetFactory.create(targetObject)
- val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
- statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
- )
+ val dynamicAppLinkComponents = parseDynamicAppLinkComponents(statement)
val statements = (0 until relations.length())
.map { relations.getString(it) }
@@ -129,13 +132,13 @@ object StatementParser {
}
private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
- val query = component.optJSONObject("?")
+ val query = component.optJSONObject(QUERY_NAME)
return DynamicAppLinkComponent.create(
- component.optBoolean("exclude", false),
- component.optString("#"),
- component.optString("/"),
+ component.optBoolean(EXCLUDE_NAME, false),
+ if (component.has(FRAGMENT_NAME)) component.getString(FRAGMENT_NAME) else null,
+ if (component.has(PATH_NAME)) component.getString(PATH_NAME) else null,
query?.keys()?.asSequence()?.associateWith { query.getString(it) },
- component.optString("comments")
+ component.optString(COMMENTS_NAME)
)
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
index dc27e125e204..c32f1949fed4 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -130,7 +130,7 @@ public final class DynamicAppLinkComponent {
@Override
public String toString() {
StringBuilder statement = new StringBuilder();
- statement.append("HandleAllUriRule: ");
+ statement.append("DynamicAppLinkComponent: ");
statement.append(mExclude);
statement.append(", ");
statement.append(mFragment);
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index 7635e8234dc0..ab1853c1d3ae 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -24,8 +24,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
/**
* A helper class that creates a {@link JSONObject} from a {@link JsonReader}.
@@ -48,7 +46,7 @@ public final class JsonParser {
JsonToken token = reader.peek();
if (token.equals(JsonToken.BEGIN_ARRAY)) {
- output.put(fieldName, new JSONArray(parseArray(reader)));
+ output.put(fieldName, parseArray(reader));
} else if (token.equals(JsonToken.STRING)) {
output.put(fieldName, reader.nextString());
} else if (token.equals(JsonToken.BEGIN_OBJECT)) {
@@ -57,9 +55,11 @@ public final class JsonParser {
} catch (JSONException e) {
errorMsg = e.getMessage();
}
+ } else if (token.equals(JsonToken.BOOLEAN)) {
+ output.put(fieldName, reader.nextBoolean());
} else {
reader.skipValue();
- errorMsg = "Unsupported value type.";
+ errorMsg = "Unsupported value type: " + token;
}
}
reader.endObject();
@@ -72,17 +72,36 @@ public final class JsonParser {
}
/**
- * Parses one string array from the {@link JsonReader}.
+ * Parses one JSON array from the {@link JsonReader}.
*/
- public static List<String> parseArray(JsonReader reader) throws IOException {
- ArrayList<String> output = new ArrayList<>();
+ public static JSONArray parseArray(JsonReader reader) throws IOException, JSONException {
+ JSONArray output = new JSONArray();
+ String errorMsg = null;
reader.beginArray();
while (reader.hasNext()) {
- output.add(reader.nextString());
+ JsonToken token = reader.peek();
+ if (token.equals(JsonToken.BEGIN_ARRAY)) {
+ output.put(parseArray(reader));
+ } else if (token.equals(JsonToken.STRING)) {
+ output.put(reader.nextString());
+ } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
+ try {
+ output.put(parse(reader));
+ } catch (JSONException e) {
+ errorMsg = e.getMessage();
+ }
+ } else {
+ reader.skipValue();
+ errorMsg = "Unsupported value type: " + token;
+ }
}
reader.endArray();
+ if (errorMsg != null) {
+ throw new JSONException(errorMsg);
+ }
+
return output;
}
}