Topics API developer guide

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.


Provide feedback

The Topics API infers coarse-grained interest signals on-device based on a user's app usage. These signals, called topics, are shared with advertisers to support interest-based advertising without tracking individual users across apps. Learn more about the Topics API in the design proposal. Important: Select the SDK Extensions Release or Developer Preview button to choose the program version that you're working with, as instructions may vary.

Setup

Use the latest Android Privacy Sandbox SDK, to get the most up-to-date version of the privacy-preserving APIs. You need to include a permission and create an Ad Services configuration in your manifest for your app to use the Topics API:

<uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />

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 the manifest, such as res/xml/ad_services_config.xml. Use either the allowAllToAccess attribute to grant access to all SDKs, or the allowSdksToAccess attribute to grant access to individual SDKs. Learn more about Ad Services permissions and SDK access control.

<ad-services-config>
    <topics allowAllToAccess="true" />
</ad-services-config>

Additionally, you must enable access to the Topics API (disabled by default) with these adb commands.

adb shell device_config put adservices ppapi_app_signature_allow_list \"\*\"
adb shell setprop debug.adservices.disable_topics_enrollment_check true

The primary functionality of the Topics API resides in the getTopics() method inside of the TopicsManager object, as shown in this example:

Kotlin

fun getTopics(
        getTopicsRequest: GetTopicsRequest,
        executor: Executor,
        callback: OutcomeReceiver<GetTopicsResponse, Exception>
    ) { }

Java

public void getTopics (@NonNull GetTopicsRequest getTopicsRequest,
    @NonNull Executor executor,
    @NonNull OutcomeReceiver<GetTopicsResponse, Exception> callback)

To use this method, initialize the TopicsManager object and the parameters necessary to receive topics data. GetTopicsRequest passes needed information to retrieve Topics API data, including a flag to indicate whether the caller is going to act as an observer or not. When not acting as an observer, the getTopics call returns a topic from the previous epoch, but won't influence topics data for the following one. The OutcomeReceiver callback handles the result asynchronously. For example:

Kotlin

private fun topicGetter() {
    val mContext = baseContext
    val mTopicsManager = mContext.getSystemService(TopicsManager::class.java)
    val mExecutor: Executor = Executors.newCachedThreadPool()
    val shouldRecordObservation = false
    val mTopicsRequestBuilder: GetTopicsRequest.Builder = GetTopicsRequest.Builder()
    mTopicsRequestBuilder.setAdsSdkName(baseContext.packageName)
    mTopicsRequestBuilder.setShouldRecordObservation(shouldRecordObservation)
    mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor,
        mCallback as OutcomeReceiver<GetTopicsResponse, Exception>)
}
private var mCallback: OutcomeReceiver<GetTopicsResponse, java.lang.Exception> =
object : OutcomeReceiver<GetTopicsResponse, java.lang.Exception> {
    override fun onResult(result: GetTopicsResponse) {
        // handle successful result
        val topicsResult = result.topics
        for (i in topicsResult.indices) {
            Log.i("Topic", topicsResult[i].getTopicId().toString())
        }
        if (topicsResult.size == 0) {
            Log.i("Topic", "Returned Empty")
        }
    }
    override fun onError(error: java.lang.Exception) {
        // handle error
        Log.i("Topic", "Error, did not return successfully")
    }
}

Java

public void TopicGetter() {
    @NonNull Context mContext = getBaseContext();
    TopicsManager mTopicsManager = mContext.getSystemService(TopicsManager.class);
    Executor mExecutor = Executors.newCachedThreadPool();
    boolean shouldRecordObservation = false;
    GetTopicsRequest.Builder mTopicsRequestBuilder = new GetTopicsRequest.Builder();
    mTopicsRequestBuilder.setAdsSdkName(getBaseContext().getPackageName());
    mTopicsRequestBuilder.setShouldRecordObservation(shouldRecordObservation);
    mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor, mCallback);
}
OutcomeReceiver mCallback = new OutcomeReceiver<GetTopicsResponse, Exception>() {
    @Override
    public void onResult(@NonNull GetTopicsResponse result) {
        //Handle Successful Result
        List<Topic> topicsResult = result.getTopics();
        for (int i = 0; i < topicsResult.size(); i++) {
            Log.i("Topic", topicsResult.get(i).getTopicId().toString());
        }
        if (topicsResult.size() == 0) {
            Log.i("Topic", "Returned Empty");
        }
    }
    @Override
    public void onError(@NonNull Exception error) {
        // Handle error
        Log.i("Topic", "Experienced an error, and did not return successfully");
    }
};

Request a set of topics

Once your setup is ready, you can make a call to receive a GetTopicsResponse as a result from the getTopics() method:

Kotlin

mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor,
        mCallback as OutcomeReceiver<GetTopicsResponse, java.lang.Exception>)

Java

mTopicsManager.getTopics(mTopicsRequestBuilder.build(), mExecutor, mCallback);

The above invocation will provide a list of Topics objects containing ID values that correspond to topics in the open source taxonomy that are relevant to the user, or a relevant error. The topics will resemble this example:

/Internet & Telecom/Text & Instant Messaging

Refer to the taxonomy for a list of possible topics that can be returned. This taxonomy is open source and suggested changes can be filed using the feedback button at the top of this page.

Testing

The Topics API provides relevant and fresh topics based on app usage. This early version gives a preview of the API behaviors, and we will improve the quality of topics over future releases. To get the fullest experience, we recommend a testing environment with multiple apps where you call getTopics() to see how topics are selected. The SDK Runtime and Privacy Preserving APIs Repository on GitHub contains a set of individual Android Studio projects to help you get started, including samples that demonstrate how to initialize and call the Topics API.

The topics calculation takes place at the end of an "epoch." By default, each epoch is 7 days long, but you can modify this interval to get a result. This Android Debug Bridge shell command shortens the epoch length to 5 minutes:

adb shell device_config put adservices topics_epoch_job_period_ms 300000

You can confirm the topics_epoch_job_period_ms value with get:

adb shell device_config get adservices topics_epoch_job_period_ms

To manually trigger epoch computation, execute the following command:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 2

In addition to using the sample app, there is a colab that you can use to test different combinations of app info against the topics classifier. Use this colab to view the kinds of results your app is likely to get when calling getTopics.

Limitations

For a list of in-progress capabilities for the Topics API, refer to the release notes.

Reporting 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.