• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.adservices.adselection;
18 
19 import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE;
20 import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_AD_SELECTION;
21 import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_PROTECTED_SIGNALS;
22 
23 import static java.util.concurrent.TimeUnit.MILLISECONDS;
24 
25 import android.adservices.adid.AdId;
26 import android.adservices.adid.AdIdCompatibleManager;
27 import android.adservices.common.AdServicesStatusUtils;
28 import android.adservices.common.AssetFileDescriptorUtil;
29 import android.adservices.common.CallerMetadata;
30 import android.adservices.common.FledgeErrorResponse;
31 import android.adservices.common.SandboxedSdkContextUtils;
32 import android.annotation.CallbackExecutor;
33 import android.annotation.FlaggedApi;
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.RequiresPermission;
37 import android.annotation.SuppressLint;
38 import android.app.sdksandbox.SandboxedSdkContext;
39 import android.content.Context;
40 import android.content.res.AssetFileDescriptor;
41 import android.os.Build;
42 import android.os.LimitExceededException;
43 import android.os.OutcomeReceiver;
44 import android.os.RemoteException;
45 import android.os.SystemClock;
46 import android.os.TransactionTooLargeException;
47 
48 import androidx.annotation.RequiresApi;
49 
50 import com.android.adservices.AdServicesCommon;
51 import com.android.adservices.LoggerFactory;
52 import com.android.adservices.ServiceBinder;
53 import com.android.adservices.flags.Flags;
54 import com.android.internal.annotations.VisibleForTesting;
55 
56 import java.io.IOException;
57 import java.util.Objects;
58 import java.util.concurrent.CountDownLatch;
59 import java.util.concurrent.Executor;
60 import java.util.concurrent.Executors;
61 import java.util.concurrent.TimeoutException;
62 import java.util.concurrent.atomic.AtomicReference;
63 
64 /**
65  * AdSelection Manager provides APIs for app and ad-SDKs to run ad selection processes as well as
66  * report impressions.
67  */
68 @RequiresApi(Build.VERSION_CODES.S)
69 public class AdSelectionManager {
70     private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
71 
72     /**
73      * Constant that represents the service name for {@link AdSelectionManager} to be used in {@link
74      * android.adservices.AdServicesFrameworkInitializer#registerServiceWrappers}
75      *
76      * @hide
77      */
78     public static final String AD_SELECTION_SERVICE = "ad_selection_service";
79 
80     private static final long AD_ID_TIMEOUT_MS = 400;
81     private static final String DEBUG_API_WARNING_MESSAGE =
82             "To enable debug api, include ACCESS_ADSERVICES_AD_ID "
83                     + "permission and enable advertising ID under device settings";
84     private final Executor mAdIdExecutor = Executors.newCachedThreadPool();
85     @NonNull private Context mContext;
86     @NonNull private ServiceBinder<AdSelectionService> mServiceBinder;
87     @NonNull private AdIdCompatibleManager mAdIdManager;
88     @NonNull private ServiceProvider mServiceProvider;
89 
90     /**
91      * Factory method for creating an instance of AdSelectionManager.
92      *
93      * @param context The {@link Context} to use
94      * @return A {@link AdSelectionManager} instance
95      */
96     @NonNull
get(@onNull Context context)97     public static AdSelectionManager get(@NonNull Context context) {
98         // On T+, context.getSystemService() does more than just call constructor.
99         return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
100                 ? context.getSystemService(AdSelectionManager.class)
101                 : new AdSelectionManager(context);
102     }
103 
104     /**
105      * Factory method for creating an instance of AdSelectionManager.
106      *
107      * <p>Note: This is for testing only.
108      *
109      * @param context The {@link Context} to use
110      * @param adIdManager The {@link AdIdCompatibleManager} instance to use
111      * @param adSelectionService The {@link AdSelectionService} instance to use
112      * @return A {@link AdSelectionManager} instance
113      * @hide
114      */
115     @VisibleForTesting
116     @NonNull
get( @onNull Context context, @NonNull AdIdCompatibleManager adIdManager, @NonNull AdSelectionService adSelectionService)117     public static AdSelectionManager get(
118             @NonNull Context context,
119             @NonNull AdIdCompatibleManager adIdManager,
120             @NonNull AdSelectionService adSelectionService) {
121         AdSelectionManager adSelectionManager = AdSelectionManager.get(context);
122         adSelectionManager.mAdIdManager = adIdManager;
123         adSelectionManager.mServiceProvider = () -> adSelectionService;
124         return adSelectionManager;
125     }
126 
127     /**
128      * Create AdSelectionManager
129      *
130      * @hide
131      */
AdSelectionManager(@onNull Context context)132     public AdSelectionManager(@NonNull Context context) {
133         Objects.requireNonNull(context);
134 
135         // Initialize the default service provider
136         mServiceProvider = this::doGetService;
137 
138         // In case the AdSelectionManager is initiated from inside a sdk_sandbox process the
139         // fields will be immediately rewritten by the initialize method below.
140         initialize(context);
141     }
142 
143     /**
144      * Initializes {@link AdSelectionManager} with the given {@code context}.
145      *
146      * <p>This method is called by the {@link SandboxedSdkContext} to propagate the correct context.
147      * For more information check the javadoc on the {@link
148      * android.app.sdksandbox.SdkSandboxSystemServiceRegistry}.
149      *
150      * @hide
151      * @see android.app.sdksandbox.SdkSandboxSystemServiceRegistry
152      */
initialize(@onNull Context context)153     public AdSelectionManager initialize(@NonNull Context context) {
154         Objects.requireNonNull(context);
155 
156         mContext = context;
157         mServiceBinder =
158                 ServiceBinder.getServiceBinder(
159                         context,
160                         AdServicesCommon.ACTION_AD_SELECTION_SERVICE,
161                         AdSelectionService.Stub::asInterface);
162         mAdIdManager = new AdIdCompatibleManager(context);
163         return this;
164     }
165 
166     @NonNull
getTestAdSelectionManager()167     public TestAdSelectionManager getTestAdSelectionManager() {
168         return new TestAdSelectionManager(this);
169     }
170 
171     /**
172      * Using this interface {@code getService}'s implementation is decoupled from the default {@link
173      * #doGetService()}. This allows us to inject mock instances of {@link AdSelectionService} to
174      * inspect and test the manager-service boundary.
175      */
176     interface ServiceProvider {
177         @NonNull
getService()178         AdSelectionService getService();
179     }
180 
181     @NonNull
getServiceProvider()182     ServiceProvider getServiceProvider() {
183         return mServiceProvider;
184     }
185 
186     @NonNull
doGetService()187     AdSelectionService doGetService() {
188         return mServiceBinder.getService();
189     }
190 
191     /**
192      * Collects custom audience data from device. Returns a compressed and encrypted blob to send to
193      * auction servers for ad selection. For more details, please visit <a
194      * href="https://developer.android.com/design-for-safety/privacy-sandbox/protected-audience-bidding-and-auction-services">Bidding
195      * and Auction Services Explainer</a>.
196      *
197      * <p>Custom audience ads must have a {@code ad_render_id} to be eligible for to be collected.
198      *
199      * <p>See {@link AdSelectionManager#persistAdSelectionResult} for how to process the results of
200      * the ad selection run on server-side with the blob generated by this API.
201      *
202      * <p>The output is passed by the receiver, which either returns an {@link
203      * GetAdSelectionDataOutcome} for a successful run, or an {@link Exception} includes the type of
204      * the exception thrown and the corresponding error message.
205      *
206      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
207      * the API received to run the ad selection.
208      *
209      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
210      * services.", it is caused by an internal failure of the ad selection service.
211      *
212      * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
213      * during bidding, scoring, or overall selection process to find winning Ad.
214      *
215      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
216      * exceeds the allowed rate limits and is throttled.
217      *
218      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
219      * or permission is not requested.
220      */
221     @RequiresPermission(
222             anyOf = {
223                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
224                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
225                 ACCESS_ADSERVICES_AD_SELECTION
226             })
getAdSelectionData( @onNull GetAdSelectionDataRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<GetAdSelectionDataOutcome, Exception> receiver)227     public void getAdSelectionData(
228             @NonNull GetAdSelectionDataRequest request,
229             @NonNull @CallbackExecutor Executor executor,
230             @NonNull OutcomeReceiver<GetAdSelectionDataOutcome, Exception> receiver) {
231         Objects.requireNonNull(request);
232         Objects.requireNonNull(executor);
233         Objects.requireNonNull(receiver);
234         try {
235             final AdSelectionService service = getServiceProvider().getService();
236             service.getAdSelectionData(
237                     new GetAdSelectionDataInput.Builder()
238                             .setSeller(request.getSeller())
239                             .setCallerPackageName(getCallerPackageName())
240                             .setCoordinatorOriginUri(request.getCoordinatorOriginUri())
241                             .build(),
242                     new CallerMetadata.Builder()
243                             .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
244                             .build(),
245                     new GetAdSelectionDataCallback.Stub() {
246                         @Override
247                         public void onSuccess(GetAdSelectionDataResponse resultParcel) {
248                             executor.execute(
249                                     () -> {
250                                         byte[] adSelectionData;
251                                         try {
252                                             adSelectionData = getAdSelectionData(resultParcel);
253                                         } catch (IOException e) {
254                                             receiver.onError(
255                                                     new IllegalStateException(
256                                                             "Unable to return the AdSelectionData",
257                                                             e));
258                                             return;
259                                         }
260                                         receiver.onResult(
261                                                 new GetAdSelectionDataOutcome.Builder()
262                                                         .setAdSelectionId(
263                                                                 resultParcel.getAdSelectionId())
264                                                         .setAdSelectionData(adSelectionData)
265                                                         .build());
266                                     });
267                         }
268 
269                         @Override
270                         public void onFailure(FledgeErrorResponse failureParcel) {
271                             executor.execute(
272                                     () -> {
273                                         receiver.onError(
274                                                 AdServicesStatusUtils.asException(failureParcel));
275                                     });
276                         }
277                     });
278         } catch (NullPointerException e) {
279             sLogger.e(e, "Unable to find the AdSelection service.");
280             receiver.onError(
281                     new IllegalStateException("Unable to find the AdSelection service.", e));
282         } catch (RemoteException e) {
283             sLogger.e(e, "Failure of AdSelection service.");
284             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
285         }
286     }
287 
288     /**
289      * Persists the ad selection results from the server-side. For more details, please visit <a
290      * href="https://developer.android.com/design-for-safety/privacy-sandbox/protected-audience-bidding-and-auction-services">Bidding
291      * and Auction Services Explainer</a>
292      *
293      * <p>See {@link AdSelectionManager#getAdSelectionData} for how to generate an encrypted blob to
294      * run an ad selection on the server side.
295      *
296      * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
297      * for a successful run, or an {@link Exception} includes the type of the exception thrown and
298      * the corresponding error message. The {@link AdSelectionOutcome#getAdSelectionId()} is not
299      * guaranteed to be the same as the {@link
300      * PersistAdSelectionResultRequest#getAdSelectionDataId()} or the deprecated {@link
301      * PersistAdSelectionResultRequest#getAdSelectionId()}.
302      *
303      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
304      * the API received to run the ad selection.
305      *
306      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
307      * services.", it is caused by an internal failure of the ad selection service.
308      *
309      * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
310      * during bidding, scoring, or overall selection process to find winning Ad.
311      *
312      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
313      * exceeds the allowed rate limits and is throttled.
314      *
315      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
316      * or permission is not requested.
317      */
318     @RequiresPermission(
319             anyOf = {
320                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
321                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
322                 ACCESS_ADSERVICES_AD_SELECTION
323             })
persistAdSelectionResult( @onNull PersistAdSelectionResultRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver)324     public void persistAdSelectionResult(
325             @NonNull PersistAdSelectionResultRequest request,
326             @NonNull @CallbackExecutor Executor executor,
327             @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
328         Objects.requireNonNull(request);
329         Objects.requireNonNull(executor);
330         Objects.requireNonNull(receiver);
331 
332         try {
333             final AdSelectionService service = getServiceProvider().getService();
334             service.persistAdSelectionResult(
335                     new PersistAdSelectionResultInput.Builder()
336                             .setSeller(request.getSeller())
337                             .setAdSelectionId(request.getAdSelectionId())
338                             .setAdSelectionResult(request.getAdSelectionResult())
339                             .setCallerPackageName(getCallerPackageName())
340                             .build(),
341                     new CallerMetadata.Builder()
342                             .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
343                             .build(),
344                     new PersistAdSelectionResultCallback.Stub() {
345                         @Override
346                         public void onSuccess(PersistAdSelectionResultResponse resultParcel) {
347                             executor.execute(
348                                     () ->
349                                             receiver.onResult(
350                                                     new AdSelectionOutcome.Builder()
351                                                             .setAdSelectionId(
352                                                                     resultParcel.getAdSelectionId())
353                                                             .setRenderUri(
354                                                                     resultParcel.getAdRenderUri())
355                                                             .build()));
356                         }
357 
358                         @Override
359                         public void onFailure(FledgeErrorResponse failureParcel) {
360                             executor.execute(
361                                     () -> {
362                                         receiver.onError(
363                                                 AdServicesStatusUtils.asException(failureParcel));
364                                     });
365                         }
366                     });
367         } catch (NullPointerException e) {
368             sLogger.e(e, "Unable to find the AdSelection service.");
369             receiver.onError(
370                     new IllegalStateException("Unable to find the AdSelection service.", e));
371         } catch (RemoteException e) {
372             sLogger.e(e, "Failure of AdSelection service.");
373             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
374         }
375     }
376 
377     /**
378      * Runs the ad selection process on device to select a remarketing ad for the caller
379      * application.
380      *
381      * <p>The input {@code adSelectionConfig} is provided by the Ads SDK and the {@link
382      * AdSelectionConfig} object is transferred via a Binder call. For this reason, the total size
383      * of these objects is bound to the Android IPC limitations. Failures to transfer the {@link
384      * AdSelectionConfig} will throws an {@link TransactionTooLargeException}.
385      *
386      * <p>The input {@code adSelectionConfig} contains {@code Decision Logic Uri} that could follow
387      * either the HTTPS or Ad Selection Prebuilt schemas.
388      *
389      * <p>If the URI follows HTTPS schema then the host should match the {@code seller}. Otherwise,
390      * {@link IllegalArgumentException} will be thrown.
391      *
392      * <p>Prebuilt URIs are a way of substituting a generic pre-built logics for the required
393      * JavaScripts for {@code scoreAds}. Prebuilt Uri for this endpoint should follow;
394      *
395      * <ul>
396      *   <li>{@code ad-selection-prebuilt://ad-selection/<name>?<script-generation-parameters>}
397      * </ul>
398      *
399      * <p>If an unsupported prebuilt URI is passed or prebuilt URI feature is disabled by the
400      * service then {@link IllegalArgumentException} will be thrown.
401      *
402      * <p>See {@link AdSelectionConfig.Builder#setDecisionLogicUri} for supported {@code <name>} and
403      * required {@code <script-generation-parameters>}.
404      *
405      * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
406      * for a successful run, or an {@link Exception} includes the type of the exception thrown and
407      * the corresponding error message.
408      *
409      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
410      * the API received to run the ad selection.
411      *
412      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
413      * services.", it is caused by an internal failure of the ad selection service.
414      *
415      * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
416      * during bidding, scoring, or overall selection process to find winning Ad.
417      *
418      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
419      * exceeds the allowed rate limits and is throttled.
420      *
421      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
422      * or permission is not requested.
423      */
424     @RequiresPermission(
425             anyOf = {
426                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
427                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
428                 ACCESS_ADSERVICES_AD_SELECTION
429             })
selectAds( @onNull AdSelectionConfig adSelectionConfig, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver)430     public void selectAds(
431             @NonNull AdSelectionConfig adSelectionConfig,
432             @NonNull @CallbackExecutor Executor executor,
433             @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
434         Objects.requireNonNull(adSelectionConfig);
435         Objects.requireNonNull(executor);
436         Objects.requireNonNull(receiver);
437 
438         try {
439             final AdSelectionService service = getServiceProvider().getService();
440             service.selectAds(
441                     new AdSelectionInput.Builder()
442                             .setAdSelectionConfig(adSelectionConfig)
443                             .setCallerPackageName(getCallerPackageName())
444                             .build(),
445                     new CallerMetadata.Builder()
446                             .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
447                             .build(),
448                     new AdSelectionCallback.Stub() {
449                         @Override
450                         public void onSuccess(AdSelectionResponse resultParcel) {
451                             executor.execute(
452                                     () ->
453                                             receiver.onResult(
454                                                     new AdSelectionOutcome.Builder()
455                                                             .setAdSelectionId(
456                                                                     resultParcel.getAdSelectionId())
457                                                             .setRenderUri(
458                                                                     resultParcel.getRenderUri())
459                                                             .build()));
460                         }
461 
462                         @Override
463                         public void onFailure(FledgeErrorResponse failureParcel) {
464                             executor.execute(
465                                     () -> {
466                                         receiver.onError(
467                                                 AdServicesStatusUtils.asException(failureParcel));
468                                     });
469                         }
470                     });
471         } catch (NullPointerException e) {
472             sLogger.e(e, "Unable to find the AdSelection service.");
473             receiver.onError(
474                     new IllegalStateException("Unable to find the AdSelection service.", e));
475         } catch (RemoteException e) {
476             sLogger.e(e, "Failure of AdSelection service.");
477             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
478         }
479     }
480 
481     /**
482      * Selects an ad from the results of previously ran ad selections.
483      *
484      * <p>The input {@code adSelectionFromOutcomesConfig} is provided by the Ads SDK and the {@link
485      * AdSelectionFromOutcomesConfig} object is transferred via a Binder call. For this reason, the
486      * total size of these objects is bound to the Android IPC limitations. Failures to transfer the
487      * {@link AdSelectionFromOutcomesConfig} will throws an {@link TransactionTooLargeException}.
488      *
489      * <p>The output is passed by the receiver, which either returns an {@link AdSelectionOutcome}
490      * for a successful run, or an {@link Exception} includes the type of the exception thrown and
491      * the corresponding error message.
492      *
493      * <p>The input {@code adSelectionFromOutcomesConfig} contains:
494      *
495      * <ul>
496      *   <li>{@code Seller} is required to be a registered {@link
497      *       android.adservices.common.AdTechIdentifier}. Otherwise, {@link IllegalStateException}
498      *       will be thrown.
499      *   <li>{@code List of ad selection ids} should exist and come from {@link
500      *       AdSelectionManager#selectAds} calls originated from the same application. Otherwise,
501      *       {@link IllegalArgumentException} for input validation will raise listing violating ad
502      *       selection ids.
503      *   <li>{@code Selection logic URI} that could follow either the HTTPS or Ad Selection Prebuilt
504      *       schemas.
505      *       <p>If the URI follows HTTPS schema then the host should match the {@code seller}.
506      *       Otherwise, {@link IllegalArgumentException} will be thrown.
507      *       <p>Prebuilt URIs are a way of substituting a generic pre-built logics for the required
508      *       JavaScripts for {@code selectOutcome}. Prebuilt Uri for this endpoint should follow;
509      *       <ul>
510      *         <li>{@code
511      *             ad-selection-prebuilt://ad-selection-from-outcomes/<name>?<script-generation-parameters>}
512      *       </ul>
513      *       <p>If an unsupported prebuilt URI is passed or prebuilt URI feature is disabled by the
514      *       service then {@link IllegalArgumentException} will be thrown.
515      *       <p>See {@link AdSelectionFromOutcomesConfig.Builder#setSelectionLogicUri} for supported
516      *       {@code <name>} and required {@code <script-generation-parameters>}.
517      * </ul>
518      *
519      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
520      * the API received to run the ad selection.
521      *
522      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
523      * services.", it is caused by an internal failure of the ad selection service.
524      *
525      * <p>If the {@link TimeoutException} is thrown, it is caused when a timeout is encountered
526      * during bidding, scoring, or overall selection process to find winning Ad.
527      *
528      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
529      * exceeds the allowed rate limits and is throttled.
530      *
531      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
532      * or permission is not requested.
533      */
534     @RequiresPermission(
535             anyOf = {
536                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
537                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
538                 ACCESS_ADSERVICES_AD_SELECTION
539             })
selectAds( @onNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver)540     public void selectAds(
541             @NonNull AdSelectionFromOutcomesConfig adSelectionFromOutcomesConfig,
542             @NonNull @CallbackExecutor Executor executor,
543             @NonNull OutcomeReceiver<AdSelectionOutcome, Exception> receiver) {
544         Objects.requireNonNull(adSelectionFromOutcomesConfig);
545         Objects.requireNonNull(executor);
546         Objects.requireNonNull(receiver);
547 
548         try {
549             final AdSelectionService service = getServiceProvider().getService();
550             service.selectAdsFromOutcomes(
551                     new AdSelectionFromOutcomesInput.Builder()
552                             .setAdSelectionFromOutcomesConfig(adSelectionFromOutcomesConfig)
553                             .setCallerPackageName(getCallerPackageName())
554                             .build(),
555                     new CallerMetadata.Builder()
556                             .setBinderElapsedTimestamp(SystemClock.elapsedRealtime())
557                             .build(),
558                     new AdSelectionCallback.Stub() {
559                         @Override
560                         public void onSuccess(AdSelectionResponse resultParcel) {
561                             executor.execute(
562                                     () -> {
563                                         if (resultParcel == null) {
564                                             receiver.onResult(AdSelectionOutcome.NO_OUTCOME);
565                                         } else {
566                                             receiver.onResult(
567                                                     new AdSelectionOutcome.Builder()
568                                                             .setAdSelectionId(
569                                                                     resultParcel.getAdSelectionId())
570                                                             .setRenderUri(
571                                                                     resultParcel.getRenderUri())
572                                                             .build());
573                                         }
574                                     });
575                         }
576 
577                         @Override
578                         public void onFailure(FledgeErrorResponse failureParcel) {
579                             executor.execute(
580                                     () -> {
581                                         receiver.onError(
582                                                 AdServicesStatusUtils.asException(failureParcel));
583                                     });
584                         }
585                     });
586         } catch (NullPointerException e) {
587             sLogger.e(e, "Unable to find the AdSelection service.");
588             receiver.onError(
589                     new IllegalStateException("Unable to find the AdSelection service.", e));
590         } catch (RemoteException e) {
591             sLogger.e(e, "Failure of AdSelection service.");
592             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
593         }
594     }
595 
596     /**
597      * Notifies the service that there is a new impression to report for the ad selected by the
598      * ad-selection run identified by {@code adSelectionId}. There is no guarantee about when the
599      * impression will be reported. The impression reporting could be delayed and reports could be
600      * batched.
601      *
602      * <p>To calculate the winning seller reporting URL, the service fetches the seller's JavaScript
603      * logic from the {@link AdSelectionConfig#getDecisionLogicUri()} found at {@link
604      * ReportImpressionRequest#getAdSelectionConfig()}. Then, the service executes one of the
605      * functions found in the seller JS called {@code reportResult}, providing on-device signals as
606      * well as {@link ReportImpressionRequest#getAdSelectionConfig()} as input parameters.
607      *
608      * <p>The function definition of {@code reportResult} is:
609      *
610      * <p>{@code function reportResult(ad_selection_config, render_url, bid, contextual_signals) {
611      * return { 'status': status, 'results': {'signals_for_buyer': signals_for_buyer,
612      * 'reporting_url': reporting_url } }; } }
613      *
614      * <p>To calculate the winning buyer reporting URL, the service fetches the winning buyer's
615      * JavaScript logic which is fetched via the buyer's {@link
616      * android.adservices.customaudience.CustomAudience#getBiddingLogicUri()}. Then, the service
617      * executes one of the functions found in the buyer JS called {@code reportWin}, providing
618      * on-device signals, {@code signals_for_buyer} calculated by {@code reportResult}, and specific
619      * fields from {@link ReportImpressionRequest#getAdSelectionConfig()} as input parameters.
620      *
621      * <p>The function definition of {@code reportWin} is:
622      *
623      * <p>{@code function reportWin(ad_selection_signals, per_buyer_signals, signals_for_buyer,
624      * contextual_signals, custom_audience_reporting_signals) { return {'status': 0, 'results':
625      * {'reporting_url': reporting_url } }; } }
626      *
627      * <p>In addition, buyers and sellers have the option to register to receive reports on specific
628      * ad events. To do so, they can invoke the platform provided {@code registerAdBeacon} function
629      * inside {@code reportWin} and {@code reportResult} for buyers and sellers, respectively.
630      *
631      * <p>The function definition of {@code registerBeacon} is:
632      *
633      * <p>{@code function registerAdBeacon(beacons)}, where {@code beacons} is a dict of string to
634      * string pairs
635      *
636      * <p>For each ad event a buyer/seller is interested in reports for, they would add an {@code
637      * event_key}: {@code event_reporting_uri} pair to the {@code beacons} dict, where {@code
638      * event_key} is an identifier for that specific event. This {@code event_key} should match
639      * {@link ReportEventRequest#getKey()} when the SDK invokes {@link #reportEvent}. In addition,
640      * each {@code event_reporting_uri} should parse properly into a {@link android.net.Uri}. This
641      * will be the {@link android.net.Uri} reported to when the SDK invokes {@link #reportEvent}.
642      *
643      * <p>When the buyer/seller has added all the pairings they want to receive events for, they can
644      * invoke {@code registerAdBeacon(beacons)}, where {@code beacons} is the name of the dict they
645      * added the pairs to.
646      *
647      * <p>{@code registerAdBeacon} will throw a {@code TypeError} in these situations:
648      *
649      * <ol>
650      *   <li>{@code registerAdBeacon}is called more than once. If this error is caught in
651      *       reportWin/reportResult, the original set of pairings will be registered
652      *   <li>{@code registerAdBeacon} doesn't have exactly 1 dict argument.
653      *   <li>The contents of the 1 dict argument are not all {@code String: String} pairings.
654      * </ol>
655      *
656      * <p>The output is passed by the {@code receiver}, which either returns an empty {@link Object}
657      * for a successful run, or an {@link Exception} includes the type of the exception thrown and
658      * the corresponding error message.
659      *
660      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
661      * the API received to report the impression.
662      *
663      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
664      * services.", it is caused by an internal failure of the ad selection service.
665      *
666      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
667      * exceeds the allowed rate limits and is throttled.
668      *
669      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
670      * or permission is not requested.
671      *
672      * <p>Impressions will be reported at most once as a best-effort attempt.
673      */
674     @RequiresPermission(
675             anyOf = {
676                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
677                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
678                 ACCESS_ADSERVICES_AD_SELECTION
679             })
reportImpression( @onNull ReportImpressionRequest request, @NonNull Executor executor, @NonNull OutcomeReceiver<Object, Exception> receiver)680     public void reportImpression(
681             @NonNull ReportImpressionRequest request,
682             @NonNull Executor executor,
683             @NonNull OutcomeReceiver<Object, Exception> receiver) {
684         Objects.requireNonNull(request);
685         Objects.requireNonNull(executor);
686         Objects.requireNonNull(receiver);
687 
688         try {
689             final AdSelectionService service = getServiceProvider().getService();
690             service.reportImpression(
691                     new ReportImpressionInput.Builder()
692                             .setAdSelectionId(request.getAdSelectionId())
693                             .setAdSelectionConfig(request.getAdSelectionConfig())
694                             .setCallerPackageName(getCallerPackageName())
695                             .build(),
696                     new ReportImpressionCallback.Stub() {
697                         @Override
698                         public void onSuccess() {
699                             executor.execute(() -> receiver.onResult(new Object()));
700                         }
701 
702                         @Override
703                         public void onFailure(FledgeErrorResponse failureParcel) {
704                             executor.execute(
705                                     () -> {
706                                         receiver.onError(
707                                                 AdServicesStatusUtils.asException(failureParcel));
708                                     });
709                         }
710                     });
711         } catch (NullPointerException e) {
712             sLogger.e(e, "Unable to find the AdSelection service.");
713             receiver.onError(
714                     new IllegalStateException("Unable to find the AdSelection service.", e));
715         } catch (RemoteException e) {
716             sLogger.e(e, "Exception");
717             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
718         }
719     }
720 
721     /**
722      * Notifies the service that there is a new ad event to report for the ad selected by the
723      * ad-selection run identified by {@code adSelectionId}. An ad event is any occurrence that
724      * happens to an ad associated with the given {@code adSelectionId}. There is no guarantee about
725      * when the ad event will be reported. The event reporting could be delayed and reports could be
726      * batched.
727      *
728      * <p>Using {@link ReportEventRequest#getKey()}, the service will fetch the {@code reportingUri}
729      * that was registered in {@code registerAdBeacon}. See documentation of {@link
730      * #reportImpression} for more details regarding {@code registerAdBeacon}. Then, the service
731      * will attach {@link ReportEventRequest#getData()} to the request body of a POST request and
732      * send the request. The body of the POST request will have the {@code content-type} of {@code
733      * text/plain}, and the data will be transmitted in {@code charset=UTF-8}.
734      *
735      * <p>The output is passed by the receiver, which either returns an empty {@link Object} for a
736      * successful run, or an {@link Exception} includes the type of the exception thrown and the
737      * corresponding error message.
738      *
739      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
740      * the API received to report the ad event.
741      *
742      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
743      * services.", it is caused by an internal failure of the ad selection service.
744      *
745      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
746      * exceeds the allowed rate limits and is throttled.
747      *
748      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
749      * or permission is not requested.
750      *
751      * <p>Events will be reported at most once as a best-effort attempt.
752      */
753     @RequiresPermission(
754             anyOf = {
755                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
756                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
757                 ACCESS_ADSERVICES_AD_SELECTION
758             })
reportEvent( @onNull ReportEventRequest request, @NonNull Executor executor, @NonNull OutcomeReceiver<Object, Exception> receiver)759     public void reportEvent(
760             @NonNull ReportEventRequest request,
761             @NonNull Executor executor,
762             @NonNull OutcomeReceiver<Object, Exception> receiver) {
763         Objects.requireNonNull(request);
764         Objects.requireNonNull(executor);
765         Objects.requireNonNull(receiver);
766         try {
767             ReportInteractionInput.Builder inputBuilder =
768                     new ReportInteractionInput.Builder()
769                             .setAdSelectionId(request.getAdSelectionId())
770                             .setInteractionKey(request.getKey())
771                             .setInteractionData(request.getData())
772                             .setReportingDestinations(request.getReportingDestinations())
773                             .setCallerPackageName(getCallerPackageName())
774                             .setCallerSdkName(getCallerSdkName())
775                             .setInputEvent(request.getInputEvent());
776 
777             getAdId((adIdValue) -> inputBuilder.setAdId(adIdValue));
778 
779             final AdSelectionService service = getServiceProvider().getService();
780             service.reportInteraction(
781                     inputBuilder.build(),
782                     new ReportInteractionCallback.Stub() {
783                         @Override
784                         public void onSuccess() {
785                             executor.execute(() -> receiver.onResult(new Object()));
786                         }
787 
788                         @Override
789                         public void onFailure(FledgeErrorResponse failureParcel) {
790                             executor.execute(
791                                     () -> {
792                                         receiver.onError(
793                                                 AdServicesStatusUtils.asException(failureParcel));
794                                     });
795                         }
796                     });
797         } catch (NullPointerException e) {
798             sLogger.e(e, "Unable to find the AdSelection service.");
799             receiver.onError(
800                     new IllegalStateException("Unable to find the AdSelection service.", e));
801         } catch (RemoteException e) {
802             sLogger.e(e, "Exception");
803             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
804         }
805     }
806 
807     /**
808      * Gives the provided list of adtechs the ability to do app install filtering on the calling
809      * app.
810      *
811      * <p>The input {@code request} is provided by the Ads SDK and the {@code request} object is
812      * transferred via a Binder call. For this reason, the total size of these objects is bound to
813      * the Android IPC limitations. Failures to transfer the {@code advertisers} will throws an
814      * {@link TransactionTooLargeException}.
815      *
816      * <p>The output is passed by the receiver, which either returns an empty {@link Object} for a
817      * successful run, or an {@link Exception} includes the type of the exception thrown and the
818      * corresponding error message.
819      *
820      * <p>If the {@link IllegalArgumentException} is thrown, it is caused by invalid input argument
821      * the API received.
822      *
823      * <p>If the {@link IllegalStateException} is thrown with error message "Failure of AdSelection
824      * services.", it is caused by an internal failure of the ad selection service.
825      *
826      * <p>If the {@link LimitExceededException} is thrown, it is caused when the calling package
827      * exceeds the allowed rate limits and is throttled.
828      *
829      * <p>If the {@link SecurityException} is thrown, it is caused when the caller is not authorized
830      * or permission is not requested.
831      */
832     @FlaggedApi(Flags.FLAG_FLEDGE_AD_SELECTION_FILTERING_ENABLED)
833     @RequiresPermission(
834             anyOf = {
835                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
836                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
837                 ACCESS_ADSERVICES_AD_SELECTION
838             })
setAppInstallAdvertisers( @onNull SetAppInstallAdvertisersRequest request, @NonNull Executor executor, @NonNull OutcomeReceiver<Object, Exception> receiver)839     public void setAppInstallAdvertisers(
840             @NonNull SetAppInstallAdvertisersRequest request,
841             @NonNull Executor executor,
842             @NonNull OutcomeReceiver<Object, Exception> receiver) {
843         Objects.requireNonNull(request);
844         Objects.requireNonNull(executor);
845         Objects.requireNonNull(receiver);
846 
847         try {
848             final AdSelectionService service = getServiceProvider().getService();
849             service.setAppInstallAdvertisers(
850                     new SetAppInstallAdvertisersInput.Builder()
851                             .setAdvertisers(request.getAdvertisers())
852                             .setCallerPackageName(getCallerPackageName())
853                             .build(),
854                     new SetAppInstallAdvertisersCallback.Stub() {
855                         @Override
856                         public void onSuccess() {
857                             executor.execute(() -> receiver.onResult(new Object()));
858                         }
859 
860                         @Override
861                         public void onFailure(FledgeErrorResponse failureParcel) {
862                             executor.execute(
863                                     () -> {
864                                         receiver.onError(
865                                                 AdServicesStatusUtils.asException(failureParcel));
866                                     });
867                         }
868                     });
869         } catch (NullPointerException e) {
870             sLogger.e(e, "Unable to find the AdSelection service.");
871             receiver.onError(
872                     new IllegalStateException("Unable to find the AdSelection service.", e));
873         } catch (RemoteException e) {
874             sLogger.e(e, "Exception");
875             receiver.onError(new IllegalStateException("Failure of AdSelection service.", e));
876         }
877     }
878 
879     /**
880      * Updates the counter histograms for an ad which was previously selected by a call to {@link
881      * #selectAds(AdSelectionConfig, Executor, OutcomeReceiver)}.
882      *
883      * <p>The counter histograms are used in ad selection to inform frequency cap filtering on
884      * candidate ads, where ads whose frequency caps are met or exceeded are removed from the
885      * bidding process during ad selection.
886      *
887      * <p>Counter histograms can only be updated for ads specified by the given {@code
888      * adSelectionId} returned by a recent call to FLEDGE ad selection from the same caller app.
889      *
890      * <p>A {@link SecurityException} is returned via the {@code outcomeReceiver} if:
891      *
892      * <ol>
893      *   <li>the app has not declared the correct permissions in its manifest, or
894      *   <li>the app or entity identified by the {@code callerAdTechIdentifier} are not authorized
895      *       to use the API.
896      * </ol>
897      *
898      * An {@link IllegalStateException} is returned via the {@code outcomeReceiver} if the call does
899      * not come from an app with a foreground activity.
900      *
901      * <p>A {@link LimitExceededException} is returned via the {@code outcomeReceiver} if the call
902      * exceeds the calling app's API throttle.
903      *
904      * <p>In all other failure cases, the {@code outcomeReceiver} will return an empty {@link
905      * Object}. Note that to protect user privacy, internal errors will not be sent back via an
906      * exception.
907      */
908     @RequiresPermission(
909             anyOf = {
910                 ACCESS_ADSERVICES_CUSTOM_AUDIENCE,
911                 ACCESS_ADSERVICES_PROTECTED_SIGNALS,
912                 ACCESS_ADSERVICES_AD_SELECTION
913             })
updateAdCounterHistogram( @onNull UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver)914     public void updateAdCounterHistogram(
915             @NonNull UpdateAdCounterHistogramRequest updateAdCounterHistogramRequest,
916             @NonNull @CallbackExecutor Executor executor,
917             @NonNull OutcomeReceiver<Object, Exception> outcomeReceiver) {
918         Objects.requireNonNull(updateAdCounterHistogramRequest, "Request must not be null");
919         Objects.requireNonNull(executor, "Executor must not be null");
920         Objects.requireNonNull(outcomeReceiver, "Outcome receiver must not be null");
921 
922         try {
923             final AdSelectionService service = getServiceProvider().getService();
924             Objects.requireNonNull(service);
925             service.updateAdCounterHistogram(
926                     new UpdateAdCounterHistogramInput.Builder(
927                                     updateAdCounterHistogramRequest.getAdSelectionId(),
928                                     updateAdCounterHistogramRequest.getAdEventType(),
929                                     updateAdCounterHistogramRequest.getCallerAdTech(),
930                                     getCallerPackageName())
931                             .build(),
932                     new UpdateAdCounterHistogramCallback.Stub() {
933                         @Override
934                         public void onSuccess() {
935                             executor.execute(() -> outcomeReceiver.onResult(new Object()));
936                         }
937 
938                         @Override
939                         public void onFailure(FledgeErrorResponse failureParcel) {
940                             executor.execute(
941                                     () -> {
942                                         outcomeReceiver.onError(
943                                                 AdServicesStatusUtils.asException(failureParcel));
944                                     });
945                         }
946                     });
947         } catch (NullPointerException e) {
948             sLogger.e(e, "Unable to find the AdSelection service");
949             outcomeReceiver.onError(
950                     new IllegalStateException("Unable to find the AdSelection service", e));
951         } catch (RemoteException e) {
952             sLogger.e(e, "Remote exception encountered while updating ad counter histogram");
953             outcomeReceiver.onError(new IllegalStateException("Failure of AdSelection service", e));
954         }
955     }
956 
getCallerPackageName()957     private String getCallerPackageName() {
958         SandboxedSdkContext sandboxedSdkContext =
959                 SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
960         return sandboxedSdkContext == null
961                 ? mContext.getPackageName()
962                 : sandboxedSdkContext.getClientPackageName();
963     }
964 
getAdSelectionData(GetAdSelectionDataResponse response)965     private byte[] getAdSelectionData(GetAdSelectionDataResponse response) throws IOException {
966         if (Objects.nonNull(response.getAssetFileDescriptor())) {
967             AssetFileDescriptor assetFileDescriptor = response.getAssetFileDescriptor();
968             return AssetFileDescriptorUtil.readAssetFileDescriptorIntoBuffer(assetFileDescriptor);
969         } else {
970             return response.getAdSelectionData();
971         }
972     }
973 
getCallerSdkName()974     private String getCallerSdkName() {
975         SandboxedSdkContext sandboxedSdkContext =
976                 SandboxedSdkContextUtils.getAsSandboxedSdkContext(mContext);
977         return sandboxedSdkContext == null ? "" : sandboxedSdkContext.getSdkPackageName();
978     }
979 
980     private interface AdSelectionAdIdCallback {
onResult(@ullable String adIdValue)981         void onResult(@Nullable String adIdValue);
982     }
983 
984     @SuppressLint("MissingPermission")
getAdId(AdSelectionAdIdCallback adSelectionAdIdCallback)985     private void getAdId(AdSelectionAdIdCallback adSelectionAdIdCallback) {
986         try {
987             CountDownLatch timer = new CountDownLatch(1);
988             AtomicReference<String> adIdValue = new AtomicReference<>();
989             mAdIdManager.getAdId(
990                     mAdIdExecutor,
991                     new android.adservices.common.AdServicesOutcomeReceiver<>() {
992                         @Override
993                         public void onResult(AdId adId) {
994                             String id = adId.getAdId();
995                             adIdValue.set(!AdId.ZERO_OUT.equals(id) ? id : null);
996                             sLogger.v("AdId permission enabled: %b.", !AdId.ZERO_OUT.equals(id));
997                             timer.countDown();
998                         }
999 
1000                         @Override
1001                         public void onError(Exception e) {
1002                             if (e instanceof IllegalStateException
1003                                     || e instanceof SecurityException) {
1004                                 sLogger.w(DEBUG_API_WARNING_MESSAGE);
1005                             } else {
1006                                 sLogger.w(e, DEBUG_API_WARNING_MESSAGE);
1007                             }
1008                             timer.countDown();
1009                         }
1010                     });
1011 
1012             boolean timedOut = false;
1013             try {
1014                 timedOut = !timer.await(AD_ID_TIMEOUT_MS, MILLISECONDS);
1015             } catch (InterruptedException e) {
1016                 sLogger.w(e, "Interrupted while getting the AdId.");
1017             }
1018             if (timedOut) {
1019                 sLogger.w("AdId call timed out.");
1020             }
1021             adSelectionAdIdCallback.onResult(adIdValue.get());
1022         } catch (Exception e) {
1023             sLogger.d(e, "Could not get AdId.");
1024             adSelectionAdIdCallback.onResult(null);
1025         }
1026     }
1027 }
1028