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
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 90e1808..39b0302 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -38,8 +38,10 @@
"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 0000000..21ecc8b
--- /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 0000000..c616669
--- /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 0000000..3b4dcea
--- /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 0000000..39833f6
--- /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 acb54f6..0d7a1fd 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 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 @@
}
}
- //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 @@
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 29f844f..6914347 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 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 @@
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 @@
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 a17f9c9..64d2d98 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 @@
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 0000000..f53dfc4
--- /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 61ab2c2..f83601a 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 @@
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 @@
.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 @@
}
.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 7a198cb..253a162 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.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 @@
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 @@
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 562b132..8b1347a 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.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 @@
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 ad137400..d10cb0f 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 @@
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 @@
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 @@
}
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 dc27e12..c32f194 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 @@
@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 7635e82..ab1853c 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.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 @@
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 @@
} 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 @@
}
/**
- * 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;
}
}