diff options
author | 2024-11-19 16:10:11 -0800 | |
---|---|---|
committer | 2024-11-20 13:58:30 -0800 | |
commit | 92e74bc583f64e88e9fc3fdc675848fe84e093d3 (patch) | |
tree | dab43cfc9ccb8645d0d1ec01b8a2b1c65428ff8a | |
parent | 35e40bd5097471a439a402cf929fd96e4ae52665 (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
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; } } |