As you read through the Privacy Sandbox on Android documentation, use the Developer Preview or Beta button to select the program version that you're working with, as instructions may vary.
The Protected Audience API on Android (formerly known as FLEDGE) 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 the Protected Audience API on Android in the design proposal.
This guide describes how to work with the Protected Audience API on Android to do the following:
Before you begin
Before you get started, complete the following:
- Set up your development environment for the Privacy Sandbox on Android.
- Either install a system image onto a supported device or set up an emulator that includes support for the Privacy Sandbox on Android.
In a terminal, enable access to the Protected Audience API (disabled by default) with the following adb command.
adb shell device_config put adservices ppapi_app_allow_list \"*\"
Include an
ACCESS_ADSERVICES_CUSTOM_AUDIENCE
permission in your app manifest:<uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
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" />
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>
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
andreportImpression
APIs fail if this isn't satisfied. There are two options to set this up:Option 1: Run the following adb command to deactivate 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 earlier.
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:
- 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)
// 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
updates 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 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.
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.- 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." - 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 =
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
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.
Run ad selection
To use the Protected Audience API to select ads, call the selectAds()
method:
- Initialize an
AdSelectionManager
object. - Build an
AdSelectionConfig
object. - Call the asynchronous
selectAds()
method with theAdSelectionConfig
object and relevantExecutor
andOutcomeReceiver
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)
.setBuyerContextualAds(
Collections.singletonMap(
contextualAds.getBuyer(), contextualAds
)
).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)
.setBuyerContextualAds(
Collections.singletonMap(contextualAds.getBuyer(), contextualAds)
)
.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.
- HTTPS URL: queried to obtain the seller ad network's JavaScript logic. See the required function signatures.
- Prebuilt URI: that follows FLEDGE's ad selection format.
IllegalArgumentException
is thrown, if an unsupported or malformed prebuilt uri is passed.
- 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. - Contextual ads: A collection of ad candidates that are collected directly from buyers during an auction that happens outside of a Protected Audience auction.
Once an ad is selected, the results, bids, and signals are persisted internally
for reporting. The OutcomeReceiver.onResult()
callback returns 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 provides an AdServicesException
with the following behaviors:
- If the ad selection is initiated with invalid arguments, the
AdServicesException
indicates anIllegalArgumentException
as the cause. - All other errors receive an
AdServicesException
with anIllegalStateException
as the cause.
Frequency cap filtering
Frequency cap filtering enables ad techs to limit the number of times an ad is shown. Frequency cap filtering reduces ad overexposure and optimizes alternate ad selection for a given ad campaign.
There are two main components of a frequency cap filter: the ad event type, and the ad counter key. The available ad event types that can be used are:
- Win (coming soon): A win event indicates that the ad has won an auction. Win events are automatically updated by the Protected Audience API and cannot be called directly by the developer. Win data is only visible to ads within a given custom audience.
- Impression: Separate from
reportImpression
, an on-device caller (SSP or MMP) usesupdateAdCounterHistogram()
to invoke impression events at the point in code they choose. Impression events are visible to all ads belonging to a given DSP, and are not limited to ads in the same custom audience. - View: Event is invoked by the on-device caller (SSP or MMP) at a point in
code they choose using a call to
updateAdCounterHistogram()
. View events are visible to all ads belonging to a given DSP and not limited to ads in the same Custom Audience. - Click: Event is invoked by the on-device caller (SSP or MMP) at a point in
code they choose using a call to
updateAdCounterHistogram()
. Click events are visible to all ads belonging to a given DSP and not limited to ads in the same Custom Audience.
In the publisher app, an SSP or MMP that has a presence on the device invokes ad
events. When updateAdCounterHistogram()
is called, the counter of a frequency
cap filter is incremented so that future auctions will have up to date
information about a user's exposure to a given ad. The ad event types are not
forcibly tied to the corresponding user action and are guidelines given to help
callers to structure their event system. To increment ad counters at the time of
an event, the on-device actor provides the winning ad auction's ad selection ID.
Ad counter keys are arbitrary 32-bit signed integers assigned by a buyer ad tech, and they correspond to a given set of ads as defined by the DSP. Since ad counter keys are limited only to ads that belong to a given DSP, these keys can be selected without overlapping with histograms from another ad tech. Ad counter keys are used to increment DSP-specific identifiers across a DSP's ads or within a given custom audience to filter out ads from future auctions.
Counter keys can be leveraged to prioritize ads that are more likely to be interesting to a given user based on their interactions with other ads from a given buyer ad tech. For example, an ad that has received a high level of engagement from winning ad auctions, views, and clicks, represents an inferred data point. To further illustrate this point: an ad for left handed golf clubs might indicate that the user wouldn't be interested in right handed ones. A frequency cap filter set for a counter key assigned to left-handed ads could filter out ads for right handed clubs.
To use frequency capping in your auction, you must first create
KeyedFrequencyCap
objects as shown below:
Kotlin
// Value used when incrementing frequency counter
val adCounterKey = 123
// Frequency cap exceeded after 2 counts
val keyedFrequencyCapForImpression: KeyedFrequencyCap = Builder(
adCounterKey, 2, Duration.ofSeconds(10)
).build()
// Frequency cap exceeded after 1 counts
val keyedFrequencyCapForImpression: KeyedFrequencyCap = Builder(
adCounterKey, 1, Duration.ofSeconds(10)
).build()
Java
// Value used when incrementing frequency counter
int adCounterKey = 123;
// Frequency cap exceeded after 2 counts
KeyedFrequencyCap keyedFrequencyCapForImpression =
new KeyedFrequencyCap.Builder(
adCounterKey, 2, Duration.ofSeconds(10)
).build();
// Frequency Cap exceeded after 1 counts
KeyedFrequencyCap keyedFrequencyCapForClick =
new KeyedFrequencyCap.Builder(
adCounterKey, 1, Duration.ofSeconds(10)
).build();
Once the KeyedFrequencyCap
objects are created, you can pass them into an
AdFilters
object.
Kotlin
val filters: AdFilters = Builder()
.setFrequencyCapFilters(
Builder()
.setKeyedFrequencyCapsForImpressionEvents(
ImmutableObject.of(keyedFrequencyCapForImpression)
)
.setKeyedFrequencyCapsForClickEvents(
ImmutableObject.of(keyedFrequencyCapForClick)
)
).build()
Java
AdFilters filters = new AdFilters.Builder()
.setFrequencyCapFilters(new FrequencyCapFilters.Builder()
.setKeyedFrequencyCapsForImpressionEvents(
ImmutableObject.of(keyedFrequencyCapForImpression)
)
.setKeyedFrequencyCapsForClickEvents(
ImmutableObject.of(keyedFrequencyCapForClick)
)
).build();
When the AdFilters
object is populated with frequency cap filters, it can be
passed along when the custom audience is created:
Kotlin
// Initialize a custom audience.
val audience: CustomAudience = Builder()
.setBuyer(buyer)
.setName(name)
.setAds(
listOf(
Builder()
.setRenderUri(renderUri)
.setMetadata(JSONObject().toString())
.setAdFilters(filters)
.setAdCounterKeys(adCounterKeys)
.build()
)
).build()
Java
// Initialize a custom audience.
CustomAudience audience = new CustomAudience.Builder()
.setBuyer(buyer)
.setName(name)
.setAds(Collections.singletonList(new AdData.Builder()
.setRenderUri(renderUri)
.setMetadata(new JSONObject().toString())
.setAdFilters(filters)
.setAdCounterKeys(adCounterKeys)
.build()))
.build();
When frequency cap filters are implemented into a custom audience, the SSP can then invoke the necessary click, view, or impression events.
Kotlin
val callerAdTech: AdTechIdentifier = mAdSelectionConfig.getSeller()
val request: UpdateAdCounterHistogramRequest = Builder(
adSelectionId,
FrequencyCapFilters.AD_EVENT_TYPE_CLICK, //CLICK, VIEW, or IMPRESSION
callerAdTech
).build()
Java
AdTechIdentifier callerAdTech = mAdSelectionConfig.getSeller();
UpdateAdCounterHistogramRequest request =
new UpdateAdCounterHistogramRequest.Builder(
adSelectionId,
FrequencyCapFilters.AD_EVENT_TYPE_CLICK, //CLICK, VIEW, or IMPRESSION
callerAdTech
).build();
Ads that have hit their pre-set frequency cap filter limits are filtered out of the auction, with filtering happening before the bidding logic is executed. This toolkit gives ad techs the flexibility to use the interactions between users and the ads within their custom audiences to focus ad targeting while minimizing ad overexposure.
Report ad impressions
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:
- Initialize an
AdSelectionManager
object. - Build a
ReportImpressionRequest
object with the ad selection ID. - Call the asynchronous
reportImpression()
method with theReportImpressionRequest
object and relevantExecutor
andOutcomeReceiver
objects.
Java
AdSelectionManager adSelectionManager =
context.getSystemService(AdSelectionManager.class);
// Initialize a ReportImpressionRequest
ReportImpressionRequest reportImpressionRequest =
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 impression reporting URLs have been created and the request has been scheduled. - The
onError()
callback indicates the following possible conditions:- If the call is initialized with an invalid input argument, the
AdServicesException
indicates anIllegalArgumentException
as the cause. - All other errors receive an
AdServicesException
with anIllegalStateException
as the cause.
- If the call is initialized with an invalid input argument, the
Impression reporting endpoints
The report impression API issues HTTPS GET requests to endpoints provided by the sell-side platform and the winning buy-side platform:
Buy-side platform endpoint:
- The API uses the Bidding logic URL specified in the custom audience to fetch the buyer-provided JavaScript that includes logic to return an impression reporting URL.
- 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()
method is designed to offer a best-effort completion of
reporting.
Report Ad Interactions
Protected Audience provides support to report on more granular interactions for a rendered ad. This can include interactions such as view time, clicks, hovers, or any other useful metric that can be collected. The process to receive these reports requires two steps. First, buyers and sellers must register to receive these reports in their reporting JavaScript. Then, the client will need to report these events.
Registering to receive interaction events
Registering for interaction events happens in the buyer's reportWin()
and
seller's reportResult()
JavaScript functions using a JavaScript function
provided by the platform: registerAdBeacon
. To register to receive an
event report, simply call the platform JavaScript Function from your reporting
JavaScript. The following snippet is using a buyer's reportWin()
, but the same
approach applies to reportResult()
.
reportWin(
adSelectionSignals,
perBuyerSignals,
signalsForBuyer,
contextualSignals,
customAudienceSignals) {
...
// Calculate reportingUri, clickUri, viewUri, and hoverUri
registerAdBeacon("click", clickUri)
registerAdBeacon("view", viewUri)
registerAdBeacon("hover", hoverUri)
return reportingUrl;
}
Reporting interaction events
After reporting an impression, clients can report the interactions back to
previously registered winning buy-side and sell-side platforms with the
AdSelectionManager.reportInteraction()
method. To report an ad event:
- Initialize an
AdSelectionManager
object. - Build a
ReportInteractionRequest
object with the ad selection ID, interaction key, interaction data, and reporting destination. - Call the asynchronous
reportInteraction()
method with therequest
object and relevantExecutor
andOutcomeReceiver
objects.
AdSelectionManager adSelectionManager =
context.getSystemService(AdSelectionManager.class);
// Initialize a ReportInteractionRequest
ReportInteractionRequest request =
new ReportInteractionRequest.Builder()
.setAdSelectionId(adSelectionId)
.setInteractionKey("view")
.setInteractionData("{ viewTimeInSeconds : 1 }") // Can be any string
.setReportingDestinations(
FLAG_REPORTING_DESTINATION_BUYER | FLAG_REPORTING_DESTINATION_SELLER
)
.build();
// Request to report the impression with the ReportImpressionRequest
adSelectionManager.reportInteraction(
reportImpressionRequest,
executor,
outcomeReceiver);
Initialize the ReportInteractionRequest
with the following required
parameters:
- Ad selection ID: An ad selection ID retrieved from a previously returned
AdSelectionOutcome
. - Interaction Key: A string key defined by the client describing the action being reported. This must match the key that was registered by the seller or buyer in the reporting JavaScript functions.
- Interaction Data: A string containing data to be included with the event report, to be POSTed back to the reporting servers.
- Reporting Destinations: A bit mask specifying if the events should be
reported to the buyer, seller, or both. These flags are provided by the
platform and the final destination mask can be created using bitwise
operations. To report to one destination, you can use the flag provided by the
platform directly. To report to multiple destinations, you can use the bitwise
OR (
|
) to combine flag values.
The asynchronous reportInteraction()
method uses the OutcomeReceiver
object to signal the result of the API call.
- The
onResult()
callback indicates the report interaction call is valid. - The
onError()
callback indicates the following possible conditions:- If the call is made when the app is running in the background, an
IllegalStateException
with a description of the failure is returned. - If the client is throttled from calling
reportInteraction()
, aLimitExceededException
is returned. - If the package is not enrolled to call the Privacy Preserving APIs, a
SecurityException()
is returned. - If the app reporting interactions is different from the app that called
selectAds()
, anIllegalStateException
is returned.
- If the call is made when the app is running in the background, an
- If the user has not consented to enable the Privacy Sandbox APIs, the call will fail silently.
Interaction reporting endpoints
The report interaction API issues HTTPS POST requests to endpoints provided by the sell-side platform and the winning buy-side platform. Protected Audience will match the interaction keys with the URIs declared in reporting JavaScript and issue a POST request to each endpoint for each interaction being reported. The content type of the request is plain text with the body being the Interaction Data.
Best effort Interaction reporting
The reportInteraction()
is designed to offer a best-effort completion of
reporting through HTTP POST.
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 ignores any malformed fields which results in no updates to that particular field in the response.
- An empty HTTP response or an empty JSON object "
{}
" results 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_bidding_signals) {
return {'status': 0, 'ad': ad, 'bid': ad.metadata.result };
}
Input parameters:
ad
: a JSON object with the formatvar ad = { 'render_url': url, 'metadata': json_metadata };
auction_signals, per_buyer_signals
: JSON objects specified in the auction configuration objectcustom_audience_bidding_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
, andname
are string taken from the properties with the same name of the Custom Audience participating to the ad selectionactivation_time
andexpiration_time
are the time of activation and expiration of the custom audience, expressed as seconds since the Unix epochca_user_bidding_signals
is a JSON string specified in theuserBiddingSignals
field of theCustomAudience
at creation timetrusted_bidding_signals, contextual_signals
, anduser_signals
are JSON objects. They are 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. Therender_url
property of the ad is expected to be unaltered.bid
: a float value representing the bid value for this adstatus
: an integer value that can be:0
: for a successful execution1
: (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 thegenerateBid
documentationbid
: the bid value for the adad_selection_config
: a JSON object representing theAdSelectionConfig
parameter of theselectAds
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 thesellerSignals
AdSelectionConfig
API parametertrusted_scoring_signal
: read from theadSelectionSignals
field in theAdSelectionConfig
API parametercontextual_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 theperBuyerSignal
map in theAdSelectionConfig
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 adstatus
: 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 causes the failure of the process, the value determines 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 ofscoreAds
render_url
: the render URL of the winning adbid
: the bid offered for the winning adcontextual_signals
: see the documentation ofgenerateBid
Output:
status: 0
for success and non-zero for failureresults
: a JSON objects containing:signals_for_buyer
: a JSON object that is passed to thereportWin
functionreporting_url
: a URL that is 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 forscoreAd
signals_for_buyer
: a JSON object returned byreportResult
contextual_signals, custom_audience_signals
: see the documentation forgenerateBid
Output:
status: 0
for success and non-zero for failureresults
: a JSON object containing:reporting_url
: a URL that is used by the platform to notify the impression to the seller
registerAdBeacon()
function registerAdBeacon(
interaction_key,
reporting_uri
)
Input Parameters:
interaction_key
: A string representing the event. This is used by the platform later when reporting event interactions to look up thereporting_uri
that should be notified. This key needs to match between what the buyer or seller is registering, and what the seller is reporting.reporting_uri
: A URI to receive event reports. This should be specific to the event type being reported. It must accept a POST request to handle any data reported along with the event.
Ad Selection prebuilt URIs
Prebuilt URIs give ad techs the ability to appoint JavaScript functions for ad
selection decision logic in the AdSelectionConfig
and
AdSelectionFromOutcomesConfig
classes. Prebuilt URIs don't require network
calls to download the corresponding JavaScript. Ad techs can use prebuilt URIs
without having to set up an enrolled domain to host the JavaScript.
A prebuilt URI is constructed using the following format:
ad-selection-prebuilt:<use-case>/<name>?<required-script-generation-parameters>
The Privacy Sandbox platform provides JavaScript using the information from this URI in the runtime.
An IllegalArgumentException
is thrown if:
- any of the required parameters are not present in the URI
- there are unrecognized parameters in the URI
Supported prebuilt URI use cases and names
Use case 1: ad-selection
Prebuilt URIs under the ad-selection
use case are supported in the
selectAds(AdSelectionConfig)
flow.
Prebuilt URI name: highest-bid-wins
This prebuilt URI provides a JavaScript that picks the ad with the highest bid
after bidding. It also provides a basic reporting function to report the
winner's render_uri
and bid
.
Required parameters
reportingUrl
: The base reporting URL that is parameterized with the
render_uri
and the bid
of the winning ad:
<reportingUrl>?render_uri=<renderUriOfWinnigAd>&bid=<bidOfWinningAd>
Usage
If your base reporting URL is https://www.ssp.com/reporting
then the prebuilt
URI would be:
`ad-selection-prebuilt://ad-selection/highest-bid-wins/?reportingUrl=https://www.ssp.com/reporting`
Use case 2: ad-selection-from-outcomes
Prebuilt URIs under the ad-selection-from-outcomes
use case support the
selectAds(AdSelectionFromOutcomesConfig)
workflow.
Prebuilt URI name: waterfall-mediation-truncation
The waterfall-mediation-truncation
prebuilt URI provides JavaScript that
implements waterfall mediation truncation logic where the JavaScript returns
a first-party ad if the bid
is higher then or equal to the bid floor
, and
otherwise returns null
.
Required parameters
bidFloor
: The key of the bid floor value passed in the getSelectionSignals()
that is compared against the mediation SDK's ad.
Usage
If your ad selection signals look like {"bid_floor": 10}
then the resulting
prebuilt URI would be:
`ad-selection-prebuilt://ad-selection-from-outcomes/waterfall-mediation-truncation/?bidFloor=bid_floor`
Testing
To help you get started with the Protected Audience API, we've created sample apps in Kotlin and Java, which can be found on GitHub.
Prerequisites
The Protected Audience API 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 need to set up 7 HTTPS endpoints that your test device or emulator can access:
- Buyer endpoint that serves the bidding logic JavaScript.
- An endpoint that serves the bidding signals.
- Seller endpoint that serves the decision logic JavaScript.
- An endpoint that serves scoring signals.
- Winning buyer impression reporting endpoint.
- Seller impression reporting endpoint.
- 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 the Protected Audience API 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 Protected Audience API 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 need an HTTPS endpoint to serve any JavaScript you aren't providing overrides for. Please see the README for information about 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 following code example:
- Initialize an
AdSelectionManager
object. - Get a reference to
TestAdSelectionManager
from theAdSelectionManager
object. - Build an
AdSelectionConfig
object. - Build an
AddAdSelectionOverrideRequest
with theAdSelectionConfig
object and aString
representing the JavaScript you intend to use as an override. - Call the asynchronous
overrideAdSelectionConfigRemoteInfo()
method with theAddAdSelectionOverrideRequest
object and relevantExecutor
andOutcomeReceiver
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 about 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
Protected Audience API 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
indicates anIllegalArgumentException
as the cause. - If the override is attempted with an app not running in debug mode with
developer options enabled, the
AdServiceException
indicatesIllegalStateException
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
:
- Call the asynchronous
resetAllAdSelectionConfigRemoteOverrides()
method with the relevantOutcomeReceiver
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, calls to selectAds()
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 provides an AdServiceException
.
If the removal of overrides is attempted with an app not running in debug mode
with developer options enabled, AdServiceException
indicates
IllegalStateException
as the cause.
Override buy-side JavaScript
- Follow the steps to join a custom audience
- 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 - Call the asynchronous
overrideCustomAudienceRemoteInfo()
method with theAddCustomAudienceOverrideRequest
object and relevantExecutor
andOutcomeReceiver
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 that is 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.
Subsequent calls to selectAds()
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
indicates anIllegalArgumentException
as the cause. - If the override is attempted with an app not running in debug mode with
developer options enabled, the
AdServiceException
indicatesIllegalStateException
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:
- Call the asynchronous
resetAllCustomAudienceOverrides()
method with relevantExecutor
andOutcomeReceiver
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, subsequent calls to selectAds()
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 provides an AdServiceException
.
If the removal of overrides is attempted with an app not running in debug mode
with developer options enabled, the AdServiceException
indicates
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.
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
the Protected Audience API that impression reporting completed successfully.
Functionality to test
- Exercise joining or 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 the Protected Audience API 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 | |
Event reporting | Maximum number of beacons per buyer per auction | 10 |
Maximum number of beacons per seller per auction |
10 |
|
Maximum size of event key |
40 bytes |
|
Maximum size of event data |
64KB |
|
Ads | Maximum size of ad list | 10 KB shared by all
AdData
in a single CA 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.
Recommended for you
- Note: link text is displayed when JavaScript is off
- Support custom audience targeting with the Protected Audience API
- Release notes
- Protected Audience: integration guide