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