Guía para desarrolladores del entorno de ejecución del SDK

Envía comentarios

El entorno de ejecución del SDK permite que los SDK se ejecuten en una zona de pruebas dedicada independiente de la app que realiza la llamada. El entorno de ejecución del SDK proporciona mejores protecciones y garantías en cuanto a la recopilación de datos del usuario. Esto se hace a través de un entorno de ejecución modificado que limita los derechos de acceso a los datos y el conjunto de permisos permitidos. Obtén más información sobre el entorno de ejecución del SDK en la propuesta de diseño.

Los pasos que se indican en esta página te guiarán a través del proceso de creación de un SDK habilitado para el entorno de ejecución que define una vista basada en la Web que se puede renderizar, de forma remota, en una app que realiza la llamada.

Antes de comenzar

Antes de comenzar, completa los siguientes pasos:

  1. Configura tu entorno de desarrollo para Privacy Sandbox en Android.
  2. Instala una imagen del sistema en un dispositivo compatible o configura un emulador que incluya compatibilidad con Privacy Sandbox en Android.

Configura tu proyecto en Android Studio

Para probar el entorno de ejecución del SDK, usa un modelo similar al modelo cliente-servidor. La principal diferencia es que las apps (el cliente) y el SDK (el "servidor") se ejecutan en el mismo dispositivo.

Agrega un módulo de app y un módulo de SDK a tu proyecto. De esta manera, se facilitan la ejecución y la prueba de tu código en paralelo. Debes crear dos apps separadas para asegurarte de que el código de la app y del SDK estén separados.

En función de si eres un desarrollador del SDK o un desarrollador de apps, es posible que tengas una configuración final diferente a la que se describe en el párrafo anterior.

Instala el SDK en un dispositivo de prueba, de manera similar a como instalarías una app, con Android Studio o Android Debug Bridge (ADB).

Para comenzar, creamos apps de ejemplo en los lenguajes de programación Kotlin y Java, que se pueden encontrar en este repositorio de GitHub.

Prepara el SDK

En el archivo AndroidManifest.xml de tu app del SDK, incluye los elementos <sdk-library> y <property> en la sección <application>, como se muestra en el siguiente fragmento de código. Usa un nombre único para el SDK habilitado para el entorno de ejecución y proporciona una versión.

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

El punto de entrada de tu SDK extiende SandboxedSdkProvider. Para que se compile tu app del SDK, debes anular métodos para controlar el ciclo de vida del SDK.

initSdk()
Inicializa el SDK y notifica a la app que realiza la llamada cuando la inicialización está completa y lista para usarse.
getView()
Crea y configura la vista de tu anuncio, inicializa la vista de la misma manera que cualquier otra vista de Android, y se muestra la vista para la renderización remota.
onDataReceived()
Maneja todos los metadatos enviados desde la app de alojamiento mediante sendData().

En el siguiente fragmento de código, se muestra cómo anular estos métodos:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Update the callback with optional data to show that 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
    }

    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())
    }
}

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

    @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());
    }
}

Cómo probar reproductores de video en el entorno de ejecución de SDK

Además de admitir anuncios de banner, Privacy Sandbox se compromete a admitir los reproductores de video que se ejecutan dentro del entorno de ejecución de SDK.

El flujo para probar reproductores de video es similar al de probar anuncios de banner. Cambia el método getView() del punto de entrada de tu SDK para incluir un reproductor de video en el objeto View que se muestra. Prueba todos los flujos del reproductor de video que esperas que sean compatibles con Privacy Sandbox. Ten en cuenta que la comunicación entre el SDK y la app cliente sobre el ciclo de vida del video está fuera del alcance, por lo que aún no se requieren comentarios.

Tus pruebas y comentarios garantizarán que el entorno de ejecución de SDK sea compatible con todos los casos de uso de tu reproductor de video preferido.

En el siguiente fragmento de código, se demuestra cómo mostrar una vista de video simple que se carga desde una URL.

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {

    override fun getView(windowContext: Context, params: Bundle): View {
        val videoView = VideoView(windowContext)
        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 params) {
        VideoView videoView = new VideoView(windowContext);
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
        videoView.setOnPreparedListener(mp -> {
            mp.start();
        });
        return videoView;
    }
}

Usa las API de Storage en tu SDK

Los SDK en el entorno de ejecución del SDK ya no pueden acceder al almacenamiento interno de una app, ni viceversa, ni leer o escribir en él. Al entorno de ejecución de SDK se le asignará su propia área de almacenamiento interno, que se garantiza que será independiente de la app.

Dentro del almacenamiento interno independiente para cada entorno de ejecución del SDK, cada SDK proporcionará sus propios directorios de almacenamiento, llamados almacenamiento por SDK. El almacenamiento por SDK es una separación lógica del almacenamiento interno del entorno de ejecución de SDK que ayuda a conocer la cantidad de almacenamiento que usa cada uno.

Cada SDK puede usar su almacenamiento por SDK mediante las API de Storage de archivos en el objeto SandboxedSdkContext que el SDK recibe en su método initSdk(). El almacenamiento por SDK se conservará hasta que se desinstale la app cliente o cuando se borren los datos de la app cliente.

Los SDK solo pueden usar almacenamiento interno. Por lo tanto, solo funcionarán las API de almacenamiento interno, como SandboxedSdkContext.getFilesDir() o SandboxedSdkContext.getCacheDir(). Puedes ver más ejemplos en Acceso desde el almacenamiento interno.

No se admite el acceso al almacenamiento externo desde el entorno de ejecución del SDK. Llamar a las API para que accedan al almacenamiento externo arrojará una excepción o mostrará null. Varios ejemplos:

Debes usar el SandboxedSdkContext proporcionado para el almacenamiento. El uso de la API de Storage de archivos en cualquier otra instancia de objeto Context, como el contexto de la aplicación, no usará el almacenamiento interno por SDK y no se garantiza que funcione en todas las situaciones o en el futuro.

En el siguiente fragmento de código, se muestra cómo usar el almacenamiento en el entorno de ejecución del SDK:

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

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    private lateinit var mContext: SandboxedSdkContext

    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Store reference to the SandboxedContext for later usage
        mContext = sandboxedSdkContext;
    }

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

Actualiza las apps cliente

Para llamar a un SDK que se ejecuta en el entorno de ejecución del SDK, realiza los siguientes cambios en la app cliente que realiza la llamada:

  1. En la actividad de la app que incluye un anuncio, declara una referencia a SdkSandboxManager, un valor booleano para saber si se cargó el SDK, y a un objeto SurfaceView para la renderización remota:

    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. Para definir una clase de devolución de llamada, implementa LoadSdkCallback a fin de interactuar con el SDK en el entorno de ejecución:

    Kotlin

    private inner class RemoteSdkCallbackImpl() : RemoteSdkCallback {
        override fun onLoadSdkSuccess(params: Bundle) {
            mSdkLoaded = true
        }
    
        override fun onLoadSdkFailure(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
        }
    }
    

Para recuperar una vista remota del SDK en el entorno de ejecución mientras llamas a requestSurfacePackage(), define la clase de devolución de llamada RequestSurfacePackageCallback:

Kotlin

private inner class RequestSurfacePackageCallbackImpl() : RequestSurfacePackageCallback {

    // 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 RequestSurfacePackageCallbackImpl implements RequestSurfacePackageCallback {

    // 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
    }
}

Para hacer un seguimiento del estado de sendData() y recuperar los datos que muestra el SDK, define la clase de devolución de llamada SendDataCallback.

Kotlin

private inner class SendDataCallbackImpl() : SendDataCallback {

    override fun onSendDataSuccess(
        params: Bundle
    ) {
        // Use the data returned by the SDK if required.
    }

    override fun onSendDataError(errorCode: Int, errorMessage: String) {
        // log/show error
    }
}

Java

private class SendDataCallbackImpl implements SendDataCallback {

    @Override
    public void onSendDataSuccess(Bundle params) {
        // Use the data returned by the SDK if required.
    }

    @Override
    public void onSendDataError(int errorCode, String errorMessage) {
        // log/show error
    }
}
  1. En onCreate(), inicializa SdkSandboxManager, las devoluciones de llamada necesarias y, luego, envía una solicitud para mostrar la vista remota:

    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
        )
    
        val sendDataCallback = SendDataCallback()
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, Bundle(),{ obj: Runnable -> obj.run() }, sendDataCallback)
    
        val requestSurfacePackageCallback = RequestSurfacePackageCallback()
        Handler(Looper.getMainLooper()).post {
            val bundle = Bundle()
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, display!!.displayId,
                    mClientView.width, mClientView.height,
                    bundle, requestSurfacePackageCallback
            )
        }
    }
    

    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);
    
        SendDataCallback sendDataCallback = new SendDataCallback();
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, new Bundle(), Runnable::run, sendDataCallback);
    
        RequestSurfacePackageCallback requestSurfacePackageCallback = new RequestSurfacePackageCallback();
        new Handler(Looper.getMainLooper()).post(() -> {
            Bundle bundle = new Bundle();
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, getDisplay().getDisplayId(),
                    mClientView.getWidth(), mClientView.getHeight(), bundle, requestSurfacePackageCallback);
        });
    }
    
  2. Especifica el resumen del certificado de forma manual. Para encontrar el resumen del certificado, extráelo del archivo del almacén de claves de depuración mediante keytool. La contraseña predeterminada es android.

    keytool -list -keystore ~/.android/debug.keystore
    
  3. En el elemento <application> del archivo de manifiesto de la app, agrega el elemento <uses-sdk-library>. Dentro de este elemento, establece el atributo android:certDigest en el resultado del paso anterior:

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

    Nota: Si introduces un valor incorrecto para android:certDigest, se producirá el siguiente error:

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

Implementa tus apps

Antes de ejecutar la app cliente, instala la app del SDK y la app cliente en tu dispositivo de prueba con Android Studio o la línea de comandos.

Implementa mediante Android Studio

Cuando realices implementaciones mediante Android Studio, sigue estos pasos:

  1. Abre el proyecto de Android Studio de tu app del SDK.
  2. Ve a Run > Edit Configurations. Aparecerá la ventana Run/Debug Configuration.
  3. En Launch Options, establece Launch en Nothing, ya que no hay actividad para comenzar.
  4. Haz clic en Apply y, luego, en OK.
  5. Haz clic en Run para instalar la app del SDK en tu dispositivo de prueba.
  6. En la ventana de herramientas Project, navega al módulo de la app cliente.
  7. Ve a Run > Edit Configurations. Aparecerá la ventana Run/Debug Configuration.
  8. Configura Launch Options con la actividad principal de la app cliente.
  9. Haz clic en Apply y, luego, en OK.
  10. Haz clic en Run para instalar la app cliente en tu dispositivo de prueba.

Implementa en la línea de comandos

Cuando realices implementaciones con la línea de comandos, completa los pasos de la siguiente lista. En esta sección, se supone que el nombre del módulo de tu app del SDK es sdk-app y que el nombre del módulo de tu app cliente es client-app.

  1. Implementa la app del SDK:

    ./gradlew sdk-app:installDebug
    
  2. Implementa la app cliente:

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

Depura tus apps

Para depurar la app cliente, haz clic en el botón Debug en Android Studio.

Para depurar la app del SDK, ve a Run > Attach to Process, que muestra una pantalla emergente (Figura 1). Marca la casilla Show all processes. En la lista que aparece, busca un proceso con el nombre sdk_sandbox_CLIENT_APP_UID. Selecciona esta opción y agrega puntos de interrupción en el código de la app del SDK para comenzar a depurar tu SDK.

El proceso de la app del SDK aparece en una vista de lista cerca de la parte inferior del diálogo.
Figura 1: La pantalla Choose process, en la que puedes seleccionar la app del SDK que depurarás

Limitaciones

A fin de obtener una lista de las funciones en curso para el entorno de ejecución del SDK, consulta las notas de la versión.

Muestras de código

El repositorio de API del entorno de ejecución y la protección de la privacidad del SDK en GitHub contiene un conjunto de proyectos individuales de Android Studio que te ayudarán a comenzar, incluidos ejemplos que muestran cómo inicializar y llamar al entorno de ejecución del SDK.

Informa errores y problemas

Tus comentarios son una parte fundamental de Privacy Sandbox en Android. Avísanos si tienes problemas, o bien quieres brindar ideas para mejorar Privacy Sandbox en Android.