A custom audience represents a group of users with common intentions or interests as decided by an advertiser app. An app or SDK may use a custom audience to indicate a particular audience, such as someone who has left items in a shopping cart.
The Android Protected Audience API can be used to join and leave custom audiences on a user's device. When you create and join a custom audience, you can either delegate to a server from which you'll fetch some or all of the custom audience properties, or you can specify this information when calling the API directly.
Custom audiences
The combination of the following parameters uniquely identifies each
CustomAudience
object on a device:
owner
: Package name of the owner app. This is implicitly set to the package name of the caller app.buyer
: Identifier for the buyer ad network which manages ads for this custom audience.name
: An arbitrary name or identifier for the custom audience.
Additionally, the CustomAudience
must be created with these required
parameters:
- Daily update URL: An HTTPS URL queried daily in the background to update a custom audience's user bidding signals, trusted bidding data, and render URLs and metadata for ads.
- Bidding logic URL: An HTTPS URL queried during ad selection to fetch a buyer's JavaScript bidding logic. See the required function signatures in this JavaScript.
- Ad Render IDs: An arbitrary ID set by the buyer ad tech. This is an optimization for generating the payload for B&A.
Optional parameters for a CustomAudience
object may include:
- Activation time: A custom audience can only participate in ad selection and daily updates after its activation time. This can be useful to engage lapsed users of an app, for example.
- Expiration time: A future time after which the custom audience is removed from the device.
- User bidding signals: A JSON string containing user signals, such as the user's preferred locale, that a buyer's bidding logic JavaScript consumes to generate bids during the ad selection process. This format helps ad tech platforms reuse code across platforms and eases the consumption in JavaScript functions.
- Trusted bidding data: An HTTPS URL and a list of strings used during the ad selection process that fetch bidding signals from a trusted Key/Value service.
- Ads: A list of
AdData
objects corresponding to the ads that participate in ad selection. EachAdData
object consists of:- Render URL: An HTTPS URL that is queried to render the final ad.
- Metadata: A JSON object serialized as a string containing information to be consumed by buyer bidding logic during the ad selection process.
- Ad Filters: A class that contains all necessary information for app install ad filtering and frequency capping during ad selection.
Fetch and join a custom audience
The fetchAndJoinCustomAudience
API allows buyers to delegate joining a custom
audience by leveraging the on-device presence of their partner MMPs or SSPs.
For this to work, the on-device caller (whether that be an MMP or an SSP SDK)
creates a fetchAndJoinCustomAudienceRequest
that looks like the following:
Kotlin
/**
* @param fetchUri The URL to retrieve the CA from.
* (optional)@param name The name of the CA to join.
* (optional)@param activationTime The time when the CA will activate.
* (optional)@param expirationTime The time when the CA will expire,
must be a time in the future otherwise this will fail
* (optional)@param userBiddingSignals The user bidding signals used at auction.
*/
val request = FetchAndJoinCustomAudienceRequest.Builder(fetchUri)
.setName(name)
.setActivationTime(activationTime)
.setExpirationTime(expirationTime)
.setUserBiddingSignals(userBiddingSignals)
.build()
Java
/**
* @param fetchUri The URL to retrieve the CA from.
* (optional)@param name The name of the CA to join.
* (optional)@param activationTime The time when the CA will activate.
* (optional)@param expirationTime The time when the CA will expire,
must be a time in the future otherwise this will fail
* (optional)@param userBiddingSignals The user bidding signals used at auction.
*/
FetchAndJoinCustomAudienceRequest request =
new FetchAndJoinCustomAudienceRequest.Builder(fetchUri)
.setName(name) //Optional
.setActivationTime(activationTime) //Optional
.setExpirationTime(expirationTime) //Optional
.setUserBiddingSignals(userBiddingSignals) //Optional
.build();
An important note about all of the optional parameters is that if they are set inside of the fetch request, their data cannot be overridden by what is returned from the Buyer, giving the on-device caller additional controls over what custom audience is persisted.
The fetchUri
should point to a server endpoint operated by the Buyer
which will return a Custom Audience JSON object that matches the format seen
here:
//Return a 200 response with data matching the format of the following in the body
{
"daily_update_uri": "https://js.example.com/bidding/daily",
"bidding_logic_uri": "https://js.example.com/bidding",
"user_bidding_signals": {
"valid": true,
"arbitrary": "yes"
},
"trusted_bidding_data": {
"trusted_bidding_uri": "https://js.example.com/bidding/trusted",
"trusted_bidding_keys": [
"key1",
"key2"
]
},
"ads": [
{
"render_uri": "https://js.example.com/render/fetch_and_join_ad1",
"metadata": {
"valid": 1
}
},
{
"render_uri": "https://js.example.com/render/fetch_and_join_ad2",
"metadata": {
"valid": 2
}
}
]
}
Further information about how this resolves on the API side can be found in the Design Proposal for Join CA Delegation.
Testing
Once you've implemented the Fetch call inside of the client code and have an endpoint set up on the DSP side to return the Custom Audience Data, you can test delegation of joining a Custom Audience. Before running your app, you will need to run the commands on the Testing setup page. Once you've run these commands, you should be able to start successfully making calls using the Fetch API.
To see an example of this flow, fetch calls have been added to the Privacy Sandbox Samples repository on GitHub.
Join a custom audience directly
If you already have all of the information you need to create and join a custom audience, you can do so directly using an asynchronous Protected Audience API call. To create or join a custom audience directly, do the following:
- Initialize the
CustomAudienceManager
object. - Create a
CustomAudience
object by specifying key parameters such as the buyer's package and a relevant name. Then, initialize theJoinCustomAudienceRequest
object with theCustomAudience
object. - Call the asynchronous
joinCustomAudience()
with theJoinCustomAudienceRequest
object and relevantExecutor
andOutcomeReceiver
objects.
Kotlin
val customAudienceManager: CustomAudienceManager =
context.getSystemService(CustomAudienceManager::class.java)
// Minimal initialization of a CustomAudience object
val audience: CustomAudience = CustomAudience.Builder()
.setBuyer(AdTechIdentifier.fromString("my.buyer.domain.name"))
.setName("example-custom-audience-name")
.setDailyUpdateUrl(Uri.parse("https://DAILY_UPDATE_URL"))
.setBiddingLogicUrl(Uri.parse("https://BIDDING_LOGIC_URL"))
.build()
// Initialize a custom audience request.
val joinCustomAudienceRequest: JoinCustomAudienceRequest =
JoinCustomAudienceRequest.Builder().setCustomAudience(audience).build()
// Request to join a custom audience.
customAudienceManager.joinCustomAudience(joinCustomAudienceRequest,
executor,
outcomeReceiver)
Java
CustomAudienceManager customAudienceManager =
context.getSystemService(CustomAudienceManager.class);
// Minimal initialization of a CustomAudience object
CustomAudience audience = CustomAudience.Builder()
.setBuyer(AdTechIdentifier.fromString("my.buyer.domain.name"))
.setName("example-custom-audience-name")
.setDailyUpdateUrl(Uri.parse("https://DAILY_UPDATE_URL"))
.setBiddingLogicUrl(Uri.parse("https://BIDDING_LOGIC_URL"))
.build();
// Initialize a custom audience request.
JoinCustomAudienceRequest joinCustomAudienceRequest =
new JoinCustomAudienceRequest.Builder().setCustomAudience(audience).build();
// Request to join a custom audience.
customAudienceManager.joinCustomAudience(joinCustomAudienceRequest,
executor,
outcomeReceiver);
Handle joinCustomAudience() outcomes
The asynchronous joinCustomAudience()
method uses the OutcomeReceiver
object to signal the result of the API call.
- The
onResult()
callback signifies the custom audience is successfully created or updated. - The
onError()
callback signifies two possible conditions.- If the
JoinCustomAudienceRequest
is initialized with invalid arguments, theAdServicesException
indicates anIllegalArgumentException
as the cause. - All other errors receive an
AdServicesException
with anIllegalStateException
as the cause.
- If the
Here's an example of handling the outcome of joinCustomAudience()
:
Kotlin
var callback: OutcomeReceiver<Void, AdServicesException> =
object : OutcomeReceiver<Void, AdServicesException> {
override fun onResult(result: Void) {
Log.i("CustomAudience", "Completed joinCustomAudience")
}
override fun onError(error: AdServicesException) {
// Handle error
Log.e("CustomAudience", "Error executing joinCustomAudience", error)
}
};
Java
OutcomeReceiver callback = new OutcomeReceiver<Void, AdServicesException>() {
@Override
public void onResult(@NonNull Void result) {
Log.i("CustomAudience", "Completed joinCustomAudience");
}
@Override
public void onError(@NonNull AdServicesException error) {
// Handle error
Log.e("CustomAudience", "Error executing joinCustomAudience", error);
}
};
Leave a custom audience
If the user no longer satisfies the business criteria for a given custom
audience, an app or SDK can call leaveCustomAudience()
to remove the custom
audience from the device. To remove a CustomAudience
based on its unique
parameters, do the following:
- Initialize the
CustomAudienceManager
object. - Initialize the
LeaveCustomAudienceRequest
with the custom audience'sbuyer
andname
. To learn more about these input fields, read "Join a custom audience directly." - Call the asynchronous
leaveCustomAudience()
method with theLeaveCustomAudienceRequest
object and relevantExecutor
andOutcomeReceiver
objects.
Kotlin
val customAudienceManager: CustomAudienceManager =
context.getSystemService(CustomAudienceManager::class.java)
// Initialize a LeaveCustomAudienceRequest
val leaveCustomAudienceRequest: LeaveCustomAudienceRequest =
LeaveCustomAudienceRequest.Builder()
.setBuyer(buyer)
.setName(name)
.build()
// Request to leave a custom audience
customAudienceManager.leaveCustomAudience(
leaveCustomAudienceRequest,
executor,
outcomeReceiver)
Java
CustomAudienceManager customAudienceManager =
context.getSystemService(CustomAudienceManager.class);
// Initialize a LeaveCustomAudienceRequest
LeaveCustomAudienceRequest leaveCustomAudienceRequest =
new LeaveCustomAudienceRequest.Builder()
.setBuyer(buyer)
.setName(name)
.build();
// Request to leave a custom audience
customAudienceManager.leaveCustomAudience(
leaveCustomAudienceRequest,
executor,
outcomeReceiver);
Similar to calling joinCustomAudience()
, the OutcomeReceiver
signals
the end of an API call. To help protect privacy, an error outcome doesn't
distinguish between internal errors and invalid arguments. The onResult()
callback is called when the API call has completed, whether or not a matching
custom audience is removed successfully.