• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.adservices.service.customaudience;
18 
19 import android.adservices.common.AdTechIdentifier;
20 import android.annotation.NonNull;
21 import android.content.pm.PackageManager;
22 import android.net.Uri;
23 import android.util.Pair;
24 
25 import com.android.adservices.LoggerFactory;
26 import com.android.adservices.concurrency.AdServicesExecutors;
27 import com.android.adservices.data.adselection.AppInstallDao;
28 import com.android.adservices.data.customaudience.CustomAudienceDao;
29 import com.android.adservices.data.customaudience.CustomAudienceStats;
30 import com.android.adservices.data.customaudience.DBCustomAudienceBackgroundFetchData;
31 import com.android.adservices.data.enrollment.EnrollmentDao;
32 import com.android.adservices.service.Flags;
33 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
34 
35 import com.google.common.util.concurrent.FluentFuture;
36 
37 import java.io.IOException;
38 import java.time.Instant;
39 import java.util.Objects;
40 import java.util.concurrent.CancellationException;
41 
42 /** Runner executing actual background fetch tasks. */
43 public class BackgroundFetchRunner {
44     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
45     private final CustomAudienceDao mCustomAudienceDao;
46     private final AppInstallDao mAppInstallDao;
47     private final PackageManager mPackageManager;
48     private final EnrollmentDao mEnrollmentDao;
49     private final Flags mFlags;
50     private final AdServicesHttpsClient mHttpsClient;
51 
BackgroundFetchRunner( @onNull CustomAudienceDao customAudienceDao, @NonNull AppInstallDao appInstallDao, @NonNull PackageManager packageManager, @NonNull EnrollmentDao enrollmentDao, @NonNull Flags flags)52     public BackgroundFetchRunner(
53             @NonNull CustomAudienceDao customAudienceDao,
54             @NonNull AppInstallDao appInstallDao,
55             @NonNull PackageManager packageManager,
56             @NonNull EnrollmentDao enrollmentDao,
57             @NonNull Flags flags) {
58         Objects.requireNonNull(customAudienceDao);
59         Objects.requireNonNull(appInstallDao);
60         Objects.requireNonNull(packageManager);
61         Objects.requireNonNull(enrollmentDao);
62         Objects.requireNonNull(flags);
63         mCustomAudienceDao = customAudienceDao;
64         mAppInstallDao = appInstallDao;
65         mPackageManager = packageManager;
66         mEnrollmentDao = enrollmentDao;
67         mFlags = flags;
68         mHttpsClient =
69                 new AdServicesHttpsClient(
70                         AdServicesExecutors.getBlockingExecutor(),
71                         flags.getFledgeBackgroundFetchNetworkConnectTimeoutMs(),
72                         flags.getFledgeBackgroundFetchNetworkReadTimeoutMs(),
73                         flags.getFledgeBackgroundFetchMaxResponseSizeB());
74     }
75 
76     /**
77      * Deletes custom audiences whose expiration timestamps have passed.
78      *
79      * <p>Also clears corresponding update information from the background fetch DB.
80      */
deleteExpiredCustomAudiences(@onNull Instant jobStartTime)81     public void deleteExpiredCustomAudiences(@NonNull Instant jobStartTime) {
82         Objects.requireNonNull(jobStartTime);
83 
84         sLogger.d("Starting expired custom audience garbage collection");
85         int numCustomAudiencesDeleted =
86                 mCustomAudienceDao.deleteAllExpiredCustomAudienceData(jobStartTime);
87         sLogger.d("Deleted %d expired custom audiences", numCustomAudiencesDeleted);
88     }
89 
90     /**
91      * Deletes custom audiences whose owner applications which are not installed or in the app
92      * allowlist.
93      *
94      * <p>Also clears corresponding update information from the background fetch table.
95      */
deleteDisallowedOwnerCustomAudiences()96     public void deleteDisallowedOwnerCustomAudiences() {
97         sLogger.d("Starting custom audience disallowed owner garbage collection");
98         CustomAudienceStats deletedCAStats =
99                 mCustomAudienceDao.deleteAllDisallowedOwnerCustomAudienceData(
100                         mPackageManager, mFlags);
101         sLogger.d(
102                 "Deleted %d custom audiences belonging to %d disallowed owner apps",
103                 deletedCAStats.getTotalCustomAudienceCount(), deletedCAStats.getTotalOwnerCount());
104     }
105 
106     /**
107      * Deletes app install data whose packages are not installed or are not in the app allowlist.
108      */
deleteDisallowedPackageAppInstallEntries()109     public void deleteDisallowedPackageAppInstallEntries() {
110         sLogger.d("Starting app install disallowed package garbage collection");
111         int numDeleted = mAppInstallDao.deleteAllDisallowedPackageEntries(mPackageManager, mFlags);
112         sLogger.d("Deleted %d app install entries", numDeleted);
113     }
114 
115     /**
116      * Deletes custom audiences whose buyer ad techs which are not enrolled to use FLEDGE.
117      *
118      * <p>Also clears corresponding update information from the background fetch table.
119      */
deleteDisallowedBuyerCustomAudiences()120     public void deleteDisallowedBuyerCustomAudiences() {
121         sLogger.d("Starting custom audience disallowed buyer garbage collection");
122         CustomAudienceStats deletedCAStats =
123                 mCustomAudienceDao.deleteAllDisallowedBuyerCustomAudienceData(
124                         mEnrollmentDao, mFlags);
125         sLogger.d(
126                 "Deleted %d custom audiences belonging to %d disallowed buyer ad techs",
127                 deletedCAStats.getTotalCustomAudienceCount(), deletedCAStats.getTotalBuyerCount());
128     }
129 
130     /** Updates a single given custom audience and persists the results. */
updateCustomAudience( @onNull Instant jobStartTime, @NonNull final DBCustomAudienceBackgroundFetchData fetchData)131     public FluentFuture<?> updateCustomAudience(
132             @NonNull Instant jobStartTime,
133             @NonNull final DBCustomAudienceBackgroundFetchData fetchData) {
134         Objects.requireNonNull(jobStartTime);
135         Objects.requireNonNull(fetchData);
136 
137         return fetchAndValidateCustomAudienceUpdatableData(
138                         jobStartTime, fetchData.getBuyer(), fetchData.getDailyUpdateUri())
139                 .transform(
140                         updatableData -> {
141                             DBCustomAudienceBackgroundFetchData updatedData =
142                                     fetchData.copyWithUpdatableData(updatableData);
143 
144                             if (updatableData.getContainsSuccessfulUpdate()) {
145                                 mCustomAudienceDao.updateCustomAudienceAndBackgroundFetchData(
146                                         updatedData, updatableData);
147                             } else {
148                                 // In a failed update, we don't need to update the main CA table, so
149                                 // only update the background fetch table
150                                 mCustomAudienceDao.persistCustomAudienceBackgroundFetchData(
151                                         updatedData);
152                             }
153 
154                             return null;
155                         },
156                         AdServicesExecutors.getBackgroundExecutor());
157     }
158 
159     /**
160      * Fetches the custom audience update from the given daily update URI and validates the response
161      * in a {@link CustomAudienceUpdatableData} object.
162      */
163     @NonNull
fetchAndValidateCustomAudienceUpdatableData( @onNull Instant jobStartTime, @NonNull AdTechIdentifier buyer, @NonNull Uri dailyFetchUri)164     public FluentFuture<CustomAudienceUpdatableData> fetchAndValidateCustomAudienceUpdatableData(
165             @NonNull Instant jobStartTime,
166             @NonNull AdTechIdentifier buyer,
167             @NonNull Uri dailyFetchUri) {
168         Objects.requireNonNull(jobStartTime);
169         Objects.requireNonNull(buyer);
170         Objects.requireNonNull(dailyFetchUri);
171 
172         // TODO(b/234884352): Perform k-anon check on daily fetch URI
173         return FluentFuture.from(mHttpsClient.fetchPayload(dailyFetchUri))
174                 .transform(
175                         updateResponse ->
176                                 Pair.create(
177                                         UpdateResultType.SUCCESS, updateResponse.getResponseBody()),
178                         AdServicesExecutors.getBackgroundExecutor())
179                 .catching(
180                         Throwable.class,
181                         t -> handleThrowable(t, dailyFetchUri),
182                         AdServicesExecutors.getBackgroundExecutor())
183                 .transform(
184                         fetchResultAndResponse ->
185                                 CustomAudienceUpdatableData.createFromResponseString(
186                                         jobStartTime,
187                                         buyer,
188                                         fetchResultAndResponse.first,
189                                         fetchResultAndResponse.second,
190                                         mFlags),
191                         AdServicesExecutors.getBackgroundExecutor());
192     }
193 
handleThrowable( Throwable t, @NonNull Uri dailyFetchUri)194     private Pair<UpdateResultType, String> handleThrowable(
195             Throwable t, @NonNull Uri dailyFetchUri) {
196         if (t instanceof IOException) {
197             // TODO(b/237342352): Investigate separating connect and read timeouts
198             sLogger.e(
199                     t,
200                     "Timed out while fetching custom audience update from %s",
201                     dailyFetchUri.toSafeString());
202             return Pair.create(UpdateResultType.NETWORK_FAILURE, "{}");
203         }
204         if (t instanceof CancellationException) {
205             sLogger.e(
206                     t,
207                     "Custom audience update cancelled while fetching from %s",
208                     dailyFetchUri.toSafeString());
209             return Pair.create(UpdateResultType.UNKNOWN, "{}");
210         }
211 
212         sLogger.e(
213                 t,
214                 "Encountered unexpected error while fetching custom audience update from" + " %s",
215                 dailyFetchUri.toSafeString());
216         return Pair.create(UpdateResultType.UNKNOWN, "{}");
217     }
218 
219     /** Represents the result of an update attempt prior to parsing the update response. */
220     public enum UpdateResultType {
221         SUCCESS,
222         UNKNOWN,
223         K_ANON_FAILURE,
224         // TODO(b/237342352): Consolidate if we don't need to distinguish network timeouts
225         NETWORK_FAILURE,
226         NETWORK_READ_TIMEOUT_FAILURE,
227         RESPONSE_VALIDATION_FAILURE
228     }
229 }
230