1 /* 2 * Copyright 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 androidx.privacysandbox.ads.adservices.java.adselection 18 19 import android.adservices.common.AdServicesPermissions 20 import android.content.Context 21 import android.os.LimitExceededException 22 import android.os.TransactionTooLargeException 23 import androidx.annotation.DoNotInline 24 import androidx.annotation.RequiresPermission 25 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig 26 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionFromOutcomesConfig 27 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager 28 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion.obtain 29 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome 30 import androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataOutcome 31 import androidx.privacysandbox.ads.adservices.adselection.GetAdSelectionDataRequest 32 import androidx.privacysandbox.ads.adservices.adselection.PersistAdSelectionResultRequest 33 import androidx.privacysandbox.ads.adservices.adselection.ReportEventRequest 34 import androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest 35 import androidx.privacysandbox.ads.adservices.adselection.UpdateAdCounterHistogramRequest 36 import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures 37 import androidx.privacysandbox.ads.adservices.java.internal.asListenableFuture 38 import com.google.common.util.concurrent.ListenableFuture 39 import java.util.concurrent.TimeoutException 40 import kotlinx.coroutines.CoroutineScope 41 import kotlinx.coroutines.Dispatchers 42 import kotlinx.coroutines.async 43 44 /** 45 * This class provides APIs to select ads and report impressions. This class can be used by Java 46 * clients. 47 */ 48 @OptIn(ExperimentalFeatures.Ext8OptIn::class) 49 abstract class AdSelectionManagerFutures internal constructor() { 50 51 /** 52 * Runs the ad selection process on device to select a remarketing ad for the caller 53 * application. 54 * 55 * @param adSelectionConfig the config The input {@code adSelectionConfig} is provided by the 56 * Ads SDK and the [AdSelectionConfig] object is transferred via a Binder call. For this 57 * reason, the total size of these objects is bound to the Android IPC limitations. Failures 58 * to transfer the [AdSelectionConfig] will throws an [TransactionTooLargeException]. 59 * 60 * The output is passed by the receiver, which either returns an [AdSelectionOutcome] for a 61 * successful run, or an [Exception] includes the type of the exception thrown and the 62 * corresponding error message. 63 * 64 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 65 * received to run the ad selection. 66 * 67 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 68 * services.", it is caused by an internal failure of the ad selection service. 69 * 70 * If the [TimeoutException] is thrown, it is caused when a timeout is encountered during 71 * bidding, scoring, or overall selection process to find winning Ad. 72 * 73 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 74 * allowed rate limits and is throttled. 75 */ 76 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) selectAdsAsyncnull77 abstract fun selectAdsAsync( 78 adSelectionConfig: AdSelectionConfig 79 ): ListenableFuture<AdSelectionOutcome> 80 81 /** 82 * Selects an ad from the results of previously ran ad selections. 83 * 84 * @param adSelectionFromOutcomesConfig is provided by the Ads SDK and the 85 * [AdSelectionFromOutcomesConfig] object is transferred via a Binder call. For this reason, 86 * the total size of these objects is bound to the Android IPC limitations. Failures to 87 * transfer the [AdSelectionFromOutcomesConfig] will throw an [TransactionTooLargeException]. 88 * 89 * The output is passed by the receiver, which either returns an [AdSelectionOutcome] for a 90 * successful run, or an [Exception] includes the type of the exception thrown and the 91 * corresponding error message. 92 * 93 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 94 * received to run the ad selection. 95 * 96 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 97 * services.", it is caused by an internal failure of the ad selection service. 98 * 99 * If the [TimeoutException] is thrown, it is caused when a timeout is encountered during 100 * bidding, scoring, or overall selection process to find winning Ad. 101 * 102 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 103 * allowed rate limits and is throttled. 104 * 105 * If the [SecurityException] is thrown, it is caused when the caller is not authorized or 106 * permission is not requested. 107 * 108 * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and 109 * AdServices module versions don't support this API. 110 */ 111 @ExperimentalFeatures.Ext10OptIn 112 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 113 abstract fun selectAdsAsync( 114 adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig 115 ): ListenableFuture<AdSelectionOutcome> 116 117 /** 118 * Report the given impression. The [ReportImpressionRequest] is provided by the Ads SDK. The 119 * receiver either returns a {@code void} for a successful run, or an [Exception] indicates the 120 * error. 121 * 122 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 123 * received to report the impression. 124 * 125 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 126 * services.", it is caused by an internal failure of the ad selection service. 127 * 128 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 129 * allowed rate limits and is throttled. 130 * 131 * If the [SecurityException] is thrown, it is caused when the caller is not authorized or 132 * permission is not requested. 133 * 134 * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and 135 * AdServices module versions don't support [ReportImpressionRequest] with null {@code 136 * AdSelectionConfig} 137 * 138 * @param reportImpressionRequest the request for reporting impression. 139 */ 140 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 141 abstract fun reportImpressionAsync( 142 reportImpressionRequest: ReportImpressionRequest 143 ): ListenableFuture<Unit> 144 145 /** 146 * Notifies the service that there is a new ad event to report for the ad selected by the 147 * ad-selection run identified by {@code adSelectionId}. An ad event is any occurrence that 148 * happens to an ad associated with the given {@code adSelectionId}. There is no guarantee about 149 * when the ad event will be reported. The event reporting could be delayed and reports could be 150 * batched. 151 * 152 * Using [ReportEventRequest#getKey()], the service will fetch the {@code reportingUri} that was 153 * registered in {@code registerAdBeacon}. See documentation of [reportImpressionAsync] for more 154 * details regarding {@code registerAdBeacon}. Then, the service will attach 155 * [ReportEventRequest#getData()] to the request body of a POST request and send the request. 156 * The body of the POST request will have the {@code content-type} of {@code text/plain}, and 157 * the data will be transmitted in {@code charset=UTF-8}. 158 * 159 * The output is passed by the receiver, which either returns an empty [Object] for a successful 160 * run, or an [Exception] includes the type of the exception thrown and the corresponding error 161 * message. 162 * 163 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 164 * received to report the ad event. 165 * 166 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 167 * services.", it is caused by an internal failure of the ad selection service. 168 * 169 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 170 * allowed rate limits and is throttled. 171 * 172 * If the [SecurityException] is thrown, it is caused when the caller is not authorized or 173 * permission is not requested. 174 * 175 * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and 176 * AdServices module versions don't support this API. 177 * 178 * Events will be reported at most once as a best-effort attempt. 179 * 180 * @param reportEventRequest the request for reporting event. 181 */ 182 @ExperimentalFeatures.Ext8OptIn 183 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 184 abstract fun reportEventAsync(reportEventRequest: ReportEventRequest): ListenableFuture<Unit> 185 186 /** 187 * Updates the counter histograms for an ad which was previously selected by a call to 188 * [selectAdsAsync]. 189 * 190 * The counter histograms are used in ad selection to inform frequency cap filtering on 191 * candidate ads, where ads whose frequency caps are met or exceeded are removed from the 192 * bidding process during ad selection. 193 * 194 * Counter histograms can only be updated for ads specified by the given {@code adSelectionId} 195 * returned by a recent call to Protected Audience API ad selection from the same caller app. 196 * 197 * A [SecurityException] is returned if: 198 * <ol> 199 * <li>the app has not declared the correct permissions in its manifest, or 200 * <li>the app or entity identified by the {@code callerAdTechIdentifier} are not authorized to 201 * use the API. 202 * </ol> 203 * 204 * An [IllegalStateException] is returned if the call does not come from an app with a 205 * foreground activity. 206 * 207 * A [LimitExceededException] is returned if the call exceeds the calling app's API throttle. 208 * 209 * An [UnsupportedOperationException] is returned if the Android API level and AdServices module 210 * versions don't support this API. 211 * 212 * In all other failure cases, it will return an empty [Object]. Note that to protect user 213 * privacy, internal errors will not be sent back via an exception. 214 * 215 * @param updateAdCounterHistogramRequest the request for updating the ad counter histogram. 216 */ 217 @ExperimentalFeatures.Ext8OptIn 218 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 219 abstract fun updateAdCounterHistogramAsync( 220 updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest 221 ): ListenableFuture<Unit> 222 223 /** 224 * Collects custom audience data from device. Returns a compressed and encrypted blob to send to 225 * auction servers for ad selection. 226 * 227 * Custom audience ads must have a {@code ad_render_id} to be eligible for to be collected. 228 * 229 * See [AdSelectionManager#persistAdSelectionResult] for how to process the results of the ad 230 * selection run on server-side with the blob generated by this API. 231 * 232 * The output is passed by the receiver, which either returns an [GetAdSelectionDataOutcome] for 233 * a successful run, or an [Exception] includes the type of the exception thrown and the 234 * corresponding error message. 235 * 236 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 237 * received to run the ad selection. 238 * 239 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 240 * services.", it is caused by an internal failure of the ad selection service. 241 * 242 * If the [TimeoutException] is thrown, it is caused when a timeout is encountered during 243 * bidding, scoring, or overall selection process to find winning Ad. 244 * 245 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 246 * allowed rate limits and is throttled. 247 * 248 * If the [SecurityException] is thrown, it is caused when the caller is not authorized or 249 * permission is not requested. 250 * 251 * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and 252 * AdServices module versions don't support this API. 253 * 254 * @param getAdSelectionDataRequest the request for get ad selection data. 255 */ 256 @ExperimentalFeatures.Ext10OptIn 257 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 258 abstract fun getAdSelectionDataAsync( 259 getAdSelectionDataRequest: GetAdSelectionDataRequest 260 ): ListenableFuture<GetAdSelectionDataOutcome> 261 262 /** 263 * Persists the ad selection results from the server-side. 264 * 265 * See [AdSelectionManager#getAdSelectionData] for how to generate an encrypted blob to run an 266 * ad selection on the server side. 267 * 268 * The output is passed by the receiver, which either returns an [AdSelectionOutcome] for a 269 * successful run, or an [Exception] includes the type of the exception thrown and the 270 * corresponding error message. 271 * 272 * If the [IllegalArgumentException] is thrown, it is caused by invalid input argument the API 273 * received to run the ad selection. 274 * 275 * If the [IllegalStateException] is thrown with error message "Failure of AdSelection 276 * services.", it is caused by an internal failure of the ad selection service. 277 * 278 * If the [TimeoutException] is thrown, it is caused when a timeout is encountered during 279 * bidding, scoring, or overall selection process to find winning Ad. 280 * 281 * If the [LimitExceededException] is thrown, it is caused when the calling package exceeds the 282 * allowed rate limits and is throttled. 283 * 284 * If the [SecurityException] is thrown, it is caused when the caller is not authorized or 285 * permission is not requested. 286 * 287 * If the [UnsupportedOperationException] is thrown, it is caused when the Android API level and 288 * AdServices module versions don't support this API. 289 * 290 * @param persistAdSelectionResultRequest the request for persist ad selection result. 291 */ 292 @ExperimentalFeatures.Ext10OptIn 293 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 294 abstract fun persistAdSelectionResultAsync( 295 persistAdSelectionResultRequest: PersistAdSelectionResultRequest 296 ): ListenableFuture<AdSelectionOutcome> 297 298 private class Api33Ext4JavaImpl(private val mAdSelectionManager: AdSelectionManager?) : 299 AdSelectionManagerFutures() { 300 @DoNotInline 301 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 302 override fun selectAdsAsync( 303 adSelectionConfig: AdSelectionConfig 304 ): ListenableFuture<AdSelectionOutcome> { 305 return CoroutineScope(Dispatchers.Default) 306 .async { mAdSelectionManager!!.selectAds(adSelectionConfig) } 307 .asListenableFuture() 308 } 309 310 @OptIn(ExperimentalFeatures.Ext10OptIn::class) 311 @DoNotInline 312 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 313 override fun selectAdsAsync( 314 adSelectionFromOutcomesConfig: AdSelectionFromOutcomesConfig 315 ): ListenableFuture<AdSelectionOutcome> { 316 return CoroutineScope(Dispatchers.Default) 317 .async { mAdSelectionManager!!.selectAds(adSelectionFromOutcomesConfig) } 318 .asListenableFuture() 319 } 320 321 @DoNotInline 322 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 323 override fun reportImpressionAsync( 324 reportImpressionRequest: ReportImpressionRequest 325 ): ListenableFuture<Unit> { 326 return CoroutineScope(Dispatchers.Default) 327 .async { mAdSelectionManager!!.reportImpression(reportImpressionRequest) } 328 .asListenableFuture() 329 } 330 331 @DoNotInline 332 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 333 override fun updateAdCounterHistogramAsync( 334 updateAdCounterHistogramRequest: UpdateAdCounterHistogramRequest 335 ): ListenableFuture<Unit> { 336 return CoroutineScope(Dispatchers.Default) 337 .async { 338 mAdSelectionManager!!.updateAdCounterHistogram(updateAdCounterHistogramRequest) 339 } 340 .asListenableFuture() 341 } 342 343 @OptIn(ExperimentalFeatures.Ext8OptIn::class) 344 @DoNotInline 345 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 346 override fun reportEventAsync( 347 reportEventRequest: ReportEventRequest 348 ): ListenableFuture<Unit> { 349 return CoroutineScope(Dispatchers.Default) 350 .async { mAdSelectionManager!!.reportEvent(reportEventRequest) } 351 .asListenableFuture() 352 } 353 354 @OptIn(ExperimentalFeatures.Ext10OptIn::class) 355 @DoNotInline 356 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 357 override fun getAdSelectionDataAsync( 358 getAdSelectionDataRequest: GetAdSelectionDataRequest 359 ): ListenableFuture<GetAdSelectionDataOutcome> { 360 return CoroutineScope(Dispatchers.Default) 361 .async { mAdSelectionManager!!.getAdSelectionData(getAdSelectionDataRequest) } 362 .asListenableFuture() 363 } 364 365 @OptIn(ExperimentalFeatures.Ext10OptIn::class) 366 @DoNotInline 367 @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE) 368 override fun persistAdSelectionResultAsync( 369 persistAdSelectionResultRequest: PersistAdSelectionResultRequest 370 ): ListenableFuture<AdSelectionOutcome> { 371 return CoroutineScope(Dispatchers.Default) 372 .async { 373 mAdSelectionManager!!.persistAdSelectionResult(persistAdSelectionResultRequest) 374 } 375 .asListenableFuture() 376 } 377 } 378 379 companion object { 380 /** 381 * Creates [AdSelectionManagerFutures]. 382 * 383 * @return AdSelectionManagerFutures object. If the device is running an incompatible build, 384 * the value returned is null. 385 */ 386 @JvmStatic fromnull387 fun from(context: Context): AdSelectionManagerFutures? { 388 return obtain(context)?.let { Api33Ext4JavaImpl(it) } 389 } 390 } 391 } 392