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.adselection; 18 19 import static com.android.adservices.service.stats.AdServicesLoggerUtil.getResultCodeFromException; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS; 21 22 import android.adservices.adselection.AdSelectionCallback; 23 import android.adservices.adselection.AdSelectionConfig; 24 import android.adservices.adselection.AdSelectionInput; 25 import android.adservices.adselection.AdSelectionResponse; 26 import android.adservices.adselection.ContextualAds; 27 import android.adservices.common.AdServicesStatusUtils; 28 import android.adservices.common.AdTechIdentifier; 29 import android.adservices.common.FledgeErrorResponse; 30 import android.adservices.exceptions.AdServicesException; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.content.Context; 34 import android.net.Uri; 35 import android.os.Build; 36 import android.os.RemoteException; 37 import android.os.Trace; 38 39 import androidx.annotation.RequiresApi; 40 41 import com.android.adservices.LoggerFactory; 42 import com.android.adservices.data.adselection.AdSelectionEntryDao; 43 import com.android.adservices.data.adselection.DBAdSelection; 44 import com.android.adservices.data.adselection.DBBuyerDecisionLogic; 45 import com.android.adservices.data.customaudience.CustomAudienceDao; 46 import com.android.adservices.data.customaudience.DBCustomAudience; 47 import com.android.adservices.service.Flags; 48 import com.android.adservices.service.common.AdSelectionServiceFilter; 49 import com.android.adservices.service.common.Throttler; 50 import com.android.adservices.service.consent.ConsentManager; 51 import com.android.adservices.service.exception.FilterException; 52 import com.android.adservices.service.js.JSScriptEngine; 53 import com.android.adservices.service.profiling.Tracing; 54 import com.android.adservices.service.stats.AdSelectionExecutionLogger; 55 import com.android.adservices.service.stats.AdServicesLogger; 56 import com.android.adservices.service.stats.AdServicesLoggerUtil; 57 import com.android.adservices.service.stats.AdServicesStatsLog; 58 import com.android.internal.annotations.VisibleForTesting; 59 60 import com.google.common.base.Preconditions; 61 import com.google.common.util.concurrent.AsyncFunction; 62 import com.google.common.util.concurrent.FluentFuture; 63 import com.google.common.util.concurrent.FutureCallback; 64 import com.google.common.util.concurrent.Futures; 65 import com.google.common.util.concurrent.ListenableFuture; 66 import com.google.common.util.concurrent.ListeningExecutorService; 67 import com.google.common.util.concurrent.MoreExecutors; 68 import com.google.common.util.concurrent.UncheckedTimeoutException; 69 70 import java.time.Clock; 71 import java.util.Collections; 72 import java.util.HashMap; 73 import java.util.List; 74 import java.util.Map; 75 import java.util.Objects; 76 import java.util.concurrent.ExecutorService; 77 import java.util.concurrent.ScheduledThreadPoolExecutor; 78 import java.util.concurrent.TimeUnit; 79 import java.util.concurrent.TimeoutException; 80 import java.util.stream.Collectors; 81 82 /** 83 * Orchestrator that runs the Ads Auction/Bidding and Scoring logic The class expects the caller to 84 * create a concrete object instance of the class. The instances are mutually exclusive and do not 85 * share any values across shared class instance. 86 * 87 * <p>Class takes in an executor on which it runs the AdSelection logic 88 */ 89 // TODO(b/269798827): Enable for R. 90 @RequiresApi(Build.VERSION_CODES.S) 91 public abstract class AdSelectionRunner { 92 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 93 94 @VisibleForTesting static final String AD_SELECTION_ERROR_PATTERN = "%s: %s"; 95 96 @VisibleForTesting 97 static final String ERROR_AD_SELECTION_FAILURE = "Encountered failure during Ad Selection"; 98 99 @VisibleForTesting static final String ERROR_NO_WINNING_AD_FOUND = "No winning Ads found"; 100 101 @VisibleForTesting 102 static final String ERROR_NO_VALID_BIDS_OR_CONTEXTUAL_ADS_FOR_SCORING = 103 "No valid bids or contextual ads available for scoring"; 104 105 @VisibleForTesting 106 static final String ERROR_NO_CA_AND_CONTEXTUAL_ADS_AVAILABLE = 107 "No Custom Audience or contextual ads available"; 108 109 @VisibleForTesting 110 static final String ERROR_NO_BUYERS_OR_CONTEXTUAL_ADS_AVAILABLE = 111 "The list of the custom audience buyers and contextual ads both should not be empty."; 112 113 @VisibleForTesting 114 static final String AD_SELECTION_TIMED_OUT = "Ad selection exceeded allowed time limit"; 115 116 @VisibleForTesting 117 static final String JS_SANDBOX_IS_NOT_AVAILABLE = 118 String.format( 119 AD_SELECTION_ERROR_PATTERN, 120 ERROR_AD_SELECTION_FAILURE, 121 "JS Sandbox is not available"); 122 123 @NonNull protected final CustomAudienceDao mCustomAudienceDao; 124 @NonNull protected final AdSelectionEntryDao mAdSelectionEntryDao; 125 @NonNull protected final ListeningExecutorService mLightweightExecutorService; 126 @NonNull protected final ListeningExecutorService mBackgroundExecutorService; 127 @NonNull protected final ScheduledThreadPoolExecutor mScheduledExecutor; 128 @NonNull protected final AdSelectionIdGenerator mAdSelectionIdGenerator; 129 @NonNull protected final Clock mClock; 130 @NonNull protected final AdServicesLogger mAdServicesLogger; 131 @NonNull protected final Flags mFlags; 132 @NonNull protected final AdSelectionExecutionLogger mAdSelectionExecutionLogger; 133 @NonNull private final AdSelectionServiceFilter mAdSelectionServiceFilter; 134 @NonNull private final AdFilterer mAdFilterer; 135 private final int mCallerUid; 136 @NonNull private final PrebuiltLogicGenerator mPrebuiltLogicGenerator; 137 138 /** 139 * @param context service context 140 * @param customAudienceDao DAO to access custom audience storage 141 * @param adSelectionEntryDao DAO to access ad selection storage 142 * @param lightweightExecutorService executor for running short tasks 143 * @param backgroundExecutorService executor for longer running tasks (ex. network calls) 144 * @param scheduledExecutor executor for tasks to be run with a delay or timed executions 145 * @param adServicesLogger logger for logging calls to PPAPI 146 * @param flags for accessing feature flags 147 * @param adSelectionServiceFilter for validating the request 148 */ AdSelectionRunner( @onNull final Context context, @NonNull final CustomAudienceDao customAudienceDao, @NonNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final AdServicesLogger adServicesLogger, @NonNull final Flags flags, @NonNull final AdSelectionExecutionLogger adSelectionExecutionLogger, @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, @NonNull final AdFilterer adFilterer, @NonNull final int callerUid)149 public AdSelectionRunner( 150 @NonNull final Context context, 151 @NonNull final CustomAudienceDao customAudienceDao, 152 @NonNull final AdSelectionEntryDao adSelectionEntryDao, 153 @NonNull final ExecutorService lightweightExecutorService, 154 @NonNull final ExecutorService backgroundExecutorService, 155 @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, 156 @NonNull final AdServicesLogger adServicesLogger, 157 @NonNull final Flags flags, 158 @NonNull final AdSelectionExecutionLogger adSelectionExecutionLogger, 159 @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, 160 @NonNull final AdFilterer adFilterer, 161 @NonNull final int callerUid) { 162 Objects.requireNonNull(context); 163 Objects.requireNonNull(customAudienceDao); 164 Objects.requireNonNull(adSelectionEntryDao); 165 Objects.requireNonNull(lightweightExecutorService); 166 Objects.requireNonNull(backgroundExecutorService); 167 Objects.requireNonNull(adServicesLogger); 168 Objects.requireNonNull(flags); 169 Objects.requireNonNull(adSelectionServiceFilter); 170 Objects.requireNonNull(adFilterer); 171 172 Preconditions.checkArgument( 173 JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable(), 174 JS_SANDBOX_IS_NOT_AVAILABLE); 175 Objects.requireNonNull(adSelectionExecutionLogger); 176 177 mCustomAudienceDao = customAudienceDao; 178 mAdSelectionEntryDao = adSelectionEntryDao; 179 mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); 180 mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); 181 mScheduledExecutor = scheduledExecutor; 182 mAdServicesLogger = adServicesLogger; 183 mAdSelectionIdGenerator = new AdSelectionIdGenerator(); 184 mClock = Clock.systemUTC(); 185 mFlags = flags; 186 mAdSelectionExecutionLogger = adSelectionExecutionLogger; 187 mAdSelectionServiceFilter = adSelectionServiceFilter; 188 mAdFilterer = adFilterer; 189 mCallerUid = callerUid; 190 mPrebuiltLogicGenerator = new PrebuiltLogicGenerator(mFlags); 191 } 192 193 @VisibleForTesting AdSelectionRunner( @onNull final Context context, @NonNull final CustomAudienceDao customAudienceDao, @NonNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final AdSelectionIdGenerator adSelectionIdGenerator, @NonNull Clock clock, @NonNull final AdServicesLogger adServicesLogger, @NonNull final Flags flags, int callerUid, @NonNull AdSelectionServiceFilter adSelectionServiceFilter, @NonNull AdFilterer adFilterer, @NonNull final AdSelectionExecutionLogger adSelectionExecutionLogger)194 AdSelectionRunner( 195 @NonNull final Context context, 196 @NonNull final CustomAudienceDao customAudienceDao, 197 @NonNull final AdSelectionEntryDao adSelectionEntryDao, 198 @NonNull final ExecutorService lightweightExecutorService, 199 @NonNull final ExecutorService backgroundExecutorService, 200 @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, 201 @NonNull final AdSelectionIdGenerator adSelectionIdGenerator, 202 @NonNull Clock clock, 203 @NonNull final AdServicesLogger adServicesLogger, 204 @NonNull final Flags flags, 205 int callerUid, 206 @NonNull AdSelectionServiceFilter adSelectionServiceFilter, 207 @NonNull AdFilterer adFilterer, 208 @NonNull final AdSelectionExecutionLogger adSelectionExecutionLogger) { 209 Objects.requireNonNull(context); 210 Objects.requireNonNull(customAudienceDao); 211 Objects.requireNonNull(adSelectionEntryDao); 212 Objects.requireNonNull(lightweightExecutorService); 213 Objects.requireNonNull(backgroundExecutorService); 214 Objects.requireNonNull(scheduledExecutor); 215 Objects.requireNonNull(adSelectionIdGenerator); 216 Objects.requireNonNull(clock); 217 Objects.requireNonNull(adServicesLogger); 218 Objects.requireNonNull(flags); 219 Objects.requireNonNull(adSelectionExecutionLogger); 220 Objects.requireNonNull(adFilterer); 221 222 Preconditions.checkArgument( 223 JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable(), 224 JS_SANDBOX_IS_NOT_AVAILABLE); 225 226 mCustomAudienceDao = customAudienceDao; 227 mAdSelectionEntryDao = adSelectionEntryDao; 228 mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); 229 mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); 230 mScheduledExecutor = scheduledExecutor; 231 mAdSelectionIdGenerator = adSelectionIdGenerator; 232 mClock = clock; 233 mAdServicesLogger = adServicesLogger; 234 mFlags = flags; 235 mAdSelectionExecutionLogger = adSelectionExecutionLogger; 236 mAdSelectionServiceFilter = adSelectionServiceFilter; 237 mAdFilterer = adFilterer; 238 mCallerUid = callerUid; 239 mPrebuiltLogicGenerator = new PrebuiltLogicGenerator(mFlags); 240 } 241 242 /** 243 * Runs the ad selection for a given seller 244 * 245 * @param inputParams containing {@link AdSelectionConfig} and {@code callerPackageName} 246 * @param callback used to notify the result back to the calling seller 247 */ runAdSelection( @onNull AdSelectionInput inputParams, @NonNull AdSelectionCallback callback)248 public void runAdSelection( 249 @NonNull AdSelectionInput inputParams, @NonNull AdSelectionCallback callback) { 250 final int traceCookie = Tracing.beginAsyncSection(Tracing.RUN_AD_SELECTION); 251 Objects.requireNonNull(inputParams); 252 Objects.requireNonNull(callback); 253 AdSelectionConfig adSelectionConfig = inputParams.getAdSelectionConfig(); 254 255 try { 256 ListenableFuture<Void> filterAndValidateRequestFuture = 257 Futures.submit( 258 () -> { 259 try { 260 Trace.beginSection(Tracing.VALIDATE_REQUEST); 261 sLogger.v("Starting filtering and validation."); 262 mAdSelectionServiceFilter.filterRequest( 263 adSelectionConfig.getSeller(), 264 inputParams.getCallerPackageName(), 265 mFlags 266 .getEnforceForegroundStatusForFledgeRunAdSelection(), 267 true, 268 mCallerUid, 269 AdServicesStatsLog 270 .AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, 271 Throttler.ApiKey.FLEDGE_API_SELECT_ADS); 272 validateAdSelectionConfig(adSelectionConfig); 273 } finally { 274 sLogger.v("Completed filtering and validation."); 275 Trace.endSection(); 276 } 277 }, 278 mLightweightExecutorService); 279 280 ListenableFuture<DBAdSelection> dbAdSelectionFuture = 281 FluentFuture.from(filterAndValidateRequestFuture) 282 .transformAsync( 283 ignoredVoid -> 284 orchestrateAdSelection( 285 inputParams.getAdSelectionConfig(), 286 inputParams.getCallerPackageName()), 287 mLightweightExecutorService) 288 .transform( 289 this::closeSuccessfulAdSelection, mLightweightExecutorService) 290 .catching( 291 RuntimeException.class, 292 this::closeFailedAdSelectionWithRuntimeException, 293 mLightweightExecutorService) 294 .catching( 295 AdServicesException.class, 296 this::closeFailedAdSelectionWithAdServicesException, 297 mLightweightExecutorService); 298 299 Futures.addCallback( 300 dbAdSelectionFuture, 301 new FutureCallback<DBAdSelection>() { 302 @Override 303 public void onSuccess(DBAdSelection result) { 304 Tracing.endAsyncSection(Tracing.RUN_AD_SELECTION, traceCookie); 305 notifySuccessToCaller(result, callback); 306 } 307 308 @Override 309 public void onFailure(Throwable t) { 310 Tracing.endAsyncSection(Tracing.RUN_AD_SELECTION, traceCookie); 311 if (t instanceof FilterException 312 && t.getCause() 313 instanceof ConsentManager.RevokedConsentException) { 314 // Skip logging if a FilterException occurs. 315 // AdSelectionServiceFilter ensures the failing assertion is logged 316 // internally. 317 318 // Fail Silently by notifying success to caller 319 notifyEmptySuccessToCaller(callback); 320 } else { 321 if (t.getCause() instanceof AdServicesException) { 322 notifyFailureToCaller(callback, t.getCause()); 323 } else { 324 notifyFailureToCaller(callback, t); 325 } 326 } 327 } 328 }, 329 mLightweightExecutorService); 330 } catch (Throwable t) { 331 Tracing.endAsyncSection(Tracing.RUN_AD_SELECTION, traceCookie); 332 sLogger.v("run ad selection fails fast with exception %s.", t.toString()); 333 notifyFailureToCaller(callback, t); 334 } 335 } 336 337 @Nullable closeFailedAdSelectionWithRuntimeException(RuntimeException e)338 private DBAdSelection closeFailedAdSelectionWithRuntimeException(RuntimeException e) { 339 sLogger.v("Close failed ad selection and rethrow the RuntimeException %s.", e.toString()); 340 int resultCode = AdServicesLoggerUtil.getResultCodeFromException(e); 341 mAdSelectionExecutionLogger.close(resultCode); 342 throw e; 343 } 344 345 @Nullable closeFailedAdSelectionWithAdServicesException(AdServicesException e)346 private DBAdSelection closeFailedAdSelectionWithAdServicesException(AdServicesException e) { 347 int resultCode = AdServicesLoggerUtil.getResultCodeFromException(e); 348 mAdSelectionExecutionLogger.close(resultCode); 349 sLogger.v( 350 "Close failed ad selection and wrap the AdServicesException with" 351 + " an RuntimeException with message: %s and log with resultCode : %d", 352 e.getMessage(), resultCode); 353 throw new RuntimeException(e.getMessage(), e.getCause()); 354 } 355 356 @NonNull closeSuccessfulAdSelection(@onNull DBAdSelection dbAdSelection)357 private DBAdSelection closeSuccessfulAdSelection(@NonNull DBAdSelection dbAdSelection) { 358 mAdSelectionExecutionLogger.close(AdServicesStatusUtils.STATUS_SUCCESS); 359 return dbAdSelection; 360 } 361 notifySuccessToCaller( @onNull DBAdSelection result, @NonNull AdSelectionCallback callback)362 private void notifySuccessToCaller( 363 @NonNull DBAdSelection result, @NonNull AdSelectionCallback callback) { 364 try { 365 int overallLatencyMs = 366 mAdSelectionExecutionLogger.getRunAdSelectionOverallLatencyInMs(); 367 sLogger.v( 368 "Ad Selection with Id:%d completed with overall latency %d in ms, " 369 + "attempted notifying success", 370 result.getAdSelectionId(), overallLatencyMs); 371 // TODO(b//253522566): When including logging data from bidding & auction server side 372 // should be able to differentiate the data from the on-device telemetry. 373 // Note: Success is logged before the callback to ensure deterministic testing. 374 mAdServicesLogger.logFledgeApiCallStats( 375 AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, 376 AdServicesStatusUtils.STATUS_SUCCESS, 377 overallLatencyMs); 378 379 callback.onSuccess( 380 new AdSelectionResponse.Builder() 381 .setAdSelectionId(result.getAdSelectionId()) 382 .setRenderUri(result.getWinningAdRenderUri()) 383 .build()); 384 } catch (RemoteException e) { 385 sLogger.e(e, "Encountered exception during notifying AdSelection callback"); 386 } 387 } 388 389 /** Sends a successful response to the caller that represents a silent failure. */ notifyEmptySuccessToCaller(@onNull AdSelectionCallback callback)390 private void notifyEmptySuccessToCaller(@NonNull AdSelectionCallback callback) { 391 try { 392 callback.onSuccess( 393 new AdSelectionResponse.Builder() 394 .setAdSelectionId(mAdSelectionIdGenerator.generateId()) 395 .setRenderUri(Uri.EMPTY) 396 .build()); 397 } catch (RemoteException e) { 398 sLogger.e(e, "Encountered exception during notifying AdSelection callback"); 399 } 400 } 401 notifyFailureToCaller( @onNull AdSelectionCallback callback, @NonNull Throwable t)402 private void notifyFailureToCaller( 403 @NonNull AdSelectionCallback callback, @NonNull Throwable t) { 404 try { 405 sLogger.e(t, "Ad Selection failure: "); 406 407 int resultCode = AdServicesLoggerUtil.getResultCodeFromException(t); 408 409 // Skip logging if a FilterException occurs. 410 // AdSelectionServiceFilter ensures the failing assertion is logged internally. 411 // Note: Failure is logged before the callback to ensure deterministic testing. 412 if (!(t instanceof FilterException)) { 413 int overallLatencyMs = 414 mAdSelectionExecutionLogger.getRunAdSelectionOverallLatencyInMs(); 415 sLogger.v("Ad Selection failed with overall latency %d in ms", overallLatencyMs); 416 // TODO(b//253522566): When including logging data from bidding & auction server 417 // side 418 // should be able to differentiate the data from the on-device telemetry. 419 mAdServicesLogger.logFledgeApiCallStats( 420 AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs); 421 } 422 423 FledgeErrorResponse selectionFailureResponse = 424 new FledgeErrorResponse.Builder() 425 .setErrorMessage( 426 String.format( 427 AD_SELECTION_ERROR_PATTERN, 428 ERROR_AD_SELECTION_FAILURE, 429 t.getMessage())) 430 .setStatusCode(resultCode) 431 .build(); 432 callback.onFailure(selectionFailureResponse); 433 } catch (RemoteException e) { 434 sLogger.e(e, "Encountered exception during notifying AdSelection callback"); 435 } 436 } 437 438 /** 439 * Overall moderator for running Ad Selection 440 * 441 * @param adSelectionConfig Set of data from Sellers and Buyers needed for Ad Auction and 442 * Selection 443 * @return {@link AdSelectionResponse} 444 */ orchestrateAdSelection( @onNull final AdSelectionConfig adSelectionConfig, @NonNull final String callerPackageName)445 private ListenableFuture<DBAdSelection> orchestrateAdSelection( 446 @NonNull final AdSelectionConfig adSelectionConfig, 447 @NonNull final String callerPackageName) { 448 sLogger.v("Beginning Ad Selection Orchestration"); 449 450 AdSelectionConfig adSelectionConfigInput = adSelectionConfig; 451 if (!mFlags.getFledgeAdSelectionContextualAdsEnabled()) { 452 // Empty all contextual ads if the feature is disabled 453 sLogger.v("Contextual flow is disabled"); 454 adSelectionConfigInput = getAdSelectionConfigWithoutContextualAds(adSelectionConfig); 455 } else { 456 sLogger.v("Contextual flow is enabled, filtering contextual ads"); 457 adSelectionConfigInput = getAdSelectionConfigFilterContextualAds(adSelectionConfig); 458 } 459 460 ListenableFuture<List<DBCustomAudience>> buyerCustomAudience = 461 getBuyersCustomAudience(adSelectionConfigInput); 462 ListenableFuture<AdSelectionOrchestrationResult> dbAdSelection = 463 orchestrateAdSelection( 464 adSelectionConfigInput, callerPackageName, buyerCustomAudience); 465 466 AsyncFunction<AdSelectionOrchestrationResult, DBAdSelection> saveResultToPersistence = 467 adSelectionAndJs -> 468 persistAdSelection( 469 adSelectionAndJs.mDbAdSelectionBuilder, 470 adSelectionAndJs.mBuyerDecisionLogicJs, 471 callerPackageName); 472 473 return FluentFuture.from(dbAdSelection) 474 .transformAsync(saveResultToPersistence, mLightweightExecutorService) 475 .withTimeout( 476 mFlags.getAdSelectionOverallTimeoutMs(), 477 TimeUnit.MILLISECONDS, 478 mScheduledExecutor) 479 .catching( 480 TimeoutException.class, 481 this::handleTimeoutError, 482 mLightweightExecutorService); 483 } 484 orchestrateAdSelection( @onNull AdSelectionConfig adSelectionConfig, @NonNull String callerPackageName, @NonNull ListenableFuture<List<DBCustomAudience>> buyerCustomAudience)485 abstract ListenableFuture<AdSelectionOrchestrationResult> orchestrateAdSelection( 486 @NonNull AdSelectionConfig adSelectionConfig, 487 @NonNull String callerPackageName, 488 @NonNull ListenableFuture<List<DBCustomAudience>> buyerCustomAudience); 489 490 @Nullable handleTimeoutError(TimeoutException e)491 private DBAdSelection handleTimeoutError(TimeoutException e) { 492 sLogger.e(e, "Ad Selection exceeded time limit"); 493 throw new UncheckedTimeoutException(AD_SELECTION_TIMED_OUT); 494 } 495 getBuyersCustomAudience( final AdSelectionConfig adSelectionConfig)496 private ListenableFuture<List<DBCustomAudience>> getBuyersCustomAudience( 497 final AdSelectionConfig adSelectionConfig) { 498 final int traceCookie = Tracing.beginAsyncSection(Tracing.GET_BUYERS_CUSTOM_AUDIENCE); 499 return mBackgroundExecutorService.submit( 500 () -> { 501 boolean atLeastOnePresent = 502 !(adSelectionConfig.getCustomAudienceBuyers().isEmpty() 503 && adSelectionConfig.getBuyerContextualAds().isEmpty()); 504 505 Preconditions.checkArgument( 506 atLeastOnePresent, ERROR_NO_BUYERS_OR_CONTEXTUAL_ADS_AVAILABLE); 507 // Set start of bidding stage. 508 mAdSelectionExecutionLogger.startBiddingProcess( 509 countBuyersRequested(adSelectionConfig)); 510 List<DBCustomAudience> buyerCustomAudience = 511 mCustomAudienceDao.getActiveCustomAudienceByBuyers( 512 adSelectionConfig.getCustomAudienceBuyers(), 513 mClock.instant(), 514 mFlags.getFledgeCustomAudienceActiveTimeWindowInMs()); 515 if ((buyerCustomAudience == null || buyerCustomAudience.isEmpty()) 516 && adSelectionConfig.getBuyerContextualAds().isEmpty()) { 517 IllegalStateException exception = 518 new IllegalStateException(ERROR_NO_CA_AND_CONTEXTUAL_ADS_AVAILABLE); 519 mAdSelectionExecutionLogger.endBiddingProcess( 520 null, getResultCodeFromException(exception)); 521 throw exception; 522 } 523 // end a successful get-buyers-custom-audience process. 524 mAdSelectionExecutionLogger.endGetBuyersCustomAudience( 525 countBuyersFromCustomAudiences(buyerCustomAudience)); 526 Tracing.endAsyncSection(Tracing.GET_BUYERS_CUSTOM_AUDIENCE, traceCookie); 527 return buyerCustomAudience; 528 }); 529 } 530 countBuyersRequested(@onNull AdSelectionConfig adSelectionConfig)531 private int countBuyersRequested(@NonNull AdSelectionConfig adSelectionConfig) { 532 Objects.requireNonNull(adSelectionConfig); 533 return adSelectionConfig.getCustomAudienceBuyers().stream() 534 .collect(Collectors.toSet()) 535 .size(); 536 } 537 countBuyersFromCustomAudiences( @onNull List<DBCustomAudience> buyerCustomAudience)538 private int countBuyersFromCustomAudiences( 539 @NonNull List<DBCustomAudience> buyerCustomAudience) { 540 Objects.requireNonNull(buyerCustomAudience); 541 return buyerCustomAudience.stream() 542 .map(a -> a.getBuyer()) 543 .collect(Collectors.toSet()) 544 .size(); 545 } 546 persistAdSelection( @onNull DBAdSelection.Builder dbAdSelectionBuilder, @NonNull String buyerDecisionLogicJS, @NonNull String callerPackageName)547 private ListenableFuture<DBAdSelection> persistAdSelection( 548 @NonNull DBAdSelection.Builder dbAdSelectionBuilder, 549 @NonNull String buyerDecisionLogicJS, 550 @NonNull String callerPackageName) { 551 final int traceCookie = Tracing.beginAsyncSection(Tracing.PERSIST_AD_SELECTION); 552 return mBackgroundExecutorService.submit( 553 () -> { 554 long adSelectionId = mAdSelectionIdGenerator.generateId(); 555 // Retry ID generation in case of collision 556 while (mAdSelectionEntryDao.doesAdSelectionIdExist(adSelectionId)) { 557 adSelectionId = mAdSelectionIdGenerator.generateId(); 558 } 559 sLogger.v("Persisting Ad Selection Result for Id:%d", adSelectionId); 560 DBAdSelection dbAdSelection; 561 dbAdSelectionBuilder 562 .setAdSelectionId(adSelectionId) 563 .setCreationTimestamp(mClock.instant()) 564 .setCallerPackageName(callerPackageName); 565 dbAdSelection = dbAdSelectionBuilder.build(); 566 mAdSelectionExecutionLogger.startPersistAdSelection(dbAdSelection); 567 mAdSelectionEntryDao.persistAdSelection(dbAdSelection); 568 mAdSelectionEntryDao.persistBuyerDecisionLogic( 569 new DBBuyerDecisionLogic.Builder() 570 .setBuyerDecisionLogicJs(buyerDecisionLogicJS) 571 .setBiddingLogicUri(dbAdSelection.getBiddingLogicUri()) 572 .build()); 573 mAdSelectionExecutionLogger.endPersistAdSelection(); 574 Tracing.endAsyncSection(Tracing.PERSIST_AD_SELECTION, traceCookie); 575 return dbAdSelection; 576 }); 577 } 578 579 /** 580 * Validates the {@code adSelectionConfig} from the request. 581 * 582 * @param adSelectionConfig the adSelectionConfig to be validated 583 * @throws IllegalArgumentException if the provided {@code adSelectionConfig} is not valid 584 */ 585 private void validateAdSelectionConfig(AdSelectionConfig adSelectionConfig) 586 throws IllegalArgumentException { 587 AdSelectionConfigValidator adSelectionConfigValidator = 588 new AdSelectionConfigValidator(mPrebuiltLogicGenerator); 589 adSelectionConfigValidator.validate(adSelectionConfig); 590 } 591 592 private AdSelectionConfig getAdSelectionConfigFilterContextualAds( 593 AdSelectionConfig adSelectionConfig) { 594 Map<AdTechIdentifier, ContextualAds> filteredContextualAdsMap = new HashMap<>(); 595 sLogger.v("Filtering contextual ads in Ad Selection Config"); 596 for (Map.Entry<AdTechIdentifier, ContextualAds> entry : 597 adSelectionConfig.getBuyerContextualAds().entrySet()) { 598 filteredContextualAdsMap.put( 599 entry.getKey(), mAdFilterer.filterContextualAds(entry.getValue())); 600 } 601 return adSelectionConfig 602 .cloneToBuilder() 603 .setBuyerContextualAds(filteredContextualAdsMap) 604 .build(); 605 } 606 607 private AdSelectionConfig getAdSelectionConfigWithoutContextualAds( 608 AdSelectionConfig adSelectionConfig) { 609 sLogger.v("Emptying contextual ads in Ad Selection Config"); 610 return adSelectionConfig 611 .cloneToBuilder() 612 .setBuyerContextualAds(Collections.EMPTY_MAP) 613 .build(); 614 } 615 616 static class AdSelectionOrchestrationResult { 617 DBAdSelection.Builder mDbAdSelectionBuilder; 618 String mBuyerDecisionLogicJs; 619 620 AdSelectionOrchestrationResult( 621 DBAdSelection.Builder dbAdSelectionBuilder, String buyerDecisionLogicJs) { 622 this.mDbAdSelectionBuilder = dbAdSelectionBuilder; 623 this.mBuyerDecisionLogicJs = buyerDecisionLogicJs; 624 } 625 } 626 } 627