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