• 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.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