| /* |
| * 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. |
| * |
| * <p>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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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 |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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. |
| * |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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. |
| * |
| * <p>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<Object, Exception> 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. |
| * |
| * <p>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. |
| * |
| * <p>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<Object, Exception> 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)); |
| } |
| } |
| } |