Implement Topics API on Android

Setup

To implement the Topics API, you need to set up your development environment first. to perform the following setup steps:

  1. Use the latest Android Privacy Sandbox SDK to get the most up-to-date version of the privacy-preserving APIs.

  2. Add the following to your manifest:

    • Permission: Include the ACCESS_ADSERVICES_TOPICS permission to allow your app to access the Topics API:

      <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
      
    • Ad Services Configuration: Reference an Ad Services configuration file 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>
      
  3. Enroll your ad tech with the Privacy Sandbox to call Topics API in your SDK. For testing locally, you can disable Topics enrollment check with the following commands:

    adb shell setprop debug.adservices.disable_topics_enrollment_check true
    
  4. Enable access to the Topics API. By default, the Topics API is disabled. You need to enable it using ADB commands:

    adb shell device_config put adservices ppapi_app_signature_allow_list \"\*\"
    adb shell setprop debug.adservices.disable_topics_enrollment_check true
  5. Kick off your implementation by forking our Java or Kotlin version of the sample app to familiarize yourself with how to retrieve topics on a device.

Request a set of topics

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

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

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

Encryption details

With the introduction of encryption, calls to GetTopics() will now generate a response with a list of EncryptedTopic objects. Decrypting these results will result in an object with the same JSON format of the previous Topic object.

Topics API supports one shot implementation of HPKE (Hybrid Public Key Encryption). We expect the enrolled caller to host a 32-bit public key on the public encryption URL endpoint provided during enrollment. These keys are expected to be Base64 encoded.

EncryptedTopic object's have 3 fields. The list of returned topics can be obtained by using the corresponding private key for the public key.

For development purposes, you can test the Topics API encryption by disabling the enrollment check. This would force the API to use the test public key for encrypting your responses. You can decrypt the encrypted topics using the corresponding private key.

Limitations

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

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.

Next steps

Learn how users and developers can manage and customize the Topics API to suit user's preferences and needs.
Understand how Topics works on Android and lear about the core steps of the API flow.

See also

Check out our resources to better understand the Topics API on Android.