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