FLEDGE on Android developer guide

Stay organized with collections Save and categorize content based on your preferences.

FLEDGE on Android includes the Custom Audience API and the Ad Selection API. Ad tech platforms and advertisers can use these APIs to serve customized ads based on previous app engagement that limits the sharing of identifiers across apps and limits sharing a user's app interaction information with third-parties.

The Custom Audience API is centered around the "custom audience" abstraction, which represents a group of users with common intentions. An advertiser can register a user with a custom audience and associate relevant ads with it. This information is stored locally and can be used to inform advertiser bids, ad filtering, and ad rendering.

The Ad Selection API provides a framework that allows multiple developers to run an auction locally for a custom audience. To achieve this, the system considers relevant ads associated with the custom audience and performs additional processing on ads that an ad tech platform returns to the device.

Ad tech platforms can integrate these APIs to implement remarketing that preserves user privacy. Support for additional use cases, including app install ads, are planned for future releases. Learn more about FLEDGE on Android in the design proposal.

This developer guide describes how to work with FLEDGE on Android to do the following:

  1. Manage custom audiences
  2. Set up and run ad selection on a device
  3. Report ad impressions

Before you begin

Before you get started, complete the following:

  1. Set up your development environment for the Privacy Sandbox on Android.
  2. Either install a system image onto a supported device or set up an emulator that includes support for the Privacy Sandbox on Android.
  3. In a terminal, enable access to the FLEDGE API (disabled by default) with the following adb command.

      adb shell device_config put adservices ppapi_app_allow_list \"*\"
    
  4. Include an ACCESS_ADSERVICES_CUSTOM_AUDIENCE permission in your app manifest:

      <uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
    
  5. Reference an ad services configuration in the <application> element of your manifest:

      <property android:name="android.adservices.AD_SERVICES_CONFIG"
                android:resource="@xml/ad_services_config" />
    
  6. Specify the ad services XML resource referenced in your manifest, such as res/xml/ad_services_config.xml. Learn more about ad services permissions and SDK access control.

      <ad-services-config>
        <custom-audiences allowAllToAccess="true" />
      </ad-services-config>
    
  7. By default, the Ad Selection API enforces limits on the maximum amount of memory that an auction or impression reporting script can allocate. The memory limitation feature requires WebView version 105.0.5195.58 or higher. The platform enforces a version check and calls to the selectAds and reportImpression APIs will fail if this isn’t satisfied. There are two options to set this up:

    • Option 1: Run the following adb command to disable this check:

      adb device_config put fledge_js_isolate_enforce_max_heap_size false
      
    • Option 2: Install WebView Beta from the Google Play store. This must be equal to or higher than the version stated above.

Join a custom audience

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. To create or join a custom audience asynchronously, 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.

Kotlin

val customAudienceManager: CustomAudienceManager =
    context.getSystemService(CustomAudienceManager::class.java)

// Initialize a custom audience.
val audience = CustomAudience.Builder()
    .setBuyer(buyer)
    .setName(name)
    ...
    .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);

// Initialize a custom audience.
CustomAudience audience = new CustomAudience.Builder()
    .setBuyer(buyer)
    .setName(name)
    ...
    .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);

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.

Calling joinCustomAudience() repeatedly with a different instance of CustomAudience will update any existing CustomAudience with matching owner, buyer, and name parameters. To help preserve privacy, the result of the API does not distinguish between "creation" and "update."

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.

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 will be 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 will consume 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 server.
  • Ads: A list of AdData objects corresponding to the ads which will 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.

Here's an example of a CustomAudience object instantiation:

Kotlin

// Minimal initialization of a CustomAudience object
val customAudience: 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()

Java

// Minimal initialization of a CustomAudience object
CustomAudience 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();

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():

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:

  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."
  3. Call the asynchronous leaveCustomAudience() method with the LeaveCustomAudienceRequest object and relevant Executor and OutcomeReceiver objects.

Kotlin

val customAudienceManager: CustomAudienceManager =
    context.getSystemService(CustomAudienceManager::class.java)

// Initialize a LeaveCustomAudienceRequest
val leaveCustomAudienceRequest: LeaveCustomAudienceRequest =
    JoinCustomAudienceRequest.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 JoinCustomAudienceRequest.Builder()
        .setBuyer(buyer)
        .setName(name)
        .build();

// Request to leave a custom audience
customAudienceManager.leaveCustomAudience(
    leaveCustomAudienceRequest,
    executor,
    outcomeReceiver);

Similar to calling joinCustomAudience(), the OutcomeReceiver will signal the end of an API call. To help protect privacy, an error outcome will not 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.

Run ad selection

To use FLEDGE to select ads, call the selectAds() method:

  1. Initialize an AdSelectionManager object.
  2. Build an AdSelectionConfig object.
  3. Call the asynchronous selectAds() method with the AdSelectionConfig object and relevant Executor and OutcomeReceiver objects.

Kotlin

val adSelectionManager: AdSelectionManager =
    context.getSystemService(AdSelectionManager::class.java)

// Initialize AdSelectionConfig
val adSelectionConfig: AdSelectionConfig =
        AdSelectionConfig.Builder()
                .setSeller(seller)
                .setDecisionLogicUrl(decisionLogicUrl)
                .setCustomAudienceBuyers(customAudienceBuyers)
                .setAdSelectionSignals(adSelectionSignals)
                .setSellerSignals(sellerSignals)
                .setPerBuyerSignals(perBuyerSignals)
                .build()

// Run ad selection with AdSelectionConfig
adSelectionManager.selectAds(
    adSelectionConfig,
    executor,
    outcomeReceiver)

Java

AdSelectionManager adSelectionManager =
    context.getSystemService(AdSelectionManager.class);

// Initialize AdSelectionConfig
AdSelectionConfig adSelectionConfig =
        new AdSelectionConfig.Builder()
                .setSeller(seller)
                .setDecisionLogicUrl(decisionLogicUrl)
                .setCustomAudienceBuyers(customAudienceBuyers)
                .setAdSelectionSignals(adSelectionSignals)
                .setSellerSignals(sellerSignals)
                .setPerBuyerSignals(perBuyerSignals)
                .build();

// Run ad selection with AdSelectionConfig
adSelectionManager.selectAds(
    adSelectionConfig,
    executor,
    outcomeReceiver);

The selectAds() method requires an AdSelectionConfig input, where you must specify the following required parameters:

  • Seller: Identifier for the seller ad network initiating the ad selection.
  • Decision logic URL: An HTTPS URL queried to obtain the seller ad network’s JavaScript logic. See the required function signatures in this JavaScript.
  • Custom audience buyers: A full list of identifiers for buyer ad networks that are allowed by the seller to participate in the ad selection process. These buyer identifiers correspond to CustomAudience.getBuyer() of participating custom audiences.

The following parameters can be optionally specified for more customized ad selection:

  • Ad selection signals: A JSON object, serialized as a string, containing signals to be consumed by buyer bidding logic JavaScript fetched from CustomAudience.getBiddingLogicUrl().
  • Seller signals: A JSON object, serialized as a string, containing signals consumed by the seller’s fetched JavaScript decision logic from AdSelectionConfig.getDecisionLogicUrl().
  • Per buyer signals: A map of JSON objects, serialized as strings, containing signals to be consumed by specific buyers’ bidding logic JavaScript fetched from CustomAudience.getBiddingLogicUrl(), which are identified by the buyer fields of participating custom audiences.

Once an ad is selected, the results, bids, and signals are persisted internally for later reporting. From the OutcomeReceiver.onResult() callback, you’ll get back an AdSelectionOutcome that contains:

  • A render URL for the winning ad, obtained from AdData.getRenderUrl().
  • An ad selection ID unique to the device user. This ID is used for reporting the ad impression.

If the ad selection can’t be completed successfully due to reasons such as invalid arguments, timeouts, or excessive resource consumption, the OutcomeReceiver.onError() callback will provide an AdServicesException with the following behaviors:

  • If the ad selection is initiated with invalid arguments, the AdServicesException will indicate an IllegalArgumentException as the cause.
  • All other errors will receive an AdServicesException with an IllegalStateException as the cause.

Report an ad impression

After a winning ad has been chosen from the ad selection workflow, you can report the impression back to participating buy-side and sell-side platforms with the AdSelectionManager.reportImpression() method. To report an ad impression:

  1. Initialize an AdSelectionManager object.
  2. Build a ReportImpressionRequest object with the ad selection ID.
  3. Call the asynchronous reportImpression() method with the AdSelectionConfig object and relevant Executor and OutcomeReceiver objects.

Java

AdSelectionManager adSelectionManager =
    context.getSystemService(AdSelectionManager.class);

// Initialize a ReportImpressionRequest
ReportImpressionRequest adSelectionConfig =
        new ReportImpressionRequest.Builder()
                .setAdSelectionId(adSelectionId)
                .setAdSelectionConfig(adSelectionConfig);
                .build();

// Request to report the impression with the ReportImpressionRequest
adSelectionManager.reportImpression(
    reportImpressionRequest,
    executor,
    outcomeReceiver);

Kotlin

val adSelectionManager = context.getSystemService(AdSelectionManager::class.java)

// Initialize a ReportImpressionRequest
val adSelectionConfig: ReportImpressionRequest =
    ReportImpressionRequest.Builder()
        .setAdSelectionId(adSelectionId)
        .setAdSelectionConfig(adSelectionConfig);
        .build()

// Request to report the impression with the ReportImpressionRequest
adSelectionManager.reportImpression(
    reportImpressionRequest,
    executor,
    outcomeReceiver)

Initialize the ReportImpressionRequest with the following required parameters:

  • Ad selection ID: An ID unique only to a device user that identifies a successful ad selection.
  • Ad selection config: The same configuration used in the selectAds() call identified by the provided ad selection ID.

The asynchronous reportImpression() method uses the OutcomeReceiver object to signal the result of the API call.

  • The onResult() callback indicates if the ad selection has been completed.
  • The onError() callback indicates the following possible conditions:
    • If the call is initialized with an invalid input argument, the AdServicesException will indicate an IllegalArgumentException as the cause.
    • All other errors will receive an AdServicesException with an IllegalStateException as the cause.

Impression reporting endpoints

The report impression API will issue HTTPS GET requests to endpoints provided by the sell-side platform and the winning buy-side platform:

Buy-side platform endpoint:

  • The API will use the Bidding logic URL specified in the custom audience to fetch the buyer’s bidding logic JavaScript.
  • Invoke the reportResult() JavaScript function, which is expected to return the buyer's impression reporting URL.

Sell-side platform endpoint:

  • Use the Decision logic URL specified in the AdSelectionConfig object to fetch the seller’s decision logic JavaScript.
  • Invoke the reportWin() JavaScript function, which is expected to return the buyer impression reporting URL.

Best effort Impression reporting

The reportImpression() is designed to offer a best-effort completion of reporting.

Daily background update

When creating a custom audience, your app or SDK can initialize custom audience metadata. Additionally, the platform can update the following pieces of custom audience metadata with a daily background update process.

  • User bidding signals
  • Trusted bidding data
  • AdData list

This process queries against the Daily update URL defined in the custom audience and the URL may return a JSON response.

  • The JSON response may contain any of the supported metadata fields that needs to be updated.
  • Each JSON field is validated independently. The client will ignore any malformed fields which will result in no updates to that particular field in the response.
  • An empty HTTP response or an empty JSON object “{}” will result in no metadata updates.
  • The response message size must be limited to 10 KB.
  • All URIs are required to use HTTPS.
  • trusted_bidding_uri must share the same ETLD+1 as the buyer.

Example: JSON response for background daily update

{
    "user_bidding_signals" : { ... },  // Valid JSON object
    "trusted_bidding_data" : {
        "trusted_bidding_uri" : 'example-dsp1-key-value-service.com',
        "trusted_bidding_keys" : [ 'campaign123', 'campaign456', ... ]
    },
    'ads' : [
        {
            "render_uri" : 'www.example-dsp1.com/.../campaign123.html',
            'metadata' : { ... }  // Valid JSON object
        },
        {
            "render_uri" : 'www.example-dsp1.com/.../campaign456.html',
            'metadata' : { ... }  // Valid JSON object
        },
        ...
    ]
}

JavaScript for ad selection

The ad selection workflow orchestrates the execution of buyer-provided and seller-provided JavaScript.

Buyer-provided JavaScript is fetched from the Bidding logic URL specified in the custom audience. The returned JavaScript should include the following functions:

Seller-provided JavaScript is fetched from the decision logic URL specified in the AdSelectionConfig parameter for the ad selection API. The returned JavaScript should include the following functions:

generateBid()

function generateBid(
   ad,
   auction_signals,
   per_buyer_signals,
   trusted_bidding_signals,
   contextual_signals,
   user_signals,
   custom_audience_signals) {
  return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };
}

Input parameters:

  • ad: a JSON object with the following format var ad = { 'render_url': url, 'metadata': json_metadata };
  • auction_signals, per_buyer_signals: JSON objects, they are specified in the auction configuration object
  • custom_audience_signals: JSON object generated by the platform. The format for this JSON object is:

    var custom_audience_signals = {
      "owner":"ca_owner",
      "buyer":"ca_buyer",
      "name":"ca_name",
      "activation_time":"ca_activation_time_epoch_ms",
      "expiration_time":"ca_expiration_time_epoch_ms",
      "user_bidding_signals":"ca_user_bidding_signals"
    }
    

    where:

    • owner, buyer, and name are string taken from the properties with the same name of the Custom Audience participating to the ad selection
    • activation_time and expiration_time are the time of activation and expiration of the custom audience, expressed as seconds since the Unix epoch
    • ca_user_bidding_signals is a JSON string specified in the userBiddingSignals field of the CustomAudience at creation time
    • trusted_bidding_signals, contextual_signals, and user_signals are JSON objects. They are currently passed as empty objects and will be filled up in future releases. Their format is not enforced by the platform and is managed by the ad tech.

Result:

  • ad: is the ad the bid refers to. The script is allowed to return a copy of the ad it received with different metadata. The render_url property of the ad is expected to be unaltered.
  • bid: a float value representing the bid value for this ad
  • status: an integer value that can be:
    • 0: for a successful execution
    • 1: (or any non-zero value) in case any of the input signals is invalid. In case a non-zero value is returned by generate-bid the bidding process is invalidated for all the CA ads

scoreAd()

function scoreAd(
  ad,
  bid,
  ad_selection_config,
  seller_signals,
  trusted_scoring_signals,
  contextual_signal,
  user_signal,
  custom_audience_signal) {
    return {'status': 0, 'score': score };
}

Input parameters:

  • ad: see the generateBid documentation
  • bid: the bid value for the ad
  • ad_selection_config: a JSON object representing the AdSelectionConfig parameter of the selectAds API. The format is:

    var ad_selection_config = {
      'seller': 'seller',
      'decision_logic_url': 'url_of_decision_logic',
      'custom_audience_buyers': ['buyer1', 'buyer2'],
      'auction_signals': auction_signals,
      'per_buyer_signals': per_buyer_signals,
      'contextual_ads': [ad1, ad2]
    }
    
  • seller_signals: JSON objects read from the sellerSignals AdSelectionConfig API parameter

  • trusted_scoring_signal: read from the adSelectionSignals field in the AdSelectionConfig API parameter

  • contextual_signals, user_signals: JSON objects. They are currently passed as empty objects and will be filled up in future releases. Their format is not enforced by the platform and is managed by the ad-tech

  • per_buyer_signals: JSON object read from the perBuyerSignal map in the AdSelectionConfig API parameter using as key the current Custom Audience buyer. Empty if the map doesn’t contain any entry for the given buyer.

Output:

  • score: a float value representing the score value for this ad
  • status: an integer value that can be:
    • 0: for a successful execution
    • 1: in case the customAudienceSignals are invalid
    • 2: in case the AdSelectionConfig is invalid
    • 3: in case any of the other signals is invalid
    • Any non-zero value will cause the failure of the process, the value would determine the type of exception thrown

reportResult()

function reportResult(ad_selection_config, render_url, bid, contextual_signals) {
   return {
      'status': status,
      'results': {'signals_for_buyer': signals_for_buyer, 'reporting_url': reporting_url }
   };
}

Input parameters:

  • ad_selection_config: see the documentation of scoreAds
  • render_url: the render URL of the winning ad
  • bid: the bid offered for the winning ad
  • contextual_signals: see the documentation of generateBid

Output:

  • status: 0 for success and non-zero for failure
  • results: a JSON objects containing:
    • signals_for_buyer: a JSON object that will be passed to the reportWin function
    • reporting_url: a URL that will be used by the platform to notify the impression to the buyer

reportWin()

function reportWin(
   ad_selection_signals,
   per_buyer_signals,
   signals_for_buyer,
   contextual_signals,
   custom_audience_signals) {
   return {'status': 0, 'results': {'reporting_url': reporting_url } };
}

Input parameters:

  • ad_selection_signals, per_buyer_signals: see the documentation of scoreAds
  • signals_for_buyer: a JSON object returned by reportResult
  • contextual_signals, custom_audience_signals: see the documentation for generateBid

Output:

  • status: 0 for success and non-zero for failure
  • results: a JSON objects containing:
    • reporting_url: a URL that will be used by the platform to notify the impression to the seller

Testing

To help you get started with FLEDGE, we've created sample apps in Kotlin and Java, which can be found on GitHub.

Prerequisites

FLEDGE requires some JavaScript during ad selection and impression reporting. There are two methods of providing this JavaScript in a testing environment:

  • Run a server with the required HTTPS endpoints that returns the JavaScript
  • Override remote fetching by providing the necessary code from a local source

Either approach requires setting up an HTTPS endpoint to handle impression reporting.

HTTPS endpoints

To test ad selection and impression reporting, you will need to set up 7 HTTPS endpoints that your test device or emulator can access:

  1. Buyer endpoint that serves the bidding logic JavaScript.
  2. An endpoint that serves the bidding signals.
  3. Seller endpoint that serves the decision logic JavaScript.
  4. An endpoint that serves scoring signals.
  5. Winning buyer impression reporting endpoint.
  6. Seller impression reporting endpoint.
  7. An endpoint to serve the daily updates for a custom audience.

For convenience, the GitHub repo provides basic JavaScript code for testing purposes. It also includes OpenAPI service definitions which can be deployed to a supported mock or microservices platform. For more details, see the project README.

Override remote fetching of JavaScript

This feature is intended to be used for end-to-end testing. To override remote fetching, your app must run in debug mode with developer options enabled.

To enable debug mode for your application, add the following line to the application attribute in your AndroidManifest.xml:

<application
  android:debuggable="true">

For an example of how to use these overrides, please see the FLEDGE sample app on GitHub.

You need to add your own custom JavaScript to handle ad selection routines such as bidding, scoring decisions, and reporting. You can find basic JavaScript code examples that handle all required requests in the GitHub repo. The FLEDGE sample application demonstrates how to read code from that file and prepare it for use as an override.

It is possible to override sell-side and buy-side JavaScript fetching independently, though you will need an HTTPS endpoint to serve any JavaScript you aren’t providing overrides for. Please see the README for information on how to set up a server that handles these cases.

It is only possible to override JavaScript fetching for custom audiences that are owned by your package.

Override sell-side JavaScript

To set up an override of sell-side JavaScript, do the following as demonstrated in the code example below:

  1. Initialize an AdSelectionManager object.
  2. Get a reference to TestAdSelectionManager from the AdSelectionManager object.
  3. Build an AdSelectionConfig object.
  4. Build an AddAdSelectionOverrideRequest with the AdSelectionConfig object and a String representing the JavaScript you intend to use as an override.
  5. Call the asynchronous overrideAdSelectionConfigRemoteInfo() method with the AddAdSelectionOverrideRequest object and relevant Executor and OutcomeReceiver objects.

Kotlin

val testAdSelectionManager: TestAdSelectionManager =
  context.getSystemService(AdSelectionManager::class.java).getTestAdSelectionManager()

// Initialize AdSelectionConfig =
val adSelectionConfig = new AdSelectionConfig.Builder()
    .setSeller(seller)
    .setDecisionLogicUrl(decisionLogicUrl)
    .setCustomAudienceBuyers(customAudienceBuyers)
    .setAdSelectionSignals(adSelectionSignals)
    .setSellerSignals(sellerSignals)
    .setPerBuyerSignals(perBuyerSignals)
    .build()

// Initialize AddAddSelectionOverrideRequest
val request = AddAdSelectionOverrideRequest.Builder()
    .setAdSelectionConfig(adSelectionConfig)
    .setDecisionLogicJs(decisionLogicJS)
    .build()

// Run the call to override the JavaScript for the given AdSelectionConfig
// Note that this only takes effect in apps marked as debuggable
testAdSelectionManager.overrideAdSelectionConfigRemoteInfo(
    request,
    executor,
    outComeReceiver)

Java

TestAdSelectionManager testAdSelectionManager =
  context.getSystemService(AdSelectionManager.class).getTestAdSelectionManager();

// Initialize AdSelectionConfig =
AdSelectionConfig adSelectionConfig = new AdSelectionConfig.Builder()
    .setSeller(seller)
    .setDecisionLogicUrl(decisionLogicUrl)
    .setCustomAudienceBuyers(customAudienceBuyers)
    .setAdSelectionSignals(adSelectionSignals)
    .setSellerSignals(sellerSignals)
    .setPerBuyerSignals(perBuyerSignals)
    .build();

// Initialize AddAddSelectionOverrideRequest
AddAdSelectionOverrideRequest request = AddAdSelectionOverrideRequest.Builder()
    .setAdSelectionConfig(adSelectionConfig)
    .setDecisionLogicJs(decisionLogicJS)
    .build();

// Run the call to override the JavaScript for the given AdSelectionConfig
// Note that this only takes effect in apps marked as debuggable
testAdSelectionManager.overrideAdSelectionConfigRemoteInfo(
    request,
    executor,
    outComeReceiver);

See the Run ad selection section for more information on what each of the fields in the AdSelectionConfig represent. The key difference is that the decisionLogicUrl can be set to a placeholder value as it will be ignored.

In order to override the JavaScript used during ad selection, the decisionLogicJs must contain the proper seller-side function signatures. For an example of how to read a JavaScript file as a string, please see the FLEDGE sample app on GitHub.

The asynchronous overrideAdSelectionConfigRemoteInfo() method uses the OutcomeReceiver object to signal the result of the API call.

The onResult() callback signifies the override was applied successfully. Future calls to selectAds() will use whatever decision and reporting logic you have passed in as the override.

The onError() callback signifies two possible conditions:

  • If the override is attempted with invalid arguments, the AdServiceException will indicate an IllegalArgumentException as the cause.
  • If the override is attempted with an app not running in debug mode with developer options enabled, the AdServiceException will indicate IllegalStateException as the cause.

Reset sell-side overrides

This section assumes that you have overridden the sell-side JavaScript and that you have a reference to the TestAdSelectionManager and AdSelectionConfig used in the previous section.

In order to reset the overrides for all AdSelectionConfigs:

  1. Call the asynchronous resetAllAdSelectionConfigRemoteOverrides() method with the relevant OutcomeReceiver object.

Kotlin

// Resets overrides for all AdSelectionConfigs
testAadSelectionManager.resetAllAdSelectionConfigRemoteOverrides(
  outComeReceiver)

Java

// Resets overrides for all AdSelectionConfigs
testAdSelectionManager.resetAllAdSelectionConfigRemoteOverrides(
    outComeReceiver);

After you reset sell-side overrides, any future calls to selectAds() will use whatever decisionLogicUrl is stored in the AdSelectionConfig to attempt to fetch the required JavaScript.

If the call to resetAllAdSelectionConfigRemoteOverrides() fails, the OutComeReceiver.onError() callback will provide an AdServiceException. If the removal of overrides is attempted with an app not running in debug mode with developer options enabled, the AdServiceException will indicate IllegalStateException as the cause.

Override buy-side JavaScript

  1. Follow the steps to join a custom audience
  2. Build an AddCustomAudienceOverrideRequest with the buyer and name of the custom audience you wish to override, in addition to the bidding logic and data you wish to use as an override
  3. Call the asynchronous overrideCustomAudienceRemoteInfo() method with the AddCustomAudienceOverrideRequest object and relevant Executor and OutcomeReceiver objects

Kotlin

val testCustomAudienceManager: TestCustomAudienceManager =
  context.getSystemService(CustomAudienceManager::class.java).getTestCustomAudienceManager()

// Join custom audience

// Build the AddCustomAudienceOverrideRequest
val request = AddCustomAudienceOverrideRequest.Builder()
    .setBuyer(buyer)
    .setName(name)
    .setBiddingLogicJs(biddingLogicJS)
    .setTrustedBiddingSignals(trustedBiddingSignals)
    .build()

// Run the call to override JavaScript for the given custom audience
testCustomAudienceManager.overrideCustomAudienceRemoteInfo(
    request,
    executor,
    outComeReceiver)

Java

TestCustomAudienceManager testCustomAudienceManager =
  context.getSystemService(CustomAudienceManager.class).getTestCustomAudienceManager();

// Join custom audience

// Build the AddCustomAudienceOverrideRequest
AddCustomAudienceOverrideRequest request =
    AddCustomAudienceOverrideRequest.Builder()
        .setBuyer(buyer)
        .setName(name)
        .setBiddingLogicJs(biddingLogicJS)
        .setTrustedBiddingSignals(trustedBiddingSignals)
        .build();

// Run the call to override JavaScript for the given custom audience
testCustomAudienceManager.overrideCustomAudienceRemoteInfo(
    request,
    executor,
    outComeReceiver);

The values for buyer and name are the same ones used to create the custom audience. Learn more about these fields.

Additionally, you can specify two additional parameters:

  • biddingLogicJs: JavaScript that holds the buyer’s logic to be used during ad selection. See the required function signatures in this JavaScript.
  • trustedBiddingSignals: Bidding signals to be used during ad selection. For testing purposes this can be an empty string.

The asynchronous overrideCustomAudienceRemoteInfo() method uses the OutcomeReceiver object to signal the result of the API call.

The onResult() callback signifies the override was applied successfully. Future calls to selectAds() will use whatever bidding and reporting logic you have passed in as the override.

The onError() callback signifies two possible conditions.

  • If the override is attempted with invalid arguments, the AdServiceException will indicate an IllegalArgumentException as the cause.
  • If the override is attempted with an app not running in debug mode with developer options enabled, the AdServiceException will indicate IllegalStateException as the cause.

Reset buy-side overrides

This section assumes that you have overridden the buy-side JavaScript and that you have a reference to the TestCustomAudienceManager used in the previous section.

To reset overrides for all custom audiences:

  1. Call the asynchronous resetAllCustomAudienceOverrides() method with relevant Executor and OutcomeReceiver objects.

Kotlin

// Resets overrides for all custom audiences
testCustomAudienceManager.resetCustomAudienceRemoteInfoOverride(
    executor,
    outComeReceiver)

Java

// Resets overrides for all custom audiences
testCustomAudienceManager.resetCustomAudienceRemoteInfoOverride(
    executor,
    outComeReceiver)

After you reset buy-side overrides, any future calls to selectAds() will use whatever biddingLogicUrl and trustedBiddingData is stored in the CustomAudience to attempt to fetch the required JavaScript.

If the call to resetCustomAudienceRemoteInfoOverride() fails, the OutComeReceiver.onError() callback will provide an AdServiceException. If the removal of overrides is attempted with an app not running in debug mode with developer options enabled, the AdServiceException will indicate IllegalStateException as the cause.

Set Up a Reporting Server

When you use remote fetching overrides, you'll still need to set up a server that your device or emulator can reach to respond to reporting events. A simple endpoint that returns 200 is sufficient for testing. The GitHub repo includes OpenAPI service definitions which can be deployed to a supported mock or microservices platform. For more details, see the project README file.

When looking for the OpenAPI definitions, look for the reporting-server.json. This file contains a simple endpoint that returns 200, representing an HTTP response code. This endpoint is used during selectAds() and signals to FLEDGE that impression reporting completed successfully.

Functionality to test

  • Exercise joining/leaving and setting up a custom audience based on prior user actions.
  • Exercise the initiation of on-device ad selection through JavaScripts hosted remotely.
  • Observe how an app’s association with custom audience settings may affect ad selection outcomes.
  • Exercise impression reporting after ad selection.

Limitations

The following table lists limitations for FLEDGE processing. The limits presented could be subject to change based on feedback. For in-progress capabilities, read the release notes.

Component Limit Description Limit Value
Custom audience (CA) Maximum number of ads per CA 100
Maximum number of CAs per application 1000
Maximum number of apps that can create a CA 1000
Maximum delay in the activation time of a CA from its creation time 60 days
Maximum expiration time of a CA from its activation time 60 days
Maximum number of CAs on device 4000
Maximum size of CA name 200 bytes
Maximum size of daily fetch URI 400 bytes
Maximum size of bidding logic URI 400 bytes
Maximum size of trusted bidding data 10 KB
Maximum size of user bidding signals 10 KB
Maximum call rate for leaveCustomAudience per buyer 1 per second
Maximum call rate for joinCustomAudience per buyer 1 per second
CA Background Fetch Connect timeout 5 seconds
HTTP read timeout 30 seconds
Maximum total download size 10 KB
Max duration of a fetch iteration 5 minutes
Maximum number of CAs updated per job 1000
Ad Selection Maximum number of buyers TBD
Maximum number of CAs per buyer TBD
Maximum number of ads in an auction TBD
Initial connection timeout 5 seconds
Connection read timeout 5 seconds
Maximum execution time of overall AdSelection 10 seconds
Maximum execution time of bidding per CA in AdSelection 5 second
Maximum execution time of scoring in AdSelection 5 second
Maximum execution time for per buyer in AdSelection TBD
Maximum size of ad selection/seller/per buyer signals TBD
Maximum size of seller/buyer scripts TBD
Maximum call rate for selectAds 1 QPS
Impression reporting Minimum time before removing ad selection from persistence 24 hrs
Maximum number of storage ad selections TBD
Maximum size of reporting output URL TBD
Maximum time for impression reporting TBD
Maximum number of retries for notification calls TBD
Connection timeout 5 seconds
Maximum overall execution time for reportImpression 2 seconds
Maximum call rate for reportImpressions 1 QPS
Ads Maximum size of ad list 10 KB shared by all AdData in a single CA/TBD for contextual
URLs Maximum length of any URL string taken as input TBD
Javascript Maximum execution time 1 second for bidding and scoring for impression reporting
Maximum memory used 10 MB

Report bugs and issues

Your feedback is a crucial part of the Privacy Sandbox on Android! Let us know of any issues you find or ideas for improving Privacy Sandbox on Android.