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