Define audience data

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:

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. Each AdData 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:

/**
 * @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()
/**
 * @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:

  1. Initialize the CustomAudienceManager object.
  2. Create a CustomAudience object by specifying key parameters such as the buyer's package and a relevant name. Then, initialize the JoinCustomAudienceRequest object with the CustomAudience object.
  3. Call the asynchronous joinCustomAudience() with the JoinCustomAudienceRequest object and relevant Executor and OutcomeReceiver objects.
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)
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.

Here's an example of handling the outcome of joinCustomAudience():

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)
    }
};
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:

  1. Initialize the CustomAudienceManager object.
  2. Initialize the LeaveCustomAudienceRequest with the custom audience's buyer and name. To learn more about these input fields, read "Join a custom audience directly."
  3. Call the asynchronous leaveCustomAudience() method with the LeaveCustomAudienceRequest object and relevant Executor and OutcomeReceiver objects.
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)
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.