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.

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.

Add one app module and one SDK module to your project. This makes it easier to run and test your code side by side. You should create two separate apps to ensure that both the app code and SDK code are separated.

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.

Prepare your SDK

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>

The entry point for your SDK extends SandboxedSdkProvider. To get your SDK app to compile, you need to override methods to handle the SDK lifecycle.

initSdk()
Initializes the SDK, and notifies the calling app when initialization is complete and ready for use.
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 for remote rendering.
onExtraDataReceived()
Placeholder, not available yet. Handles any extra metadata sent in from the host app.

The following code snippet demonstrates how to override these methods:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Update the callback with optional data to show that the initialization
        // is complete.
        executor.execute { initSdkCallback.onInitSdkFinished(Bundle()) }
    }

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

    // Not available yet; this is a placeholder.
    override fun onExtraDataReceived(bundle: Bundle) {}
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Update the callback with optional data to show that the
        // initialization is complete.
        executor.execute(() -> initSdkCallback.onInitSdkFinished(new Bundle()));
    }

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

    // Not available yet; this is a placeholder.
    @Override
        public void onExtraDataReceived(@NonNull Bundle bundle) {
    }
}

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. 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;
  2. Define a callback class by implementing RemoteSdkCallback to interact with the SDK in the runtime:

    Kotlin

    private inner class RemoteSdkCallbackImpl() : RemoteSdkCallback {
        override fun onLoadSdkSuccess(params: Bundle) {
            mSdkLoaded = true
        }
    
        override fun onLoadSdkFailure(errorCode: Int, errorMessage: String) {
            // log/show error
        }
    
        // Loads the remote view specified in the SDK's getView() method.
        override fun onSurfacePackageReady(
            surfacePackage: SurfacePackage,
            surfacePackageId: Int, params: Bundle
        ) {
            Handler(Looper.getMainLooper()).post {
                mClientView.setChildSurfacePackage(surfacePackage)
                mClientView.visibility = View.VISIBLE
            }
        }
    
        override fun onSurfacePackageError(errorCode: Int, errorMessage: String) {
            // log/show error
        }
    }
    

    Java

    private class RemoteSdkCallbackImpl implements RemoteSdkCallback {
        private RemoteSdkCallbackImpl() {}
    
        @Override
        public void onLoadSdkSuccess(Bundle params) {
            mSdkLoaded = true;
        }
    
        @Override
        public void onLoadSdkFailure(int errorCode, String errorMessage) {
            // log/show error
        }
    
        // Loads the remote view specified in the SDK's getView() method.
        @Override
        public void onSurfacePackageReady(SurfacePackage surfacePackage,
                int surfacePackageId, Bundle params) {
            new Handler(Looper.getMainLooper()).post(() -> {
                mClientView.setChildSurfacePackage(surfacePackage);
                mClientView.setVisibility(View.VISIBLE);
            });
        }
    
        @Override
        public void onSurfacePackageError(int errorCode, String errorMessage) {
            // log/show error
        }
    }
    
  3. In onCreate(), initialize the SdkSandboxManager, necessary callbacks, and then make a request to display the remote view:

    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 callback = RemoteSdkCallbackImpl()
        mSdkSandboxManager.loadSdk(
                SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, callback
        )
    
        Handler(Looper.getMainLooper()).post {
            val bundle = Bundle()
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, display!!.displayId,
                    mClientView.width, mClientView.height, bundle
            )
        }
    }
    

    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);
    
        RemoteSdkCallbackImpl callback = new RemoteSdkCallbackImpl();
        mSdkSandboxManager.loadSdk(
                SDK_NAME, new Bundle(), Runnable::run, callback);
    
        new Handler(Looper.getMainLooper()).post(() -> {
            Bundle bundle = new Bundle();
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, getDisplay().getDisplayId(),
                    mClientView.getWidth(), mClientView.getHeight(), bundle);
        });
    }
    
  4. 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
    
  5. 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:

    <application ...>
      <uses-sdk-library
          android:name="com.example.basicsandboxservice"
          android:versionMajor="1"
          android:certDigest="27:76:B1:2D:...:B1:BE:E0:28:5E" />
    </application>
    

    Note: If you enter an incorrect value for android:certDigest, the following error occurs:

    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!'

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

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.

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

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.