SDK Runtime developer guide

Provide feedback

The SDK Runtime allows SDKs to run in a dedicated sandbox that's separate from the calling app. The SDK Runtime provides enhanced safeguards and guarantees around user data collection. This is done through a modified execution environment which limits data access rights and the set of allowed permissions. Learn more about the SDK Runtime in the design proposal.

The steps on this page guide you through the process of creating a runtime-enabled SDK that defines a web-based view that can be remotely rendered into a calling app.

Some features that automate the build process are only available in the latest canary version of Android Studio. This guide calls out which steps those are, and provides alternative instructions if using a stable release of Android Studio.

Before you begin

Before getting started, complete the following steps:

  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.

Set up your project in Android Studio

To try out the SDK Runtime, use a model that's similar to the client-server model. The main difference is that apps (the client) and SDKs (the "server") run on the same device.

Canary

  1. Create two separate apps to ensure that the app code and SDK code are separated.
  2. Add an app module to your project. This module serves as the client that drives the SDK.
  3. In your app module, declare the necessary permissions and configure API-specific ad services.
  4. Add one library module to your project. This module contains your SDK code.
  5. In your SDK module, declare the necessary permissions. You don't need to configure API-specific ad services in this module.
  6. Remove the dependencies in your library module's build.gradle file that your SDK doesn't use. In most cases, you can remove all dependencies.
  7. Add the following snippet to your gradle.properties file:

    android.experimental.privacysandboxsdk.enable=true
    
  8. Download the TiramisuPrivacySandbox emulator image and create an emulator with this image that includes the Play Store.

Stable

  1. Create two separate apps to ensure that the app code and SDK code are separated. This makes it easier to run and test your code side-by-side.
  2. Add an app module to your project. The app module serves as the client that drives the SDK.
  3. In your app module, declare the necessary permissions and configure API-specific ad services.
  4. Add an SDK module to your project. This module contains your SDK code.
  5. In your SDK module, declare the necessary permissions. You don't need to configure API-specific ad services in this module.
  6. Download the TiramisuPrivacySandbox emulator image and create an emulator with this image that includes the Play Store.

Depending on whether you're an SDK developer or an app developer, you may have a different final setup than the one described in the preceding paragraph.

Install the SDK onto a test device, similarly to how you'd install an app, using either Android Studio or the Android Debug Bridge (ADB).

To help you get started, we've created sample apps in the Kotlin and Java programming languages, which can be found in this GitHub repository. The README and manifest files have comments that describe what must be changed to run the sample in stable versions of Android Studio.

Prepare your SDK

Canary

  1. Manually create a module-level directory. This serves as the wrapper around your implementation code to build the SDK APK. In the new directory, add a build.gradle file and populate it with the following snippet. Use a unique name for your runtime-enabled SDK (RE-SDK), and provide a version. Include your library module in the dependencies section.

    plugins {
        id 'com.android.privacy-sandbox-sdk'
    }
    
    android {
        compileSdkPreview 'TiramisuPrivacySandbox'
        minSdkPreview 'TiramisuPrivacySandbox'
        namespace = "com.example.example-sdk"
    
        bundle {
            packageName = "com.example.privacysandbox.provider"
            sdkProviderClassName = "com.example.sdk_implementation.SdkProviderImpl"
            setVersion(1, 0, 0)
        }
    }
    
    dependencies {
        include project(':<your-library-here>')
    }
    
  2. Create a class in your implementation library to serve as an entry point for your SDK. The name of the class should map to the value of sdkProviderClassName and extend SandboxedSdkProvider.

Stable

  1. In your SDK app's AndroidManifest.xml file, include the <sdk-library> and <property> elements in the <application> section, as shown in the following code snippet. Use a unique name for your runtime-enabled SDK, and provide a version.

    <application …>
        <sdk-library android:name="com.example.privacysandbox.provider"
                    android:versionMajor="1" />
        <property
            android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
            android:value="com.example.provider.SdkProviderImpl"/>
    
    </application>
    
  2. Create a class to serve as the entry point for your SDK. The name of the class should map to the value of the property PROPERTY_SDK_PROVIDER_CLASS_NAME, and the implementation should extend SandboxedSdkProvider.

The entry point for your SDK extends SandboxedSdkProvider. The SandboxedSdkProvider contains a Context object for your SDK, which you can access by calling getContext(). This context must only be accessed once onLoadSdk() has been invoked.

To get your SDK app to compile, you need to override methods to handle the SDK lifecycle:

onLoadSdk()

Loads the SDK in the sandbox, and notifies the calling app when the SDK is ready to handle requests by passing its interface as a IBinder object that's wrapped inside a new SandboxedSdk object. The bound services guide provides different ways to provide IBinder. You have flexibility to choose your way, but it must be consistent for the SDK and the calling app.

Using AIDL as an example, you should define an AIDL file to present your IBinder which is going to be shared and used by the app:

// ISdkInterface.aidl
interface ISdkInterface {
    // the public functions to share with the App.
    int doSomthing();
}
getView()

Creates and sets up the view for your ad, initializes the view the same way as any other Android view, and returns the view to be rendered remotely in a window of a given width and height in pixels.

onDataReceived()

Handles any metadata sent from the host app using sendData().

The following code snippet demonstrates how to override these methods:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun onLoadSdk(params: Bundle?): SandboxedSdk {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return SandboxedSdk(SdkInterfaceProxy())
    }

    override fun getView(windowContext: Context, bundle: Bundle, width: Int,
            height: Int): View {
        val webView = WebView(windowContext)
        val layoutParams = LinearLayout.LayoutParams(width, height)
        webView.setLayoutParams(layoutParams)
        webView.loadUrl("https://developer.android.com/privacy-sandbox")
        return webView
    }

    override fun onDataReceived(bundle: Bundle,
            dataReceivedCallback: DataReceivedCallback) {
        // Update the callback with optional data to show that the receiving
        // or processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(Bundle())
    }

    private class SdkInterfaceProxy : ISdkInterface.Stub() {
        fun doSomething() {
            // Implementation of the API.
        }
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public SandboxedSdk onLoadSdk(Bundle params) {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return new SandboxedSdk(new SdkInterfaceProxy());
    }

    @Override
    public View getView(Context windowContext, Bundle bundle, int width,
            int height) {
        WebView webView = new WebView(windowContext);
        LinearLayout.LayoutParams layoutParams =
                new LinearLayout.LayoutParams(width, height);
        webView.setLayoutParams(layoutParams);
        webView.loadUrl("https://developer.android.com/privacy-sandbox");
        return webView;
    }

    @Override
    public void onDataReceived(Bundle bundle, DataReceivedCallback callback) {
        // Update the callback with optional data to show that the receiving or
        // processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(new Bundle());
    }

    private static class SdkInterfaceProxy extends ISdkInterface.Stub {
        @Override
        public void doSomething() {
            // Implementation of the API.
        }
    }
}

Test video players in the SDK Runtime

In addition to supporting banner ads, the Privacy Sandbox is committed to supporting video players running inside the SDK Runtime.

The flow for testing video players is similar to testing banner ads. Change the getView() method of your SDK's entry point to include a video player in the returned View object. Test all of the video player flows that you expect to be supported by the Privacy Sandbox. Note that communication between the SDK and the client app about the video's lifecycle is currently out of scope, so feedback is not yet required for this.

Your testing and feedback will ensure that the SDK Runtime supports all of the use cases of your preferred video player.

The following code snippet demonstrates how to return a simple video view that loads from a URL.

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {

    override fun getView(windowContext: Context, bundle: Bundle, width: Int,
            height: Int): View {
        val videoView = VideoView(windowContext)
        val layoutParams = LinearLayout.LayoutParams(width, height)
        videoView.setLayoutParams(layoutParams)
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"))
        videoView.setOnPreparedListener { mp -> mp.start() }
        return videoView
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {

    @Override
    public View getView(Context windowContext, Bundle bundle, int width,
            int height) {
        VideoView videoView = new VideoView(windowContext);
        LinearLayout.LayoutParams layoutParams =
                new LinearLayout.LayoutParams(width, height);
        videoView.setLayoutParams(layoutParams);
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
        videoView.setOnPreparedListener(mp -> {
            mp.start();
        });
        return videoView;
    }
}

Using storage APIs in your SDK

SDKs in the SDK Runtime can no longer access, read, or write in an app's internal storage and vice versa. The SDK Runtime will be allocated its own internal storage area, which is guaranteed to be separate from the app.

Within the separate internal storage for each SDK Runtime, each SDK will be provided their own storage directories, called per-SDK storage. Per-SDK storage is a logical segregation of the SDK Runtime's internal storage which helps account for how much storage each SDK uses.

Each SDK can use its per-SDK storage using the file storage APIs on the Context object returned by the getContext() method in an instance of SandboxedSdkProvider. The per-SDK storage will be persisted until the client app is uninstalled, or when client app data is cleaned up.

SDKs can only use internal storage. Therefore, only internal storage APIs, such as Context.getFilesDir() or Context.getCacheDir() will work. See more examples in Access from internal storage.

Access to external storage from SDK Runtime is not supported. Calling APIs to access external storage will either throw an exception or return null. Several examples:

You must use the Context returned by SandboxedSdkProvider.getContext() for storage. Using file storage API on any other Context object instance, such as the application context, will not utilize per-SDK internal storage and is not guaranteed to work in all situations or in the future.

The following code snippet demonstrates how to use storage in SDK Runtime:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun onDataReceived(data: Bundle, callback: DataReceivedCallback) {
        // Use the SandboxedSdkContext to use storage
        val filename = "myfile"
        val fileContents = data.getString("extraData", "")
        try {
            getContext().openFileOutput(filename, Context.MODE_PRIVATE).use {
                it.write(fileContents.toByteArray())
                callback.onDataReceivedSuccess()
        } catch (e: Exception) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public void onDataReceived(Bundle data, DataReceivedCallback callback) {
        // Use the SandboxedSdkContext to use storage
        final filename = "extraData";
        final String fileContents = data.getString("extraData", "");
        try (FileOutputStream fos =
                getContext().openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
            callback.onDataReceivedSuccess();
        } catch (Exception e) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    private SandboxedSdkContext mContext;

    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Store reference to the SandboxedSdkContext for later usage
        mContext = sandboxedSdkContext;
    }

    @Override
    public void onDataReceived(Bundle data, DataReceivedCallback callback) {
        // Use the SandboxedSdkContext to use storage
        final filename = "extraData";
        final String fileContents = data.getString("extraData", "");
        try (FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
            callback.onDataReceivedSuccess();
        } catch (Exception e) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Update client apps

To call into an SDK that is running in the SDK Runtime, make the following changes to the calling client app:

  1. Add the INTERNET and ACCESS_NETWORK_STATE permissions to your app's manifest:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  2. In your app's activity that includes an ad, declare a reference to the SdkSandboxManager, a boolean to know whether the SDK is loaded, and a SurfaceView object for remote rendering:

    Kotlin

    private lateinit var mSdkSandboxManager: SdkSandboxManager
    private lateinit var mClientView: SurfaceView
    private var mSdkLoaded = false
    
    companion object {
        private const val SDK_NAME = "com.example.privacysandbox.provider"
    }
    

    Java

    private static final String SDK_NAME = "com.example.privacysandbox.provider";
    
    private SdkSandboxManager mSdkSandboxManager;
    private SurfaceView mClientView;
    private boolean mSdkLoaded = false;
    
  3. Define a callback class by implementing LoadSdkCallback to interact with the SDK in the runtime. In the following example, the client uses a callback to wait until the SDK has been loaded successfully, then attempts to render a web view from the SDK. The callbacks are defined later in this step.

    Kotlin

    private inner class LoadSdkOutcomeReceiverImpl private constructor() :
            OutcomeReceiver<SandboxedSdk?, LoadSdkException?> {
    
      override fun onResult(sandboxedSdk: SandboxedSdk) {
          mSdkLoaded = true
    
          val sendDataCallback = SendDataOutcomeRecieverImpl()
          // Send some data to the SDK if needed.
          mSdkSandboxManager!!.sendData(
                  SDK_NAME,
                  Bundle(), { obj: Runnable -> obj.run() },
                  sendDataCallback)
    
          // The sendData() function will be removed in the next releases.
          // Instead of using sendData(), use the APIs exposed to the
          // IBinder interface wrapped in the result object. This IBinder
          // should be shared by the SDK.
          val binder: IBinder = sandboxedSdk.getInterface()
          if (!binderInterface.isPresent()) {
              // SDK is not loaded anymore.
              return
          }
          val sdkInterface: ISdkInterface = ISdkInterface.Stub.asInterface(binder)
          sdkInterface.doSomething()
    
          Handler(Looper.getMainLooper()).post {
              val bundle = Bundle()
              bundle.putInt(SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth())
              bundle.putInt(SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight())
              bundle.putInt(SdkSandboxManager.EXTRA_DISPLAY_ID, display!!.displayId)
              bundle.putInt(SdkSandboxManager.EXTRA_HOST_TOKEN, mClientView.getHostToken())
              mSdkSandboxManager!!.requestSurfacePackage(
                      SDK_NAME, bundle, { obj: Runnable -> obj.run() },
                      RequestSurfacePackageOutcomeReceiverImpl())
          }
      }
    
      override fun onError(error: LoadSdkException) {
              // Log or show error.
      }
    }
    
    

    Java

    import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
    import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
    import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
    import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
    
    private class LoadSdkOutcomeReceiverImpl
            implements OutcomeReceiver<LoadSdkResponse, LoadSdkException> {
        private LoadSdkOutcomeReceiverImpl() {}
    
        @Override
        public void onResult(@NonNull SandboxedSdk sandboxedSdk) {
            mSdkLoaded = true;
    
            SendDataOutcomeRecieverImpl sendDataCallback = new SendDataOutcomeRecieverImpl();
    
            // Send some data to the SDK if needed.
            mSdkSandboxManager.sendData(
                    SDK_NAME,
                    new Bundle(),
                    Runnable::run,
                    sendDataCallback);
    
            // The sendData() function will be removed in the next releases.
            // Instead of using sendData(), use the APIs exposed to the
            // IBinder interface wrapped in the result object. This IBinder
            // should be shared by the SDK.
            IBinder binder = sandboxedSdk.getInterface();
            if (!binderInterface.isPresent()) {
                // SDK is not loaded anymore.
                return;
            }
            ISdkInterface sdkInterface = ISdkInterface.Stub.asInterface(binder);
            sdkInterface.doSomething();
    
            new Handler(Looper.getMainLooper()).post(() -> {
                Bundle bundle = new Bundle();
                bundle.putInt(EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth());
                bundle.putInt(EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight());
                bundle.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
                bundle.putInt(EXTRA_HOST_TOKEN, mClientView.getHostToken());
    
                mSdkSandboxManager.requestSurfacePackage(
                        SDK_NAME, bundle, Runnable::run,
                        new RequestSurfacePackageOutcomeReceiverImpl());
            });
        }
    
        @Override
        public void onError(@NonNull LoadSdkException error) {
            // Log or show error.
        }
    }
    
    

    To get back a remote view from the SDK in the runtime while calling requestSurfacePackage(), implement the OutcomeReceiver<Bundle, RequestSurfacePackageException> interface:

    Kotlin

    private inner class RequestSurfacePackageOutcomeReceiverImpl :
            OutcomeReceiver<Bundle, RequestSurfacePackageException> {
        fun onResult(@NonNull result: Bundle) {
            Handler(Looper.getMainLooper())
                    .post {
                        val surfacePackage: SurfacePackage = result.getParcelable(
                                EXTRA_SURFACE_PACKAGE,
                                SurfacePackage::class.java)
                        mRenderedView.setChildSurfacePackage(surfacePackage)
                        mRenderedView.setVisibility(View.VISIBLE)
                    }
        }
    
        fun onError(@NonNull error: RequestSurfacePackageException?) {
            // Error handling
        }
    }
    

    Java

    import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
    
    private class RequestSurfacePackageOutcomeReceiverImpl
            implements OutcomeReceiver<Bundle, RequestSurfacePackageException> {
        @Override
        public void onResult(@NonNull Bundle result) {
            new Handler(Looper.getMainLooper())
                    .post(
                            () -> {
                                SurfacePackage surfacePackage =
                                        result.getParcelable(
                                                EXTRA_SURFACE_PACKAGE,
                                                SurfacePackage.class);
                                mRenderedView.setChildSurfacePackage(surfacePackage);
                                mRenderedView.setVisibility(View.VISIBLE);
                            });
        }
        @Override
        public void onError(@NonNull RequestSurfacePackageException error) {
            // Error handling
        }
    }
    

    For tracking the status of sendData() and getting back any data returned by the SDK, implement the OutcomeReceiver<SendDataResponse, SendDataException> interface:

    Kotlin

    private inner class SendDataOutcomeRecieverImpl :
            OutcomeReceiver<SendDataResponse?, SendDataException?> {
        override fun onResult(result: SendDataResponse) {
            // Use the data returned by the SDK if required.
        }
    
        override fun onError(error: SendDataException) {
            // Log or show error.
        }
    }
    

    Java

    private class SendDataOutcomeRecieverImpl
            implements OutcomeReceiver<SendDataResponse, SendDataException> {
        @Override
        public void onResult(@NonNull SendDataResponse result) {
            // Use the data returned by the SDK if required.
        }
    
        @Override
        public void onError(@NonNull SendDataException error) {
            // Log or show error.
        }
    }
    
  4. In onCreate(), initialize the SdkSandboxManager, necessary callbacks, and then make a request to load the SDK:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mSdkSandboxManager = applicationContext.getSystemService(
                SdkSandboxManager::class.java
        )
    
        mClientView = findViewById(R.id.rendered_view)
        mClientView.setZOrderOnTop(true)
    
        val loadSdkCallback = LoadSdkCallbackImpl()
        mSdkSandboxManager.loadSdk(
                SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, loadSdkCallback
        )
    }
    

    Java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mSdkSandboxManager = getApplicationContext().getSystemService(
                SdkSandboxManager.class);
    
        mClientView = findViewById(R.id.rendered_view);
        mClientView.setZOrderOnTop(true);
    
        LoadSdkCallbackImpl loadSdkCallback = new LoadSdkCallbackImpl();
        mSdkSandboxManager.loadSdk(
                SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
    }
    
  5. To handle the case when the SDK sandbox process unexpectedly terminates, define an implementation for the SdkSandboxLifecycleCallback interface:

    Kotlin

    private inner class SdkSandboxLifecycleCallbackImpl() : SdkSandboxLifecycleCallback {
        override fun onSdkSandboxDied() {
            // The SDK runtime process has terminated. To bring back up the
            // sandbox and continue using SDKs, load the SDKs again.
            val loadSdkCallback = LoadSdkOutcomeReceiverImpl()
            mSdkSandboxManager.loadSdk(
                      SDK_NAME, Bundle(), { obj: Runnable -> obj.run() },
                      loadSdkCallback)
        }
    }
    

    Java

      private class SdkSandboxLifecycleCallbackImpl
              implements SdkSandboxLifecycleCallback {
          @Override
          public void onSdkSandboxDied() {
              // The SDK runtime process has terminated. To bring back up
              // the sandbox and continue using SDKs, load the SDKs again.
              LoadSdkOutcomeReceiverImpl loadSdkCallback =
                      new LoadSdkOutcomeReceiverImpl();
              mSdkSandboxManager.loadSdk(
                          SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
          }
      }
      ```
    

    To register this callback to receive information about when the SDK sandbox has terminated, add the following line at any time:

    Kotlin

    mSdkSandboxManager.addSdkSandboxLifecycleCallback({ obj: Runnable -> obj.run() },
            SdkSandboxLifecycleCallbackImpl())
    

    Java

    mSdkSandboxManager.addSdkSandboxLifecycleCallback(Runnable::run,
            new SdkSandboxLifecycleCallbackImpl());
    

    Because the state of the sandbox is lost when its process terminates, views that have been remotely rendered by the SDK might no longer work correctly. To continue interacting with SDKs, these views must be loaded again so that a new sandbox process is started. To monitor the status of the newly created sandbox process, re-register the callback using addSdkSandboxLifecycleCallback().

Canary

  1. Add a dependency on your SDK module to your client app’s build.gradle:
    dependencies {
        ...
        implementation project(':<your-sdk-module>')
        ...
    }

Canary

  1. Add a dependency on your SDK module to your client app’s build.gradle:
    dependencies {
        ...
        implementation project(':<your-sdk-module>')
        ...
    }

Stable

  1. Specify the cert digest manually. To find your cert digest, extract it from your debug keystore file using keytool. The default password is android.

    keytool -list -keystore ~/.android/debug.keystore
  2. In the <application> element in the app's manifest file, add the <uses-sdk-library> element. Within this element, set the android:certDigest attribute to the output from the previous step:

    <uses-sdk-library
        android:name="com.example.basicsandboxservice"
        android:versionMajor="1"
        android:certDigest="27:76:B1:2D:...:B1:BE:E0:28:5E" />
Installation failed due to: 'Failed to commit install session SESSION_ID
with command cmd package install-commit SESSION_ID. Error:
INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation failed...: Reconcile
failed: Package PACKAGE_NAME requires differently signed sdk library;
failing!'

Test your apps

Before you run the client app, install the SDK app and client app onto your test device using either Android Studio or the command line. If you are using Android Studio Canary, skip to the Deploy on the command line section to generate and deploy your SDK at this time.

Deploy through Android Studio

When deploying through Android Studio, complete the following steps:

  1. Open the Android Studio project for your SDK app.
  2. Go to Run > Edit Configurations. The Run/Debug Configuration window appears.
  3. Under Launch Options, set Launch to Nothing, since there is no activity to start.
  4. Click Apply and then OK.
  5. Click Run to install the SDK app on your test device.
  6. In the Project tool window, navigate to your client app module.
  7. Go to Run > Edit Configurations. The Run/Debug Configuration window appears.
  8. Set the Launch Options to your client app's main activity.
  9. Click Apply and then OK.
  10. Click Run to install the client app on your test device.

Deploy on the command line

When deploying using the command line, complete the steps in the following list. This section assumes that the name of your SDK app module is sdk-app and that the name of your client app module is client-app.

Canary

  1. From a command line terminal, build the Privacy Sandbox SDK APKs:

    ./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
    

    This outputs the location for the generated APKs. These APKs are signed with your local debug key. You need this path in the next command.

  2. Install the APK on your device:

    adb install -t /path/to/your/standalone.apk
    
  3. In Android Studio, click Run > Edit Configurations. The Run/Debug Configuration window appears.

  4. Under Installation Options, set Deploy to Default APK.

  5. Click Apply and then OK.

  6. Click Run to install the APK bundle on your test device.

Stable

  1. Deploy the SDK app:

    ./gradlew sdk-app:installDebug
    
  2. Deploy the client app:

    ./gradlew client-app:installDebug && \
        # Start the app's activity. This example uses the sample app.
        adb shell am start -n \
        com.example.privacysandbox.client/com.example.client.MainActivity
    

Debug your apps

To debug the client app, click the Debug button in Android Studio.

To debug the SDK app, go to Run > Attach to Process, which shows you a popup screen (figure 1). Check the Show all processes box. In the list that appears, look for a process called CLIENT_APP_PROCESS_sdk_sandbox. Select this option and add breakpoints in the SDK app's code to start debugging your SDK.

The SDK app process appears in a list view near the bottom
  of the dialog
Figure 1. The Choose process screen, where you can select the SDK app to debug.

Start and stop the SDK runtime from the command line

To start the SDK runtime process for your app, use the following shell command:

adb shell cmd sdk_sandbox start [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

Similarly, to stop the SDK runtime process, run this command:

adb shell cmd sdk_sandbox stop [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

Limitations

For a list of in-progress capabilities for the SDK Runtime, view the release notes.

Code samples

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 SDK Runtime.

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.