diff options
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; } } |