1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.statementservice.domain.worker
18 
19 import android.content.Context
20 import android.content.UriRelativeFilterGroup
21 import android.content.pm.verify.domain.DomainVerificationManager
22 import androidx.work.ListenableWorker
23 import androidx.work.WorkerParameters
24 import com.android.statementservice.domain.VerifyStatus
25 import com.android.statementservice.utils.AndroidUtils
26 import com.android.statementservice.utils.StatementUtils
27 import kotlinx.coroutines.async
28 import kotlinx.coroutines.awaitAll
29 import kotlinx.coroutines.coroutineScope
30 import kotlinx.coroutines.isActive
31 
32 abstract class PeriodicUpdateWorker(
33     appContext: Context,
34     params: WorkerParameters
35 ) : BaseRequestWorker(appContext, params) {
36 
37     data class VerifyResult(
38         val host: String,
39         val status: VerifyStatus,
40         val groups: List<UriRelativeFilterGroup>
41     )
42 
43     protected suspend fun updateDomainVerificationStatus(verifyStatusFilter: (Int) -> Boolean):
44             ListenableWorker.Result {
45         return coroutineScope {
46             if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
47                 return@coroutineScope Result.success()
48             }
49 
50             val packageNames = verificationManager.queryValidVerificationPackageNames()
51 
52             verifier.collectHosts(packageNames, verifyStatusFilter)
53                 .map { (domainSetId, packageName, hosts) ->
54                     hosts.map { host ->
55                         async {
56                             if (isActive && !isStopped) {
57                                 val (_, status, statement) = verifier.verifyHost(
58                                     host,
59                                     packageName,
60                                     params.network
61                                 )
62                                 val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
63                                     StatementUtils.createUriRelativeFilterGroup(it)
64                                 }
65                                 VerifyResult(host, status, groups)
66                             } else {
67                                 // If the job gets cancelled, stop the remaining hosts, but continue the
68                                 // job to commit the results for hosts that were already requested.
69                                 null
70                             }
71                         }
72                     }.awaitAll().filterNotNull().groupBy { it.status }
73                         .forEach { (status, results) ->
74                             val error = verificationManager.setDomainVerificationStatus(
75                                 domainSetId,
76                                 results.map { it.host }.toSet(),
77                                 status.value
78                             )
79                             if (error == DomainVerificationManager.STATUS_OK
80                                 && status == VerifyStatus.SUCCESS
81                             ) {
82                                 updateUriRelativeFilterGroups(
83                                     packageName,
84                                     results.associateBy({ it.host }, { it.groups })
85                                 )
86                             }
87                         }
88                 }
89 
90             // Succeed regardless of results since this retry is best effort and not required
91             Result.success()
92         }
93     }
94 }