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 android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS; 20 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS_FROM_OUTCOMES; 22 23 import android.adservices.adselection.AdSelectionCallback; 24 import android.adservices.adselection.AdSelectionFromOutcomesConfig; 25 import android.adservices.adselection.AdSelectionFromOutcomesInput; 26 import android.adservices.adselection.AdSelectionOutcome; 27 import android.adservices.adselection.AdSelectionResponse; 28 import android.adservices.common.AdServicesStatusUtils; 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.os.Build; 35 import android.os.RemoteException; 36 import android.os.Trace; 37 import android.util.Pair; 38 39 import androidx.annotation.RequiresApi; 40 41 import com.android.adservices.LoggerFactory; 42 import com.android.adservices.concurrency.AdServicesExecutors; 43 import com.android.adservices.data.adselection.AdSelectionEntryDao; 44 import com.android.adservices.data.adselection.datahandlers.AdSelectionResultBidAndUri; 45 import com.android.adservices.service.DebugFlags; 46 import com.android.adservices.service.Flags; 47 import com.android.adservices.service.adselection.debug.DebugReportingScriptDisabledStrategy; 48 import com.android.adservices.service.common.AdSelectionServiceFilter; 49 import com.android.adservices.service.common.BinderFlagReader; 50 import com.android.adservices.service.common.RetryStrategy; 51 import com.android.adservices.service.common.Throttler; 52 import com.android.adservices.service.common.cache.CacheProviderFactory; 53 import com.android.adservices.service.common.httpclient.AdServicesHttpsClient; 54 import com.android.adservices.service.consent.ConsentManager; 55 import com.android.adservices.service.devapi.AdSelectionDevOverridesHelper; 56 import com.android.adservices.service.devapi.DevContext; 57 import com.android.adservices.service.exception.FilterException; 58 import com.android.adservices.service.profiling.Tracing; 59 import com.android.adservices.service.stats.AdServicesLogger; 60 import com.android.adservices.service.stats.AdServicesLoggerUtil; 61 import com.android.adservices.service.stats.SelectAdsFromOutcomesExecutionLogger; 62 import com.android.internal.annotations.VisibleForTesting; 63 64 import com.google.common.collect.ImmutableList; 65 import com.google.common.util.concurrent.FluentFuture; 66 import com.google.common.util.concurrent.FutureCallback; 67 import com.google.common.util.concurrent.Futures; 68 import com.google.common.util.concurrent.ListenableFuture; 69 import com.google.common.util.concurrent.ListeningExecutorService; 70 import com.google.common.util.concurrent.MoreExecutors; 71 import com.google.common.util.concurrent.UncheckedTimeoutException; 72 73 import java.util.List; 74 import java.util.Objects; 75 import java.util.concurrent.ExecutorService; 76 import java.util.concurrent.ScheduledThreadPoolExecutor; 77 import java.util.concurrent.TimeUnit; 78 import java.util.concurrent.TimeoutException; 79 import java.util.stream.Collectors; 80 81 /** 82 * Orchestrator that runs the logic retrieved on a list of outcomes and signals. 83 * 84 * <p>Class takes in an executor on which it runs the OutcomeSelection logic 85 */ 86 @RequiresApi(Build.VERSION_CODES.S) 87 public class OutcomeSelectionRunner { 88 private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); 89 @VisibleForTesting static final String AD_SELECTION_FROM_OUTCOMES_ERROR_PATTERN = "%s: %s"; 90 91 @VisibleForTesting 92 static final String ERROR_AD_SELECTION_FROM_OUTCOMES_FAILURE = 93 "Encountered failure during Ad Selection"; 94 95 @VisibleForTesting 96 static final String SELECTED_OUTCOME_MUST_BE_ONE_OF_THE_INPUTS = 97 "Outcome selection must return a valid ad selection id"; 98 99 @VisibleForTesting 100 static final String AD_SELECTION_TIMED_OUT = "Ad selection exceeded allowed time limit"; 101 102 @NonNull private final AdSelectionEntryDao mAdSelectionEntryDao; 103 @NonNull private final ListeningExecutorService mBackgroundExecutorService; 104 @NonNull private final ListeningExecutorService mLightweightExecutorService; 105 @NonNull private final ScheduledThreadPoolExecutor mScheduledExecutor; 106 @NonNull private final AdServicesHttpsClient mAdServicesHttpsClient; 107 @NonNull private final AdServicesLogger mAdServicesLogger; 108 @NonNull private final Context mContext; 109 @NonNull private final Flags mFlags; 110 @NonNull private final DebugFlags mDebugFlags; 111 112 @NonNull private final AdOutcomeSelector mAdOutcomeSelector; 113 @NonNull private final AdSelectionServiceFilter mAdSelectionServiceFilter; 114 private final int mCallerUid; 115 @NonNull private final PrebuiltLogicGenerator mPrebuiltLogicGenerator; 116 @NonNull private final DevContext mDevContext; 117 private final boolean mShouldUseUnifiedTables; 118 119 /** 120 * @param adSelectionEntryDao DAO to access ad selection storage 121 * @param backgroundExecutorService executor for longer running tasks (ex. network calls) 122 * @param lightweightExecutorService executor for running short tasks 123 * @param scheduledExecutor executor for tasks to be run with a delay or timed executions 124 * @param adServicesHttpsClient HTTPS client to use when fetch JS logics 125 * @param adServicesLogger logger for logging calls to PPAPI 126 * @param context service context 127 * @param flags for accessing feature flags 128 * @param adSelectionServiceFilter to validate the request 129 */ OutcomeSelectionRunner( @onNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final AdServicesHttpsClient adServicesHttpsClient, @NonNull final AdServicesLogger adServicesLogger, @NonNull final DevContext devContext, @NonNull final Context context, @NonNull final Flags flags, @NonNull final DebugFlags debugFlags, @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, @NonNull final AdCounterKeyCopier adCounterKeyCopier, final int callerUid, boolean shouldUseUnifiedTables, @NonNull final RetryStrategy retryStrategy, boolean consoleMessageInLogsEnabled)130 public OutcomeSelectionRunner( 131 @NonNull final AdSelectionEntryDao adSelectionEntryDao, 132 @NonNull final ExecutorService backgroundExecutorService, 133 @NonNull final ExecutorService lightweightExecutorService, 134 @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, 135 @NonNull final AdServicesHttpsClient adServicesHttpsClient, 136 @NonNull final AdServicesLogger adServicesLogger, 137 @NonNull final DevContext devContext, 138 @NonNull final Context context, 139 @NonNull final Flags flags, 140 @NonNull final DebugFlags debugFlags, 141 @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, 142 @NonNull final AdCounterKeyCopier adCounterKeyCopier, 143 final int callerUid, 144 boolean shouldUseUnifiedTables, 145 @NonNull final RetryStrategy retryStrategy, 146 boolean consoleMessageInLogsEnabled) { 147 Objects.requireNonNull(adSelectionEntryDao); 148 Objects.requireNonNull(backgroundExecutorService); 149 Objects.requireNonNull(lightweightExecutorService); 150 Objects.requireNonNull(scheduledExecutor); 151 Objects.requireNonNull(adServicesHttpsClient); 152 Objects.requireNonNull(adServicesLogger); 153 Objects.requireNonNull(devContext); 154 Objects.requireNonNull(context); 155 Objects.requireNonNull(flags); 156 Objects.requireNonNull(debugFlags); 157 Objects.requireNonNull(adCounterKeyCopier); 158 Objects.requireNonNull(retryStrategy); 159 160 mAdSelectionEntryDao = adSelectionEntryDao; 161 mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); 162 mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); 163 mScheduledExecutor = scheduledExecutor; 164 mAdServicesHttpsClient = adServicesHttpsClient; 165 mAdServicesLogger = adServicesLogger; 166 mContext = context; 167 mFlags = flags; 168 mDebugFlags = debugFlags; 169 mDevContext = devContext; 170 171 boolean cpcBillingEnabled = BinderFlagReader.readFlag(mFlags::getFledgeCpcBillingEnabled); 172 mAdOutcomeSelector = 173 new AdOutcomeSelectorImpl( 174 new AdSelectionScriptEngine( 175 flags::getIsolateMaxHeapSizeBytes, 176 adCounterKeyCopier, 177 new DebugReportingScriptDisabledStrategy(), 178 cpcBillingEnabled, 179 retryStrategy, 180 () -> consoleMessageInLogsEnabled), 181 mLightweightExecutorService, 182 mBackgroundExecutorService, 183 mScheduledExecutor, 184 mAdServicesHttpsClient, 185 new AdSelectionDevOverridesHelper(devContext, adSelectionEntryDao), 186 mFlags, 187 mDevContext); 188 mAdSelectionServiceFilter = adSelectionServiceFilter; 189 mCallerUid = callerUid; 190 mPrebuiltLogicGenerator = new PrebuiltLogicGenerator(mFlags); 191 mShouldUseUnifiedTables = shouldUseUnifiedTables; 192 } 193 194 @VisibleForTesting OutcomeSelectionRunner( int callerUid, @NonNull final AdOutcomeSelector adOutcomeSelector, @NonNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final AdServicesLogger adServicesLogger, @NonNull final Context context, @NonNull final Flags flags, @NonNull final DebugFlags debugFlags, @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, @NonNull final DevContext devContext, boolean shouldUseUnifiedTables)195 public OutcomeSelectionRunner( 196 int callerUid, 197 @NonNull final AdOutcomeSelector adOutcomeSelector, 198 @NonNull final AdSelectionEntryDao adSelectionEntryDao, 199 @NonNull final ExecutorService backgroundExecutorService, 200 @NonNull final ExecutorService lightweightExecutorService, 201 @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, 202 @NonNull final AdServicesLogger adServicesLogger, 203 @NonNull final Context context, 204 @NonNull final Flags flags, 205 @NonNull final DebugFlags debugFlags, 206 @NonNull final AdSelectionServiceFilter adSelectionServiceFilter, 207 @NonNull final DevContext devContext, 208 boolean shouldUseUnifiedTables) { 209 Objects.requireNonNull(adOutcomeSelector); 210 Objects.requireNonNull(adSelectionEntryDao); 211 Objects.requireNonNull(backgroundExecutorService); 212 Objects.requireNonNull(lightweightExecutorService); 213 Objects.requireNonNull(scheduledExecutor); 214 Objects.requireNonNull(adServicesLogger); 215 Objects.requireNonNull(context); 216 Objects.requireNonNull(flags); 217 Objects.requireNonNull(debugFlags); 218 Objects.requireNonNull(adSelectionServiceFilter); 219 Objects.requireNonNull(devContext); 220 221 mAdSelectionEntryDao = adSelectionEntryDao; 222 mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); 223 mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); 224 mScheduledExecutor = scheduledExecutor; 225 mAdServicesHttpsClient = 226 new AdServicesHttpsClient( 227 AdServicesExecutors.getBlockingExecutor(), 228 CacheProviderFactory.create(context, flags)); 229 mAdServicesLogger = adServicesLogger; 230 mContext = context; 231 mFlags = flags; 232 mDebugFlags = debugFlags; 233 234 mAdOutcomeSelector = adOutcomeSelector; 235 mAdSelectionServiceFilter = adSelectionServiceFilter; 236 mCallerUid = callerUid; 237 mPrebuiltLogicGenerator = new PrebuiltLogicGenerator(mFlags); 238 mDevContext = devContext; 239 mShouldUseUnifiedTables = shouldUseUnifiedTables; 240 } 241 242 /** 243 * Runs outcome selection logic on given list of outcomes and signals. 244 * 245 * @param inputParams includes list of outcomes, selection signals and URI to download the logic 246 * @param callback is used to notify the results to the caller 247 */ runOutcomeSelection( @onNull AdSelectionFromOutcomesInput inputParams, @NonNull AdSelectionCallback callback, @NonNull SelectAdsFromOutcomesExecutionLogger selectAdsFromOutcomesExecutionLogger)248 public void runOutcomeSelection( 249 @NonNull AdSelectionFromOutcomesInput inputParams, 250 @NonNull AdSelectionCallback callback, 251 @NonNull SelectAdsFromOutcomesExecutionLogger selectAdsFromOutcomesExecutionLogger) { 252 Objects.requireNonNull(inputParams); 253 Objects.requireNonNull(callback); 254 Objects.requireNonNull(selectAdsFromOutcomesExecutionLogger); 255 AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig = 256 inputParams.getAdSelectionFromOutcomesConfig(); 257 try { 258 ListenableFuture<Void> filterAndValidateRequestFuture = 259 Futures.submit( 260 () -> { 261 try { 262 Trace.beginSection(Tracing.VALIDATE_REQUEST); 263 sLogger.v("Starting filtering and validation."); 264 mAdSelectionServiceFilter.filterRequest( 265 adSelectionFromOutcomesConfig.getSeller(), 266 inputParams.getCallerPackageName(), 267 mFlags 268 .getEnforceForegroundStatusForFledgeRunAdSelection(), 269 true, 270 !mDebugFlags.getConsentNotificationDebugMode(), 271 mCallerUid, 272 AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS_FROM_OUTCOMES, 273 Throttler.ApiKey.FLEDGE_API_SELECT_ADS, 274 mDevContext); 275 validateAdSelectionFromOutcomesConfig(inputParams); 276 } finally { 277 sLogger.v("Completed filtering and validation."); 278 Trace.endSection(); 279 } 280 }, 281 mLightweightExecutorService); 282 283 ListenableFuture<AdSelectionOutcome> adSelectionOutcomeFuture = 284 FluentFuture.from(filterAndValidateRequestFuture) 285 .transformAsync( 286 ignoredVoid -> 287 orchestrateOutcomeSelection( 288 inputParams.getAdSelectionFromOutcomesConfig(), 289 inputParams.getCallerPackageName(), 290 selectAdsFromOutcomesExecutionLogger), 291 mLightweightExecutorService); 292 Futures.addCallback( 293 adSelectionOutcomeFuture, 294 new FutureCallback<>() { 295 @Override 296 public void onSuccess(AdSelectionOutcome result) { 297 notifySuccessToCaller( 298 inputParams.getCallerPackageName(), result, callback); 299 selectAdsFromOutcomesExecutionLogger 300 .logSelectAdsFromOutcomesApiCalledStats(); 301 } 302 303 @Override 304 public void onFailure(Throwable t) { 305 if (t instanceof FilterException 306 && t.getCause() 307 instanceof ConsentManager.RevokedConsentException) { 308 // Skip logging if a FilterException occurs. 309 // AdSelectionServiceFilter ensures the failing assertion is logged 310 // internally. 311 312 // Fail Silently by notifying success to caller 313 notifyEmptySuccessToCaller(callback); 314 } else { 315 selectAdsFromOutcomesExecutionLogger 316 .logSelectAdsFromOutcomesApiCalledStats(); 317 if (t.getCause() instanceof AdServicesException) { 318 notifyFailureToCaller( 319 inputParams.getCallerPackageName(), 320 t.getCause(), 321 callback); 322 } else { 323 notifyFailureToCaller( 324 inputParams.getCallerPackageName(), t, callback); 325 } 326 } 327 } 328 }, 329 mLightweightExecutorService); 330 331 } catch (Throwable t) { 332 sLogger.v("runOutcomeSelection fails fast with exception %s.", t.toString()); 333 notifyFailureToCaller(inputParams.getCallerPackageName(), t, callback); 334 } 335 } 336 orchestrateOutcomeSelection( AdSelectionFromOutcomesConfig config, String callerPackageName, SelectAdsFromOutcomesExecutionLogger selectAdsFromOutcomesExecutionLogger)337 private ListenableFuture<AdSelectionOutcome> orchestrateOutcomeSelection( 338 AdSelectionFromOutcomesConfig config, 339 String callerPackageName, 340 SelectAdsFromOutcomesExecutionLogger selectAdsFromOutcomesExecutionLogger) { 341 validateExistenceOfAdSelectionIds( 342 config, callerPackageName, selectAdsFromOutcomesExecutionLogger); 343 return retrieveAdSelectionIdWithBidList(config.getAdSelectionIds(), callerPackageName) 344 .transform( 345 outcomes -> { 346 FluentFuture<Long> selectedIdFuture = 347 mAdOutcomeSelector.runAdOutcomeSelector( 348 outcomes, config, selectAdsFromOutcomesExecutionLogger); 349 return Pair.create(outcomes, selectedIdFuture); 350 }, 351 mLightweightExecutorService) 352 .transformAsync( 353 outcomeAndSelectedIdPair -> 354 convertAdSelectionIdToAdSelectionOutcome( 355 outcomeAndSelectedIdPair.first, 356 outcomeAndSelectedIdPair.second), 357 mLightweightExecutorService) 358 .withTimeout( 359 mFlags.getAdSelectionFromOutcomesOverallTimeoutMs(), 360 TimeUnit.MILLISECONDS, 361 mScheduledExecutor) 362 .catching( 363 TimeoutException.class, 364 this::handleTimeoutError, 365 mLightweightExecutorService); 366 } 367 368 @Nullable handleTimeoutError(TimeoutException e)369 private AdSelectionOutcome handleTimeoutError(TimeoutException e) { 370 sLogger.e(e, "Ad Selection exceeded time limit"); 371 throw new UncheckedTimeoutException(AD_SELECTION_TIMED_OUT); 372 } 373 notifySuccessToCaller( String callerAppPackageName, AdSelectionOutcome result, AdSelectionCallback callback)374 private void notifySuccessToCaller( 375 String callerAppPackageName, AdSelectionOutcome result, AdSelectionCallback callback) { 376 int resultCode = AdServicesStatusUtils.STATUS_UNSET; 377 try { 378 // Note: Success is logged before the callback to ensure deterministic testing. 379 mAdServicesLogger.logFledgeApiCallStats( 380 AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS_FROM_OUTCOMES, 381 callerAppPackageName, 382 STATUS_SUCCESS, 383 /* latencyMs= */ 0); 384 if (result == null) { 385 callback.onSuccess(null); 386 } else { 387 callback.onSuccess( 388 new AdSelectionResponse.Builder() 389 .setAdSelectionId(result.getAdSelectionId()) 390 .setRenderUri(result.getRenderUri()) 391 .build()); 392 } 393 } catch (RemoteException e) { 394 sLogger.e(e, "Encountered exception during notifying AdSelectionCallback"); 395 } finally { 396 sLogger.v("Ad Selection from outcomes completed and attempted notifying success"); 397 } 398 } 399 400 /** Sends a successful response to the caller that represents a silent failure. */ notifyEmptySuccessToCaller(@onNull AdSelectionCallback callback)401 private void notifyEmptySuccessToCaller(@NonNull AdSelectionCallback callback) { 402 try { 403 // TODO(b/259522822): Determine what is an appropriate empty response for revoked 404 // consent for selectAdsFromOutcomes 405 callback.onSuccess(null); 406 } catch (RemoteException e) { 407 sLogger.e(e, "Encountered exception during notifying AdSelectionCallback"); 408 } finally { 409 sLogger.v( 410 "Ad Selection from outcomes completed, attempted notifying success for a" 411 + " silent failure"); 412 } 413 } 414 415 /** Sends a failure notification to the caller */ notifyFailureToCaller( String callerAppPackageName, Throwable t, AdSelectionCallback callback)416 private void notifyFailureToCaller( 417 String callerAppPackageName, Throwable t, AdSelectionCallback callback) { 418 try { 419 sLogger.e("Notify caller of error: " + t); 420 int resultCode = AdServicesLoggerUtil.getResultCodeFromException(t); 421 422 // Skip logging if a FilterException occurs. 423 // AdSelectionServiceFilter ensures the failing assertion is logged internally. 424 // Note: Failure is logged before the callback to ensure deterministic testing. 425 if (!(t instanceof FilterException)) { 426 mAdServicesLogger.logFledgeApiCallStats( 427 AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS_FROM_OUTCOMES, 428 callerAppPackageName, 429 resultCode, 430 /* latencyMs= */ 0); 431 } 432 433 FledgeErrorResponse selectionFailureResponse = 434 new FledgeErrorResponse.Builder() 435 .setErrorMessage( 436 String.format( 437 AD_SELECTION_FROM_OUTCOMES_ERROR_PATTERN, 438 ERROR_AD_SELECTION_FROM_OUTCOMES_FAILURE, 439 t.getMessage())) 440 .setStatusCode(resultCode) 441 .build(); 442 sLogger.e(t, "Ad Selection failure: "); 443 callback.onFailure(selectionFailureResponse); 444 } catch (RemoteException e) { 445 sLogger.e(e, "Encountered exception during notifying AdSelectionCallback"); 446 } finally { 447 sLogger.v("Ad Selection From Outcomes failed"); 448 } 449 } 450 451 /** Retrieves winner ad bids using ad selection ids of already run ad selections' outcomes. */ retrieveAdSelectionIdWithBidList( List<Long> adOutcomeIds, String callerPackageName)452 private FluentFuture<List<AdSelectionResultBidAndUri>> retrieveAdSelectionIdWithBidList( 453 List<Long> adOutcomeIds, String callerPackageName) { 454 return FluentFuture.from( 455 mBackgroundExecutorService.submit( 456 () -> { 457 if (mShouldUseUnifiedTables) { 458 return mAdSelectionEntryDao.getWinningBidAndUriForIdsUnifiedTables( 459 adOutcomeIds); 460 } else if (mFlags 461 .getFledgeAuctionServerEnabledForSelectAdsMediation()) { 462 return mAdSelectionEntryDao.getWinningBidAndUriForIds(adOutcomeIds); 463 } else { 464 return mAdSelectionEntryDao 465 .getAdSelectionEntities(adOutcomeIds, callerPackageName) 466 .parallelStream() 467 .map( 468 e -> 469 AdSelectionResultBidAndUri.builder() 470 .setAdSelectionId( 471 e.getAdSelectionId()) 472 .setWinningAdBid( 473 e.getWinningAdBid()) 474 .setWinningAdRenderUri( 475 e.getWinningAdRenderUri()) 476 .build()) 477 .collect(Collectors.toList()); 478 } 479 })); 480 } 481 482 private void validateExistenceOfAdSelectionIds( 483 AdSelectionFromOutcomesConfig config, 484 String callerPackageName, 485 SelectAdsFromOutcomesExecutionLogger selectAdsFromOutcomesExecutionLogger) { 486 Objects.requireNonNull(config.getAdSelectionIds()); 487 488 ImmutableList.Builder<Long> notExistingIdsBuilder = new ImmutableList.Builder<>(); 489 List<Long> existingIds; 490 if (mShouldUseUnifiedTables) { 491 existingIds = 492 mAdSelectionEntryDao.getAdSelectionIdsWithCallerPackageNameFromUnifiedTable( 493 config.getAdSelectionIds(), callerPackageName); 494 } else if (mFlags.getFledgeAuctionServerEnabledForSelectAdsMediation()) { 495 existingIds = 496 mAdSelectionEntryDao.getAdSelectionIdsWithCallerPackageName( 497 config.getAdSelectionIds(), callerPackageName); 498 } else { 499 existingIds = 500 mAdSelectionEntryDao.getAdSelectionIdsWithCallerPackageNameInOnDeviceTable( 501 config.getAdSelectionIds(), callerPackageName); 502 } 503 config.getAdSelectionIds().stream() 504 .filter(e -> !existingIds.contains(e)) 505 .forEach(notExistingIdsBuilder::add); 506 507 // TODO(b/258912806): Current behavior is to fail if any ad selection ids are absent in the 508 // db or owned by another caller package. Investigate if this behavior needs changing due 509 // to security reasons. 510 selectAdsFromOutcomesExecutionLogger.setCountIds(config.getAdSelectionIds().size()); 511 List<Long> notExistingIds = notExistingIdsBuilder.build(); 512 selectAdsFromOutcomesExecutionLogger.setCountNonExistingIds(notExistingIds.size()); 513 if (!notExistingIds.isEmpty()) { 514 String err = 515 String.format( 516 "Ad selection ids: %s don't exists or owned by the calling package", 517 notExistingIds); 518 sLogger.e(err); 519 throw new IllegalArgumentException(err); 520 } 521 } 522 523 /** Retrieves winner ad bids using ad selection ids of already run ad selections' outcomes. */ 524 private ListenableFuture<AdSelectionOutcome> convertAdSelectionIdToAdSelectionOutcome( 525 List<AdSelectionResultBidAndUri> outcomes, FluentFuture<Long> adSelectionIdFutures) { 526 return adSelectionIdFutures.transformAsync( 527 selectedId -> { 528 if (Objects.isNull(selectedId)) { 529 sLogger.v("No id is selected. Returning null"); 530 return Futures.immediateFuture(null); 531 } 532 sLogger.v( 533 "Converting ad selection id: <%s> to AdSelectionOutcome.", selectedId); 534 return outcomes.stream() 535 .filter(e -> Objects.equals(e.getAdSelectionId(), selectedId)) 536 .findFirst() 537 .map( 538 e -> 539 Futures.immediateFuture( 540 new AdSelectionOutcome.Builder() 541 .setAdSelectionId(e.getAdSelectionId()) 542 .setRenderUri(e.getWinningAdRenderUri()) 543 .build())) 544 .orElse( 545 Futures.immediateFailedFuture( 546 new IllegalStateException( 547 SELECTED_OUTCOME_MUST_BE_ONE_OF_THE_INPUTS))); 548 }, 549 mLightweightExecutorService); 550 } 551 /** 552 * Validates the {@link AdSelectionFromOutcomesInput} from the request. 553 * 554 * @param inputParams the adSelectionConfig to be validated 555 * @throws IllegalArgumentException if the provided {@code adSelectionConfig} is not valid 556 */ 557 private void validateAdSelectionFromOutcomesConfig( 558 @NonNull AdSelectionFromOutcomesInput inputParams) throws IllegalArgumentException { 559 Objects.requireNonNull(inputParams); 560 561 AdSelectionFromOutcomesConfigValidator validator = 562 new AdSelectionFromOutcomesConfigValidator( 563 inputParams.getCallerPackageName(), 564 mPrebuiltLogicGenerator); 565 validator.validate(inputParams.getAdSelectionFromOutcomesConfig()); 566 } 567 } 568