/* * 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 android.adservices.adselection; import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_SELECTION; import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE; import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS; import android.adservices.common.AdServicesStatusUtils; import android.adservices.common.FledgeErrorResponse; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.os.Build; import android.os.OutcomeReceiver; import android.os.RemoteException; import androidx.annotation.RequiresApi; import com.android.adservices.LoggerFactory; import java.util.Objects; import java.util.concurrent.Executor; /** * {@link TestAdSelectionManager} provides APIs for apps and ad SDKs to test ad selection processes. * *

These APIs are intended to be used for end-to-end testing. They are enabled only for * debuggable apps on phones running a debuggable OS build with developer options enabled. */ @RequiresApi(Build.VERSION_CODES.S) public class TestAdSelectionManager { private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger(); private final AdSelectionManager mAdSelectionManager; TestAdSelectionManager(@NonNull AdSelectionManager adSelectionManager) { Objects.requireNonNull(adSelectionManager); mAdSelectionManager = adSelectionManager; } // TODO(b/289362476): Add override APIs for server auction key fetch /** * Overrides the AdSelection API for a given {@link AdSelectionConfig} to avoid fetching data * from remote servers and use the data provided in {@link AddAdSelectionOverrideRequest} * instead. The {@link AddAdSelectionOverrideRequest} is provided by the Ads SDK. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void overrideAdSelectionConfigRemoteInfo( @NonNull AddAdSelectionOverrideRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.overrideAdSelectionConfigRemoteInfo( request.getAdSelectionConfig(), request.getDecisionLogicJs(), request.getTrustedScoringSignals(), request.getPerBuyerDecisionLogic(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Removes an override for {@link AdSelectionConfig} in the Ad Selection API with associated the * data in {@link RemoveAdSelectionOverrideRequest}. The {@link * RemoveAdSelectionOverrideRequest} is provided by the Ads SDK. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void removeAdSelectionConfigRemoteInfoOverride( @NonNull RemoveAdSelectionOverrideRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.removeAdSelectionConfigRemoteInfoOverride( request.getAdSelectionConfig(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Removes all override data for {@link AdSelectionConfig} in the Ad Selection API. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void resetAllAdSelectionConfigRemoteOverrides( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.resetAllAdSelectionConfigRemoteOverrides( new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Overrides the AdSelection API for {@link AdSelectionFromOutcomesConfig} to avoid fetching * data from remote servers and use the data provided in {@link * AddAdSelectionFromOutcomesOverrideRequest} instead. The {@link * AddAdSelectionFromOutcomesOverrideRequest} is provided by the Ads SDK. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void overrideAdSelectionFromOutcomesConfigRemoteInfo( @NonNull AddAdSelectionFromOutcomesOverrideRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.overrideAdSelectionFromOutcomesConfigRemoteInfo( request.getAdSelectionFromOutcomesConfig(), request.getOutcomeSelectionLogicJs(), request.getOutcomeSelectionTrustedSignals(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Removes an override for {@link AdSelectionFromOutcomesConfig} in th Ad Selection API with * associated the data in {@link RemoveAdSelectionOverrideRequest}. The {@link * RemoveAdSelectionOverrideRequest} is provided by the Ads SDK. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void removeAdSelectionFromOutcomesConfigRemoteInfoOverride( @NonNull RemoveAdSelectionFromOutcomesOverrideRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(request); Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.removeAdSelectionFromOutcomesConfigRemoteInfoOverride( request.getAdSelectionFromOutcomesConfig(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Removes all override data for {@link AdSelectionFromOutcomesConfig} in the Ad Selection API. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * * @throws IllegalStateException if this API is not enabled for the caller *

The receiver either returns a {@code void} for a successful run, or an {@link * Exception} indicates the error. */ @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void resetAllAdSelectionFromOutcomesConfigRemoteOverrides( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver receiver) { Objects.requireNonNull(executor); Objects.requireNonNull(receiver); try { final AdSelectionService service = mAdSelectionManager.getServiceProvider().getService(); service.resetAllAdSelectionFromOutcomesConfigRemoteOverrides( new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> receiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> receiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service."); receiver.onError( new IllegalStateException("Unable to find the AdSelection service.", e)); } catch (RemoteException e) { sLogger.e(e, "Exception"); receiver.onError(new IllegalStateException("Failure of AdSelection service.", e)); } } /** * Sets the override for event histogram data, which is used in frequency cap filtering during * ad selection. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * *

The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or * an {@link Exception} which indicates the error. * * @throws IllegalStateException if this API is not enabled for the caller * @hide */ // TODO(b/265204820): Unhide for frequency cap dev override API review @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void setAdCounterHistogramOverride( @NonNull SetAdCounterHistogramOverrideRequest setRequest, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver outcomeReceiver) { Objects.requireNonNull(setRequest, "Request must not be null"); Objects.requireNonNull(executor, "Executor must not be null"); Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null"); try { final AdSelectionService service = Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService()); service.setAdCounterHistogramOverride( new SetAdCounterHistogramOverrideInput.Builder() .setAdEventType(setRequest.getAdEventType()) .setAdCounterKey(setRequest.getAdCounterKey()) .setHistogramTimestamps(setRequest.getHistogramTimestamps()) .setBuyer(setRequest.getBuyer()) .setCustomAudienceOwner(setRequest.getCustomAudienceOwner()) .setCustomAudienceName(setRequest.getCustomAudienceName()) .build(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> outcomeReceiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> outcomeReceiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service"); outcomeReceiver.onError( new IllegalStateException("Unable to find the AdSelection service", e)); } catch (RemoteException e) { sLogger.e(e, "Remote exception encountered while updating ad counter histogram"); outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e)); } } /** * Removes an override for event histogram data, which is used in frequency cap filtering during * ad selection. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * *

The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or * an {@link Exception} which indicates the error. * * @throws IllegalStateException if this API is not enabled for the caller * @hide */ // TODO(b/265204820): Unhide for frequency cap dev override API review @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void removeAdCounterHistogramOverride( @NonNull RemoveAdCounterHistogramOverrideRequest removeRequest, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver outcomeReceiver) { Objects.requireNonNull(removeRequest, "Request must not be null"); Objects.requireNonNull(executor, "Executor must not be null"); Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null"); try { final AdSelectionService service = Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService()); service.removeAdCounterHistogramOverride( new RemoveAdCounterHistogramOverrideInput.Builder() .setAdEventType(removeRequest.getAdEventType()) .setAdCounterKey(removeRequest.getAdCounterKey()) .setBuyer(removeRequest.getBuyer()) .build(), new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> outcomeReceiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> outcomeReceiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service"); outcomeReceiver.onError( new IllegalStateException("Unable to find the AdSelection service", e)); } catch (RemoteException e) { sLogger.e(e, "Remote exception encountered while updating ad counter histogram"); outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e)); } } /** * Removes all previously set histogram overrides used in ad selection which were set by the * caller application. * *

This method is intended to be used for end-to-end testing. This API is enabled only for * apps in debug mode with developer options enabled. * *

The given {@code outcomeReceiver} either returns an empty {@link Object} if successful or * an {@link Exception} which indicates the error. * * @throws IllegalStateException if this API is not enabled for the caller * @hide */ // TODO(b/265204820): Unhide for frequency cap dev override API review @RequiresPermission( anyOf = { ACCESS_ADSERVICES_CUSTOM_AUDIENCE, ACCESS_ADSERVICES_PROTECTED_SIGNALS, ACCESS_ADSERVICES_AD_SELECTION }) public void resetAllAdCounterHistogramOverrides( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver outcomeReceiver) { Objects.requireNonNull(executor, "Executor must not be null"); Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null"); try { final AdSelectionService service = Objects.requireNonNull(mAdSelectionManager.getServiceProvider().getService()); service.resetAllAdCounterHistogramOverrides( new AdSelectionOverrideCallback.Stub() { @Override public void onSuccess() { executor.execute(() -> outcomeReceiver.onResult(new Object())); } @Override public void onFailure(FledgeErrorResponse failureParcel) { executor.execute( () -> outcomeReceiver.onError( AdServicesStatusUtils.asException( failureParcel))); } }); } catch (NullPointerException e) { sLogger.e(e, "Unable to find the AdSelection service"); outcomeReceiver.onError( new IllegalStateException("Unable to find the AdSelection service", e)); } catch (RemoteException e) { sLogger.e(e, "Remote exception encountered while updating ad counter histogram"); outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e)); } } }