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