/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.adservices.service.adselection; import static com.android.adservices.service.common.Throttler.ApiKey.FLEDGE_API_SELECT_ADS; import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS; import android.adservices.adselection.AdSelectionCallback; import android.adservices.adselection.AdSelectionConfig; import android.adservices.adselection.AdSelectionInput; import android.adservices.adselection.AdSelectionResponse; import android.adservices.common.AdServicesStatusUtils; import android.adservices.common.FledgeErrorResponse; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.Uri; import android.os.LimitExceededException; import android.os.RemoteException; import com.android.adservices.LogUtil; import com.android.adservices.data.adselection.AdSelectionEntryDao; import com.android.adservices.data.adselection.DBAdSelection; import com.android.adservices.data.adselection.DBBuyerDecisionLogic; import com.android.adservices.data.customaudience.CustomAudienceDao; import com.android.adservices.data.customaudience.DBCustomAudience; import com.android.adservices.service.Flags; import com.android.adservices.service.common.AppImportanceFilter; import com.android.adservices.service.common.AppImportanceFilter.WrongCallingApplicationStateException; import com.android.adservices.service.common.FledgeAllowListsFilter; import com.android.adservices.service.common.FledgeAuthorizationFilter; import com.android.adservices.service.common.Throttler; import com.android.adservices.service.consent.ConsentManager; import com.android.adservices.service.js.JSSandboxIsNotAvailableException; import com.android.adservices.service.js.JSScriptEngine; import com.android.adservices.service.stats.AdServicesLogger; import com.android.adservices.service.stats.ApiServiceLatencyCalculator; import com.android.internal.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.UncheckedTimeoutException; import java.time.Clock; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; /** * Orchestrator that runs the Ads Auction/Bidding and Scoring logic The class expects the caller to * create a concrete object instance of the class. The instances are mutually exclusive and do not * share any values across shared class instance. * *

Class takes in an executor on which it runs the AdSelection logic */ public abstract class AdSelectionRunner { @VisibleForTesting static final String AD_SELECTION_ERROR_PATTERN = "%s: %s"; @VisibleForTesting static final String ERROR_AD_SELECTION_FAILURE = "Encountered failure during Ad Selection"; @VisibleForTesting static final String ERROR_NO_WINNING_AD_FOUND = "No winning Ads found"; @VisibleForTesting static final String ERROR_NO_VALID_BIDS_FOR_SCORING = "No valid bids for scoring"; @VisibleForTesting static final String ERROR_NO_CA_AVAILABLE = "No Custom Audience available"; @VisibleForTesting static final String ERROR_NO_BUYERS_AVAILABLE = "The list of the custom audience buyers should not be empty."; @VisibleForTesting static final String AD_SELECTION_TIMED_OUT = "Ad selection exceeded allowed time limit"; @VisibleForTesting static final String AD_SELECTION_THROTTLED = "Ad selection exceeded allowed rate limit"; @VisibleForTesting static final String JS_SANDBOX_IS_NOT_AVAILABLE = String.format( AD_SELECTION_ERROR_PATTERN, ERROR_AD_SELECTION_FAILURE, "JS Sandbox is not available"); public static final long DAY_IN_SECONDS = 60 * 60 * 24; @NonNull protected final Context mContext; @NonNull protected final CustomAudienceDao mCustomAudienceDao; @NonNull protected final AdSelectionEntryDao mAdSelectionEntryDao; @NonNull protected final ListeningExecutorService mLightweightExecutorService; @NonNull protected final ListeningExecutorService mBackgroundExecutorService; @NonNull protected final ScheduledThreadPoolExecutor mScheduledExecutor; @NonNull protected final AdSelectionIdGenerator mAdSelectionIdGenerator; @NonNull protected final Clock mClock; @NonNull protected final ConsentManager mConsentManager; @NonNull protected final AdServicesLogger mAdServicesLogger; @NonNull protected final Flags mFlags; @NonNull protected final AppImportanceFilter mAppImportanceFilter; @NonNull protected final Supplier mThrottlerSupplier; @NonNull protected final FledgeAuthorizationFilter mFledgeAuthorizationFilter; @NonNull protected final FledgeAllowListsFilter mFledgeAllowListsFilter; @NonNull protected final ApiServiceLatencyCalculator mApiServiceLatencyCalculator; protected final int mCallerUid; /** * @param context service context * @param customAudienceDao DAO to access custom audience storage * @param adSelectionEntryDao DAO to access ad selection storage * @param lightweightExecutorService executor for running short tasks * @param backgroundExecutorService executor for longer running tasks (ex. network calls) * @param scheduledExecutor executor for tasks to be run with a delay or timed executions * @param consentManager instance of {@link ConsentManager} for verifying user consent * @param adServicesLogger logger for logging calls to PPAPI * @param appImportanceFilter filter to assert calling app is running in the foreground * @param flags for accessing feature flags * @param throttlerSupplier supplier for throttling calls to PPAPI * @param callerUid calling app UID * @param fledgeAuthorizationFilter filter for authorizing the caller on certain behavior * @param fledgeAllowListsFilter filter for verifying the caller can call PPAPI */ public AdSelectionRunner( @NonNull final Context context, @NonNull final CustomAudienceDao customAudienceDao, @NonNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final ConsentManager consentManager, @NonNull final AdServicesLogger adServicesLogger, @NonNull AppImportanceFilter appImportanceFilter, @NonNull final Flags flags, @NonNull final Supplier throttlerSupplier, int callerUid, @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter, @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter, @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) { Objects.requireNonNull(context); Objects.requireNonNull(customAudienceDao); Objects.requireNonNull(adSelectionEntryDao); Objects.requireNonNull(lightweightExecutorService); Objects.requireNonNull(backgroundExecutorService); Objects.requireNonNull(consentManager); Objects.requireNonNull(adServicesLogger); Objects.requireNonNull(appImportanceFilter); Objects.requireNonNull(flags); Objects.requireNonNull(throttlerSupplier); Objects.requireNonNull(fledgeAuthorizationFilter); Objects.requireNonNull(fledgeAllowListsFilter); Preconditions.checkArgument( JSScriptEngine.AvailabilityChecker.isJSSandboxAvailable(), JS_SANDBOX_IS_NOT_AVAILABLE); Objects.requireNonNull(apiServiceLatencyCalculator); mContext = context; mCustomAudienceDao = customAudienceDao; mAdSelectionEntryDao = adSelectionEntryDao; mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); mScheduledExecutor = scheduledExecutor; mConsentManager = consentManager; mAdServicesLogger = adServicesLogger; mAdSelectionIdGenerator = new AdSelectionIdGenerator(); mClock = Clock.systemUTC(); mFlags = flags; mThrottlerSupplier = throttlerSupplier; mAppImportanceFilter = appImportanceFilter; mCallerUid = callerUid; mFledgeAuthorizationFilter = fledgeAuthorizationFilter; mFledgeAllowListsFilter = fledgeAllowListsFilter; mApiServiceLatencyCalculator = apiServiceLatencyCalculator; } @VisibleForTesting AdSelectionRunner( @NonNull final Context context, @NonNull final CustomAudienceDao customAudienceDao, @NonNull final AdSelectionEntryDao adSelectionEntryDao, @NonNull final ExecutorService lightweightExecutorService, @NonNull final ExecutorService backgroundExecutorService, @NonNull final ScheduledThreadPoolExecutor scheduledExecutor, @NonNull final ConsentManager consentManager, @NonNull final AdSelectionIdGenerator adSelectionIdGenerator, @NonNull Clock clock, @NonNull final AdServicesLogger adServicesLogger, @NonNull AppImportanceFilter appImportanceFilter, @NonNull final Flags flags, @NonNull final Supplier throttlerSupplier, int callerUid, @NonNull final FledgeAuthorizationFilter fledgeAuthorizationFilter, @NonNull final FledgeAllowListsFilter fledgeAllowListsFilter, @NonNull final ApiServiceLatencyCalculator apiServiceLatencyCalculator) { Objects.requireNonNull(context); Objects.requireNonNull(customAudienceDao); Objects.requireNonNull(adSelectionEntryDao); Objects.requireNonNull(lightweightExecutorService); Objects.requireNonNull(backgroundExecutorService); Objects.requireNonNull(scheduledExecutor); Objects.requireNonNull(consentManager); Objects.requireNonNull(adSelectionIdGenerator); Objects.requireNonNull(clock); Objects.requireNonNull(adServicesLogger); Objects.requireNonNull(appImportanceFilter); Objects.requireNonNull(flags); Objects.requireNonNull(fledgeAuthorizationFilter); Objects.requireNonNull(apiServiceLatencyCalculator); mContext = context; mCustomAudienceDao = customAudienceDao; mAdSelectionEntryDao = adSelectionEntryDao; mLightweightExecutorService = MoreExecutors.listeningDecorator(lightweightExecutorService); mBackgroundExecutorService = MoreExecutors.listeningDecorator(backgroundExecutorService); mScheduledExecutor = scheduledExecutor; mConsentManager = consentManager; mAdSelectionIdGenerator = adSelectionIdGenerator; mClock = clock; mAdServicesLogger = adServicesLogger; mFlags = flags; mThrottlerSupplier = throttlerSupplier; mAppImportanceFilter = appImportanceFilter; mCallerUid = callerUid; mFledgeAuthorizationFilter = fledgeAuthorizationFilter; mFledgeAllowListsFilter = fledgeAllowListsFilter; mApiServiceLatencyCalculator = apiServiceLatencyCalculator; } /** * Runs the ad selection for a given seller * * @param inputParams containing {@link AdSelectionConfig} and {@code callerPackageName} * @param callback used to notify the result back to the calling seller */ public void runAdSelection( @NonNull AdSelectionInput inputParams, @NonNull AdSelectionCallback callback) { Objects.requireNonNull(inputParams); Objects.requireNonNull(callback); try { ListenableFuture validateRequestFuture = Futures.submit( () -> validateRequest( inputParams.getAdSelectionConfig(), inputParams.getCallerPackageName()), mLightweightExecutorService); ListenableFuture dbAdSelectionFuture = FluentFuture.from(validateRequestFuture) .transformAsync( ignoredVoid -> orchestrateAdSelection( inputParams.getAdSelectionConfig(), inputParams.getCallerPackageName()), mLightweightExecutorService); Futures.addCallback( dbAdSelectionFuture, new FutureCallback() { @Override public void onSuccess(DBAdSelection result) { notifySuccessToCaller(result, callback); } @Override public void onFailure(Throwable t) { if (t instanceof ConsentManager.RevokedConsentException) { notifyEmptySuccessToCaller( callback, AdServicesStatusUtils.STATUS_USER_CONSENT_REVOKED); } else { notifyFailureToCaller(callback, t); } } }, mLightweightExecutorService); } catch (Throwable t) { LogUtil.v("run ad selection fails fast with exception %s.", t.toString()); notifyFailureToCaller(callback, t); } } private void notifySuccessToCaller( @NonNull DBAdSelection result, @NonNull AdSelectionCallback callback) { int resultCode = AdServicesStatusUtils.STATUS_UNSET; try { callback.onSuccess( new AdSelectionResponse.Builder() .setAdSelectionId(result.getAdSelectionId()) .setRenderUri(result.getWinningAdRenderUri()) .build()); resultCode = AdServicesStatusUtils.STATUS_SUCCESS; } catch (RemoteException e) { LogUtil.e(e, "Encountered exception during notifying AdSelection callback"); resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR; } finally { int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs(); LogUtil.v( "Ad Selection with Id:%d completed with overall latency %d in ms, " + "attempted notifying success", result.getAdSelectionId(), overallLatencyMs); // TODO(b//253522566): When including logging data from bidding & auction server side // should be able to differentiate the data from the on-device telemetry. mAdServicesLogger.logFledgeApiCallStats( AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs); } } /** Sends a successful response to the caller that represents a silent failure. */ private void notifyEmptySuccessToCaller(@NonNull AdSelectionCallback callback, int resultCode) { try { callback.onSuccess( new AdSelectionResponse.Builder() .setAdSelectionId(mAdSelectionIdGenerator.generateId()) .setRenderUri(Uri.EMPTY) .build()); } catch (RemoteException e) { LogUtil.e(e, "Encountered exception during notifying AdSelection callback"); resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR; } finally { int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs(); LogUtil.v( "Ad Selection with Id:%d completed with overall latency %d in ms, " + "attempted notifying success for a silent failure", mAdSelectionIdGenerator.generateId(), overallLatencyMs); // TODO(b//253522566): When including logging data from bidding & auction server side // should be able to differentiate the data from the on-device telemetry. mAdServicesLogger.logFledgeApiCallStats( AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs); } } private void notifyFailureToCaller( @NonNull AdSelectionCallback callback, @NonNull Throwable t) { int resultCode = AdServicesStatusUtils.STATUS_UNSET; try { if (t instanceof WrongCallingApplicationStateException) { resultCode = AdServicesStatusUtils.STATUS_BACKGROUND_CALLER; } else if (t instanceof UncheckedTimeoutException) { resultCode = AdServicesStatusUtils.STATUS_TIMEOUT; } else if (t instanceof FledgeAuthorizationFilter.AdTechNotAllowedException || t instanceof FledgeAllowListsFilter.AppNotAllowedException) { resultCode = AdServicesStatusUtils.STATUS_CALLER_NOT_ALLOWED; } else if (t instanceof FledgeAuthorizationFilter.CallerMismatchException) { resultCode = AdServicesStatusUtils.STATUS_UNAUTHORIZED; } else if (t instanceof IllegalArgumentException) { resultCode = AdServicesStatusUtils.STATUS_INVALID_ARGUMENT; } else if (t instanceof LimitExceededException) { resultCode = AdServicesStatusUtils.STATUS_RATE_LIMIT_REACHED; } else if (t instanceof JSSandboxIsNotAvailableException) { resultCode = AdServicesStatusUtils.STATUS_JS_SANDBOX_UNAVAILABLE; } else { resultCode = AdServicesStatusUtils.STATUS_INTERNAL_ERROR; } FledgeErrorResponse selectionFailureResponse = new FledgeErrorResponse.Builder() .setErrorMessage( String.format( AD_SELECTION_ERROR_PATTERN, ERROR_AD_SELECTION_FAILURE, t.getMessage())) .setStatusCode(resultCode) .build(); LogUtil.e(t, "Ad Selection failure: "); callback.onFailure(selectionFailureResponse); } catch (RemoteException e) { LogUtil.e(e, "Encountered exception during notifying AdSelection callback"); resultCode = AdServicesStatusUtils.STATUS_UNKNOWN_ERROR; } finally { int overallLatencyMs = mApiServiceLatencyCalculator.getApiServiceOverallLatencyMs(); LogUtil.v("Ad Selection failed with overall latency %d in ms", overallLatencyMs); // TODO(b//253522566): When including logging data from bidding & auction server side // should be able to differentiate the data from the on-device telemetry. mAdServicesLogger.logFledgeApiCallStats( AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, resultCode, overallLatencyMs); } } /** * Overall moderator for running Ad Selection * * @param adSelectionConfig Set of data from Sellers and Buyers needed for Ad Auction and * Selection * @return {@link AdSelectionResponse} */ private ListenableFuture orchestrateAdSelection( @NonNull final AdSelectionConfig adSelectionConfig, @NonNull final String callerPackageName) { LogUtil.v("Beginning Ad Selection Orchestration"); ListenableFuture> buyerCustomAudience = getBuyersCustomAudience(adSelectionConfig); ListenableFuture dbAdSelection = orchestrateAdSelection(adSelectionConfig, callerPackageName, buyerCustomAudience); AsyncFunction saveResultToPersistence = adSelectionAndJs -> { return persistAdSelection( adSelectionAndJs.mDbAdSelectionBuilder, adSelectionAndJs.mBuyerDecisionLogicJs, callerPackageName); }; return FluentFuture.from(dbAdSelection) .transformAsync(saveResultToPersistence, mLightweightExecutorService) .withTimeout( mFlags.getAdSelectionOverallTimeoutMs(), TimeUnit.MILLISECONDS, mScheduledExecutor) .catching( TimeoutException.class, this::handleTimeoutError, mLightweightExecutorService); } abstract ListenableFuture orchestrateAdSelection( @NonNull AdSelectionConfig adSelectionConfig, @NonNull String callerPackageName, @NonNull ListenableFuture> buyerCustomAudience); @Nullable private DBAdSelection handleTimeoutError(TimeoutException e) { LogUtil.e(e, "Ad Selection exceeded time limit"); throw new UncheckedTimeoutException(AD_SELECTION_TIMED_OUT); } private ListenableFuture> getBuyersCustomAudience( final AdSelectionConfig adSelectionConfig) { return mBackgroundExecutorService.submit( () -> { Preconditions.checkArgument( !adSelectionConfig.getCustomAudienceBuyers().isEmpty(), ERROR_NO_BUYERS_AVAILABLE); List buyerCustomAudience = mCustomAudienceDao.getActiveCustomAudienceByBuyers( adSelectionConfig.getCustomAudienceBuyers(), mClock.instant(), mFlags.getFledgeCustomAudienceActiveTimeWindowInMs()); if (buyerCustomAudience == null || buyerCustomAudience.isEmpty()) { // TODO(b/233296309) : Remove this exception after adding contextual // ads throw new IllegalStateException(ERROR_NO_CA_AVAILABLE); } return buyerCustomAudience; }); } private ListenableFuture persistAdSelection( @NonNull DBAdSelection.Builder dbAdSelectionBuilder, @NonNull String buyerDecisionLogicJS, @NonNull String callerPackageName) { final long adSelectionId = mAdSelectionIdGenerator.generateId(); LogUtil.v("Persisting Ad Selection Result for Id:%d", adSelectionId); return mBackgroundExecutorService.submit( () -> { // TODO : b/230568647 retry ID generation in case of collision DBAdSelection dbAdSelection; dbAdSelectionBuilder .setAdSelectionId(adSelectionId) .setCreationTimestamp(mClock.instant()) .setCallerPackageName(callerPackageName); dbAdSelection = dbAdSelectionBuilder.build(); mAdSelectionEntryDao.persistAdSelection(dbAdSelection); mAdSelectionEntryDao.persistBuyerDecisionLogic( new DBBuyerDecisionLogic.Builder() .setBuyerDecisionLogicJs(buyerDecisionLogicJS) .setBiddingLogicUri(dbAdSelection.getBiddingLogicUri()) .build()); return dbAdSelection; }); } /** * Asserts that FLEDGE APIs and the Privacy Sandbox as a whole have user consent. * * @return an ignorable {@code null} * @throws ConsentManager.RevokedConsentException if FLEDGE or the Privacy Sandbox do not have * user consent */ private Void assertCallerHasUserConsent() throws ConsentManager.RevokedConsentException { if (!mConsentManager.getConsent().isGiven()) { throw new ConsentManager.RevokedConsentException(); } return null; } /** * Asserts that the caller has the appropriate foreground status, if enabled. * * @return an ignorable {@code null} * @throws WrongCallingApplicationStateException if the foreground check is enabled and fails */ private Void maybeAssertForegroundCaller() throws WrongCallingApplicationStateException { if (mFlags.getEnforceForegroundStatusForFledgeRunAdSelection()) { mAppImportanceFilter.assertCallerIsInForeground( mCallerUid, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS, null); } return null; } /** * Asserts that the package name provided by the caller is one of the packages of the calling * uid. * * @param callerPackageName caller package name from the request * @throws FledgeAuthorizationFilter.CallerMismatchException if the provided {@code * callerPackageName} is not valid * @return an ignorable {@code null} */ private Void assertCallerPackageName(String callerPackageName) throws FledgeAuthorizationFilter.CallerMismatchException { mFledgeAuthorizationFilter.assertCallingPackageName( callerPackageName, mCallerUid, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS); return null; } /** * Validates the {@code adSelectionConfig} from the request. * * @param adSelectionConfig the adSelectionConfig to be validated * @throws IllegalArgumentException if the provided {@code adSelectionConfig} is not valid * @return an ignorable {@code null} */ private Void validateAdSelectionConfig(AdSelectionConfig adSelectionConfig) throws IllegalArgumentException { AdSelectionConfigValidator adSelectionConfigValidator = new AdSelectionConfigValidator(); adSelectionConfigValidator.validate(adSelectionConfig); return null; } /** * Check if a certain ad tech is enrolled and authorized to perform the operation for the * package. * * @param callerPackageName the package name to check against * @param adSelectionConfig contains the ad tech to check against * @throws FledgeAuthorizationFilter.AdTechNotAllowedException if the ad tech is not authorized * to perform the operation */ private Void assertFledgeEnrollment( AdSelectionConfig adSelectionConfig, String callerPackageName) throws FledgeAuthorizationFilter.AdTechNotAllowedException { if (!mFlags.getDisableFledgeEnrollmentCheck()) { mFledgeAuthorizationFilter.assertAdTechAllowed( mContext, callerPackageName, adSelectionConfig.getSeller(), AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS); } return null; } /** * Asserts the package is allowed to call PPAPI. * * @param callerPackageName the package name to be validated. * @throws FledgeAllowListsFilter.AppNotAllowedException if the package is not authorized. */ private Void assertAppInAllowList(String callerPackageName) throws FledgeAllowListsFilter.AppNotAllowedException { mFledgeAllowListsFilter.assertAppCanUsePpapi( callerPackageName, AD_SERVICES_API_CALLED__API_NAME__SELECT_ADS); return null; } /** * Ensures that the caller package is not throttled from calling the current API * * @param callerPackageName the package name, which should be verified * @throws LimitExceededException if the provided {@code callerPackageName} exceeds its rate * limits * @return an ignorable {@code null} */ private Void assertCallerNotThrottled(final String callerPackageName) throws LimitExceededException { LogUtil.v("Checking if API is throttled for package: %s ", callerPackageName); Throttler throttler = mThrottlerSupplier.get(); boolean isThrottled = !throttler.tryAcquire(FLEDGE_API_SELECT_ADS, callerPackageName); if (isThrottled) { LogUtil.e("Rate Limit Reached for API: %s", FLEDGE_API_SELECT_ADS); throw new LimitExceededException(AD_SELECTION_THROTTLED); } return null; } /** * Validates the {@code runAdSelection} request. * * @param adSelectionConfig the adSelectionConfig to be validated * @param callerPackageName caller package name to be validated * @throws FledgeAuthorizationFilter.CallerMismatchException if the {@code callerPackageName} is * not valid * @throws WrongCallingApplicationStateException if the foreground check is enabled and fails * @throws FledgeAuthorizationFilter.AdTechNotAllowedException if the ad tech is not authorized * to perform the operation * @throws FledgeAllowListsFilter.AppNotAllowedException if the package is not authorized. * @throws ConsentManager.RevokedConsentException if FLEDGE or the Privacy Sandbox do not have * user consent * @throws LimitExceededException if the provided {@code callerPackageName} exceeds the rate * limits * @throws IllegalArgumentException if the provided {@code adSelectionConfig} is not valid * @return an ignorable {@code null} */ private Void validateRequest(AdSelectionConfig adSelectionConfig, String callerPackageName) { assertCallerPackageName(callerPackageName); assertCallerNotThrottled(callerPackageName); maybeAssertForegroundCaller(); assertFledgeEnrollment(adSelectionConfig, callerPackageName); assertAppInAllowList(callerPackageName); assertCallerHasUserConsent(); validateAdSelectionConfig(adSelectionConfig); return null; } static class AdSelectionOrchestrationResult { DBAdSelection.Builder mDbAdSelectionBuilder; String mBuyerDecisionLogicJs; AdSelectionOrchestrationResult( DBAdSelection.Builder dbAdSelectionBuilder, String buyerDecisionLogicJs) { this.mDbAdSelectionBuilder = dbAdSelectionBuilder; this.mBuyerDecisionLogicJs = buyerDecisionLogicJs; } } }