summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author William Loh <wloh@google.com> 2024-11-22 15:26:59 -0800
committer William Loh <wloh@google.com> 2024-11-22 17:33:46 -0800
commit785dec24741e01ba8df15945514fd3e581593bf8 (patch)
tree1acb126fef4580372d731bafe550a40b4bb3bf75
parent2883526fde1414236d8fa49974de014f05d344fc (diff)
Add a periodic update worker for verified domains
This verified domain update worker is only meant to be a reference implementation is hardcoded to be disabled. Instead of enabling this worker, it is highly recommended to implement a custom worker that pulls updates form a caching service and not from websites directly. Bug: 307557449 Test: presubmit Flag: EXEMPT external library Change-Id: If7dbb1617e4add67c581ef0cc301f8ad978b461e
-rw-r--r--packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt1
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt39
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt8
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt17
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt94
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt69
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt29
7 files changed, 185 insertions, 72 deletions
diff --git a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
index 021a5143c09a..6af8004c4015 100644
--- a/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
+++ b/packages/StatementService/src/com/android/statementservice/StatementServiceApplication.kt
@@ -30,6 +30,7 @@ class StatementServiceApplication : Application() {
// WorkManager can only schedule when the user data directories are unencrypted (after
// the user has entered their lock password.
DomainVerificationUtils.schedulePeriodicCheckUnlocked(WorkManager.getInstance(this))
+ DomainVerificationUtils.schedulePeriodicUpdateUnlocked(WorkManager.getInstance(this))
}
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
index 694424822b30..157a800d0e09 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationUtils.kt
@@ -22,6 +22,7 @@ import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.android.statementservice.domain.worker.RetryRequestWorker
+import com.android.statementservice.domain.worker.UpdateVerifiedDomainsWorker
import java.time.Duration
object DomainVerificationUtils {
@@ -30,6 +31,10 @@ object DomainVerificationUtils {
private const val PERIODIC_SHORT_HOURS = 24L
private const val PERIODIC_LONG_ID = "retry_long"
private const val PERIODIC_LONG_HOURS = 72L
+ private const val PERIODIC_UPDATE_ID = "update"
+ private const val PERIODIC_UPDATE_HOURS = 720L
+
+ private const val UPDATE_WORKER_ENABLED = false
/**
* In a majority of cases, the initial requests will be enough to verify domains, since they
@@ -74,4 +79,38 @@ object DomainVerificationUtils {
}
}
}
+
+ /**
+ * Schedule a periodic worker to check for any updates to assetlink.json files for domains that
+ * have already been verified.
+ *
+ * Due to the potential for this worker to generate enough traffic across all android devices
+ * to overwhelm websites, this method is hardcoded to be disabled by default. It is highly
+ * recommended to not enable this worker and instead implement a custom worker that pulls
+ * updates from a caching service instead of directly from websites.
+ */
+ fun schedulePeriodicUpdateUnlocked(workManager: WorkManager) {
+ if (UPDATE_WORKER_ENABLED) {
+ workManager.apply {
+ PeriodicWorkRequestBuilder<UpdateVerifiedDomainsWorker>(
+ Duration.ofDays(
+ PERIODIC_UPDATE_HOURS
+ )
+ )
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .setRequiresDeviceIdle(true)
+ .build()
+ )
+ .build()
+ .let {
+ enqueueUniquePeriodicWork(
+ PERIODIC_UPDATE_ID,
+ ExistingPeriodicWorkPolicy.KEEP, it
+ )
+ }
+ }
+ }
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 6914347544de..c7f6c184fd22 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -64,7 +64,8 @@ class DomainVerifier private constructor(
private val targetAssetCache = AssetLruCache()
- fun collectHosts(packageNames: Iterable<String>): Iterable<Triple<UUID, String, String>> {
+ fun collectHosts(packageNames: Iterable<String>, statusFilter: (Int) -> Boolean):
+ Iterable<Triple<UUID, String, Iterable<String>>> {
return packageNames.mapNotNull { packageName ->
val (domainSetId, _, hostToStateMap) = try {
manager.getDomainVerificationInfo(packageName)
@@ -74,14 +75,13 @@ class DomainVerifier private constructor(
} ?: return@mapNotNull null
val hostsToRetry = hostToStateMap
- .filterValues(VerifyStatus::shouldRetry)
+ .filterValues(statusFilter)
.takeIf { it.isNotEmpty() }
?.map { it.key }
?: return@mapNotNull null
- hostsToRetry.map { Triple(domainSetId, packageName, it) }
+ Triple(domainSetId, packageName, hostsToRetry)
}
- .flatten()
}
suspend fun verifyHost(
diff --git a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
index 2193ec542238..c771da3ab563 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/VerifyStatus.kt
@@ -49,7 +49,7 @@ enum class VerifyStatus(val value: Int) {
return false
}
- val status = values().find { it.value == state } ?: return true
+ val status = entries.find { it.value == state } ?: return true
return when (status) {
SUCCESS,
FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
@@ -62,5 +62,20 @@ enum class VerifyStatus(val value: Int) {
FAILURE_REDIRECT -> true
}
}
+
+ fun canUpdate(state: Int): Boolean {
+ if (state == DomainVerificationInfo.STATE_UNMODIFIABLE) {
+ return false
+ }
+
+ val status = entries.find { it.value == state }
+ return when (status) {
+ SUCCESS,
+ FAILURE_LEGACY_UNSUPPORTED_WILDCARD,
+ FAILURE_REJECTED_BY_SERVER,
+ UNKNOWN -> true
+ else -> false
+ }
+ }
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt
new file mode 100644
index 000000000000..7ec6e6c28e9b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/PeriodicUpdateWorker.kt
@@ -0,0 +1,94 @@
+/*
+ * 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 android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
+import androidx.work.ListenableWorker
+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
+import kotlinx.coroutines.isActive
+
+abstract class PeriodicUpdateWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : BaseRequestWorker(appContext, params) {
+
+ data class VerifyResult(
+ val host: String,
+ val status: VerifyStatus,
+ val groups: List<UriRelativeFilterGroup>
+ )
+
+ protected suspend fun updateDomainVerificationStatus(verifyStatusFilter: (Int) -> Boolean):
+ ListenableWorker.Result {
+ return coroutineScope {
+ if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
+ return@coroutineScope Result.success()
+ }
+
+ val packageNames = verificationManager.queryValidVerificationPackageNames()
+
+ verifier.collectHosts(packageNames, verifyStatusFilter)
+ .map { (domainSetId, packageName, hosts) ->
+ hosts.map { host ->
+ async {
+ if (isActive && !isStopped) {
+ val (_, status, statement) = verifier.verifyHost(
+ host,
+ packageName,
+ params.network
+ )
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ VerifyResult(host, status, 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.
+ null
+ }
+ }
+ }.awaitAll().filterNotNull().groupBy { it.status }
+ .forEach { (status, results) ->
+ val error = verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ results.map { it.host }.toSet(),
+ status.value
+ )
+ if (error == DomainVerificationManager.STATUS_OK
+ && status == VerifyStatus.SUCCESS
+ ) {
+ updateUriRelativeFilterGroups(
+ packageName,
+ results.associateBy({ it.host }, { it.groups })
+ )
+ }
+ }
+ }
+
+ // Succeed regardless of results since this retry is best effort and not required
+ Result.success()
+ }
+ }
+} \ 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 f83601a7807b..e8b4df9b0943 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -17,18 +17,9 @@
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
-import kotlinx.coroutines.isActive
-import java.util.UUID
/**
* Scheduled every 24 hours with [NetworkType.CONNECTED] and every 72 hours without any constraints
@@ -37,63 +28,7 @@ import java.util.UUID
class RetryRequestWorker(
appContext: Context,
params: WorkerParameters
-) : BaseRequestWorker(appContext, params) {
+) : PeriodicUpdateWorker(appContext, params) {
- 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)) {
- return@coroutineScope Result.success()
- }
-
- val packageNames = verificationManager.queryValidVerificationPackageNames()
-
- verifier.collectHosts(packageNames)
- .map { (domainSetId, packageName, host) ->
- async {
- if (isActive && !isStopped) {
- 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.
- null
- }
- }
- }
- .awaitAll()
- .filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
- .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
- Result.success()
- }
+ override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::shouldRetry)
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
new file mode 100644
index 000000000000..c6f40c832860
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/UpdateVerifiedDomainsWorker.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.WorkerParameters
+import com.android.statementservice.domain.VerifyStatus
+
+class UpdateVerifiedDomainsWorker(
+ appContext: Context,
+ params: WorkerParameters
+) : PeriodicUpdateWorker(appContext, params) {
+
+ override suspend fun doWork() = updateDomainVerificationStatus(VerifyStatus::canUpdate)
+} \ No newline at end of file