Guía para desarrolladores de la personalización integrada en el dispositivo

La personalización integrada en el dispositivo (ODP) está diseñada para proteger la información de los usuarios finales de las aplicaciones. Las aplicaciones usan la ODP para personalizar sus productos y servicios para los usuarios finales, pero no podrán ver las personalizaciones exactas que se hicieron para el usuario (a menos que haya interacciones directas fuera de la ODP entre la aplicación y el usuario final). En el caso de las aplicaciones con modelos de aprendizaje automático o análisis estadísticos, la ODP proporciona un conjunto de servicios y algoritmos para garantizar que se anonimicen correctamente con los mecanismos de privacidad diferencial adecuados. Para obtener más detalles, consulta la explicación sobre la Personalización en el dispositivo.

ODP ejecuta código de desarrollador en un IsolatedProcess que no tiene acceso directo a la red, los discos locales ni otros servicios que se ejecutan en el dispositivo, pero sí tiene acceso a las siguientes fuentes de datos persistentes de forma local:

  • RemoteData: Datos de clave-valor inmutables descargados de backends remotos operados por desarrolladores, si corresponde.
  • LocalData: Datos clave-valor mutables que el desarrollador persiste de forma local, si corresponde.
  • UserData: Datos del usuario proporcionados por la plataforma.

Se admiten los siguientes resultados:

  • Resultados persistentes: Estos resultados se pueden usar en el procesamiento local futuro, lo que genera resultados mostrados, entrenamiento de modelos facilitado por el aprendizaje federado o análisis estadísticos entre dispositivos facilitados por Federated Analytics.
    • Los desarrolladores pueden escribir solicitudes y sus resultados de procesamiento en la tabla REQUESTS local.
    • Los desarrolladores pueden escribir datos adicionales asociados a una solicitud anterior en la tabla EVENTS.
  • Salida mostrada:
    • Los desarrolladores pueden devolver HTML que el ODP renderiza en un WebView dentro de un SurfaceView. La app que invoca no podrá ver el contenido que se renderiza allí.
    • Los desarrolladores pueden incorporar URLs de eventos proporcionadas por la ODP en el resultado HTML para activar el registro y el procesamiento de las interacciones del usuario con el HTML renderizado. ODP intercepta las solicitudes a esas URLs y llama al código para generar datos que se escriben en la tabla EVENTS.

Las apps cliente y los SDKs pueden invocar ODP para mostrar contenido HTML en un SurfaceView con las APIs de ODP. El contenido renderizado en un SurfaceView no es visible para la app que realiza la llamada. La app o el SDK del cliente pueden ser una entidad diferente de la que desarrolla con la ODP.

El servicio de ODP administra la app cliente que desea invocar ODP para mostrar contenido personalizado en su IU. Descarga contenido de los extremos proporcionados por el desarrollador y llama a la lógica para el procesamiento posterior de los datos descargados. También media todas las comunicaciones entre IsolatedProcess y otros servicios y apps.

Las apps cliente usan métodos en la clase OnDevicePersonalizationManager para interactuar con el código del desarrollador que se ejecuta en un IsolatedProcess. El código del desarrollador que se ejecuta en un IsolatedProcess extiende la clase IsolatedService y, además, implementa la interfaz IsolatedWorker. El IsolatedService debe crear una instancia de IsolatedWorker para cada solicitud.

En el siguiente diagrama, se muestra la relación entre los métodos de OnDevicePersonalizationManager y IsolatedWorker.

Diagrama de la relación entre OnDevicePersonalizationManager y IsolatedWorker.

Una app cliente llama al ODP con el método execute con un IsolatedService con nombre. El servicio de ODP reenvía la llamada al método onExecute de IsolatedWorker. IsolatedWorker devuelve registros para que se conserven y contenido para que se muestre. El servicio de ODP escribe el resultado persistente en la tabla REQUESTS o EVENTS, y devuelve una referencia opaca al resultado mostrado a la app cliente. La app cliente puede usar esta referencia opaca en una futura llamada a requestSurfacePackage para mostrar cualquier contenido de visualización dentro de su IU.

Salida persistente

El servicio de ODP persiste un registro en la tabla REQUESTS después de que se muestra la implementación de onExecute del desarrollador. Cada registro de la tabla REQUESTS contiene algunos datos comunes por solicitud que genera el servicio de ODP y una lista de Rows devueltos. Cada Row contiene una lista de pares (key, value). Cada valor es un escalar, una cadena o un BLOB. Los valores numéricos se pueden registrar después de la agregación, y los datos de cadena o BLOB se pueden registrar después de aplicar la privacidad diferencial local o central. Los desarrolladores también pueden escribir eventos de interacción del usuario posteriores en la tabla EVENTS. Cada registro de la tabla EVENTS está asociado a una fila de la tabla REQUESTS. El servicio de ODP registra de forma transparente una marca de tiempo y el nombre del paquete de la app que realiza la llamada, y el APK del desarrollador de ODP con cada registro.

Antes de comenzar

Antes de comenzar a desarrollar con ODP, debes configurar el manifiesto del paquete y habilitar el modo de desarrollador.

Configuración del manifiesto del paquete

Para usar ODP, se requiere lo siguiente:

  1. Una etiqueta <property> en AndroidManifest.xml que apunta a un recurso XML en el paquete que contiene información de configuración de ODP.
  2. Una etiqueta <service> en AndroidManifest.xml que identifica la clase que extiende IsolatedService, como se muestra en el siguiente ejemplo. El servicio en la etiqueta <service> debe tener los atributos exported y isolatedProcess establecidos en true.
  3. Una etiqueta <service> en el recurso XML especificado en el paso 1 que identifica la clase de servicio del paso 2 La etiqueta <service> también debe incluir parámetros de configuración adicionales específicos de la ODP dentro de la propia etiqueta, como se muestra en el segundo ejemplo.

AndroidManifest.xml

<!-- Contents of AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.odpsample" >
    <application android:label="OdpSample">
        <!-- XML resource that contains other ODP settings. -->
        <property android:name="android.ondevicepersonalization.ON_DEVICE_PERSONALIZATION_CONFIG"
                  android:resource="@xml/OdpSettings"></property>
        <!-- The service that ODP binds to. -->
        <service android:name="com.example.odpsample.SampleService"
                android:exported="true" android:isolatedProcess="true" />
    </application>
</manifest>

Manifiesto específico de ODP en el recurso XML

El archivo de recursos XML especificado en la etiqueta <property> también debe declarar la clase de servicio en una etiqueta <service> y especificar el extremo de URL desde el que el ODP descargará contenido para completar la tabla RemoteData, como se muestra en el siguiente ejemplo. Si usas las funciones de procesamiento federado, también debes especificar el extremo de URL del servidor de procesamiento federado al que se conectará el cliente de procesamiento federado.

<!-- Contents of res/xml/OdpSettings.xml -->
<on-device-personalization>
   <!-- Name of the service subclass -->
   <service name="com.example.odpsample.SampleService">
     <!-- If this tag is present, ODP will periodically poll this URL and
          download content to populate REMOTE_DATA. Developers that do not need to
          download content from their servers can skip this tag. -->
     <download-settings url="https://example.com/get" />
     <!-- If you want to use federated compute feature to train a model, you
          need to specify this tag. -->
     <federated-compute-settings url="https://fcpserver.example.com/" />
   </service>
</on-device-personalization>

Habilita el modo de desarrollador

Habilita el modo de desarrollador siguiendo las instrucciones de la sección Habilita las opciones para desarrolladores de la documentación de Android Studio.

Configuración de interruptores y marcas

El ODP tiene un conjunto de interruptores y marcas que se usan para controlar ciertas funcionalidades:

  • _global_killswitch: Es el interruptor global para todas las funciones del ODP. Se establece en falso para usar el ODP.
  • _federated_compute_kill_switch: _es el interruptor que controla todas las funciones de entrenamiento (aprendizaje federado) de ODP. Se establece en falso para usar el entrenamiento.
  • _caller_app_allowlist: Controla quién puede llamar a ODP. Se pueden agregar apps (nombre de paquete, certificado [opcional]) aquí o establecerlo como * para permitir todas.
  • _isolated_service_allowlist: Controla qué servicios pueden ejecutarse en el proceso de Isolated Service.

Puedes ejecutar los siguientes comandos para configurar todos los parámetros y las marcas para usar ODP sin restricciones:

# Set flags and killswitches
adb shell device_config set_sync_disabled_for_tests persistent
adb shell device_config put on_device_personalization global_kill_switch false
adb shell device_config put on_device_personalization federated_compute_kill_switch false
adb shell device_config put on_device_personalization caller_app_allow_list \"*\"
adb shell device_config put on_device_personalization isolated_service_allow_list \"*\"

APIs del dispositivo

Consulta la documentación de referencia de la API de Android para el ODP.

Interacciones con IsolatedService

La clase IsolatedService es una clase base abstracta que todos los desarrolladores que deseen desarrollar en función de la ODP deben extender y declarar en su manifiesto del paquete como en ejecución en un proceso aislado. El servicio de ODP inicia este servicio en un proceso aislado y le envía solicitudes. El IsolatedService recibe solicitudes del servicio de ODP y crea un IsolatedWorker para controlar la solicitud.

Los desarrolladores deben implementar los métodos de la interfaz IsolatedWorker para controlar las solicitudes de la app cliente, las finalizaciones de descargas y los eventos activados por el código HTML renderizado. Todos estos métodos tienen implementaciones predeterminadas que no son operativas, por lo que los desarrolladores pueden omitir la implementación de los métodos que no les interesan.

La clase OnDevicePersonalizationManager proporciona una API para que las apps y los SDKs interactúen con un IsolatedService implementado por el desarrollador que se ejecuta en un proceso aislado. Estos son algunos casos de uso previstos:

Genera contenido HTML para mostrar en un SurfaceView

Para generar contenido que se muestre con OnDevicePersonalizationManager#execute, la app que realiza la llamada puede usar el objeto SurfacePackageToken que se devolvió en una llamada requestSurfacePackage posterior para solicitar que el resultado se renderice en un SurfaceView .

Si la operación se realiza correctamente, se llama al receptor con un SurfacePackage para una View renderizada por el servicio de ODP. Las aplicaciones cliente deben insertar el SurfacePackage en un SurfaceView dentro de su jerarquía de View.

Cuando una app realiza una llamada a requestSurfacePackage con un SurfacePackageToken que devolvió una llamada anterior a OnDevicePersonalizationManager#execute, el servicio de ODP llama a IsolatedWorker#onRender para recuperar el fragmento de HTML que se renderizará dentro de un fenced frame. Durante esta fase, el desarrollador no tiene acceso a LocalData ni a UserData. Esto evita que el desarrollador incorpore UserData potencialmente sensible en las URLs de recuperación de recursos del HTML generado. Los desarrolladores pueden usar IsolatedService#getEventUrlProvider para generar URLs de seguimiento que se incluirán en el código HTML generado. Cuando se renderice el HTML, el servicio de ODP interceptará las solicitudes a estas URLs y llamará a IsolatedWorker#onEvent. Se puede invocar getRemoteData() cuando se implementa onRender().

Realiza un seguimiento de los eventos dentro del contenido HTML

La clase EventUrlProvider proporciona APIs para generar URLs de seguimiento de eventos que los desarrolladores pueden incluir en su salida HTML. Cuando se renderice el HTML, ODP invocará IsolatedWorker#onEvent con la carga útil de la URL del evento.

El servicio de ODP intercepta las solicitudes a las URLs de eventos generadas por ODP dentro del HTML renderizado, llama a IsolatedWorker#onEvent y registra el EventLogRecord devuelto en la tabla EVENTS.

Escribe resultados persistentes

Con OnDevicePersonalizationManager#execute, el servicio tiene la opción de escribir datos en el almacenamiento persistente (tablas REQUESTS y EVENTS). Estas son las entradas que se pueden escribir en estas tablas:

  • Un RequestLogRecord que se agregará a la tabla REQUESTS.
  • Una lista de objetos EventLogRecord que se agregarán a la tabla EVENTS, cada uno de los cuales contiene un puntero a un RequestLogRecord escrito previamente .

El aprendizaje federado puede consumir los resultados persistentes en el almacenamiento integrado en el dispositivo para entrenar modelos.

Administra tareas de entrenamiento integradas en el dispositivo

El servicio de ODP llama a IsolatedWorker#onTrainingExample cuando se inicia un trabajo de entrenamiento de procesamiento federado y desea obtener ejemplos de entrenamiento proporcionados por los desarrolladores que adoptan la ODP. Puedes invocar getRemoteData(), getLocalData(), getUserData() y getLogReader() cuando implementes onTrainingExample().

Para programar o cancelar trabajos de procesamiento federado, puedes usar la clase FederatedComputeScheduler, que proporciona APIs para todos los IsolatedService de ODP. Cada trabajo de procesamiento federado se puede identificar por su nombre de población.

Antes de programar un nuevo trabajo de procesamiento federado, haz lo siguiente:

  • Ya se debería haber creado una tarea con este nombre de población en el servidor de procesamiento federado remoto.
  • El extremo de URL del servidor de procesamiento federado ya debe especificarse en la configuración del manifiesto del paquete con la etiqueta federated-compute-settings.

Interacciones con la salida persistente

En la siguiente sección, se describe cómo interactuar con el resultado persistente en ODP.

Leer tablas locales

La clase LogReader proporciona APIs para leer las tablas REQUESTS y EVENTS. Estas tablas contienen datos que escribió IsolatedService durante las llamadas a onExecute() o onEvent(). Los datos de estas tablas se pueden usar para el entrenamiento de modelos que facilita el aprendizaje federado o para el análisis estadístico multidispositivo que facilitan las estadísticas federadas.

Interacciones con el contenido descargado

En la siguiente sección, se describe cómo interactuar con el contenido descargado en ODP.

Descargar contenido de servidores

El servicio de ODP descarga contenido periódicamente desde la URL declarada en el manifiesto del paquete de IsolatedService y llama a onDownloadCompleted después de que finaliza la descarga. La descarga es un archivo JSON que contiene pares clave-valor.

Los desarrolladores que adoptan el ODP pueden elegir qué subconjunto del contenido descargado se debe agregar a la tabla RemoteData y cuál se debe descartar. Los desarrolladores no pueden modificar el contenido descargado, lo que garantiza que la tabla RemoteData no contenga datos del usuario. Además, los desarrolladores pueden completar la tabla LocalData como deseen. Por ejemplo, pueden almacenar en caché algunos resultados precalculados.

Formato de solicitud de descarga

ODP sondea periódicamente el extremo de URL declarado en el manifiesto del paquete del desarrollador para recuperar contenido y completar la tabla RemoteData.

Se espera que el extremo muestre una respuesta JSON como se describe más adelante. La respuesta JSON debe contener un syncToken que identifique la versión de los datos que se envían, junto con una lista de pares clave-valor que se deben completar. El valor de syncToken debe ser una marca de tiempo en segundos, fijada en un límite de hora UTC. Como parte de la solicitud de descarga, el ODP proporciona el syncToken de la descarga completada anteriormente y el país del dispositivo como los parámetros syncToken y country en la URL de descarga. El servidor puede usar el syncToken anterior para implementar descargas incrementales.

Formato de descarga de archivos

El archivo descargado es un archivo JSON con la siguiente estructura. Se espera que el archivo JSON contenga un syncToken para identificar la versión de los datos que se descargan. El syncToken debe ser una marca de tiempo en UTC ajustada a un límite de hora y debe superar el syncToken de la descarga anterior. Si el syncToken no cumple con ambos requisitos, se descarta el contenido descargado sin procesarlo.

El campo contents es una lista de tuplas (clave, datos, codificación). Se espera que key sea una cadena UTF-8. El campo encoding es un parámetro opcional que especifica cómo se codifica el campo data. Se puede establecer en "utf8" o "base64", y se supone que es "utf8" de forma predeterminada. El campo key se convierte en un objeto String y el campo data se convierte en un array de bytes antes de llamar a onDownloadCompleted()..

{
  // syncToken must be a UTC timestamp clamped to an hour boundary, and must be
  // greater than the syncToken of the previously completed download.
  "syncToken": <timeStampInSecRoundedToUtcHour>,
  "contents": [
    // List of { key, data } pairs.
    { "key": "key1",
      "data": "data1"
    },
    { "key": "key2",
      "data": "data2",
      "encoding": "base64"
    },
    // ...
  ]
}

APIs del servidor

En esta sección, se describe cómo interactuar con las APIs del servidor de procesamiento federado.

APIs de Federated Compute Server

Para programar un trabajo de procesamiento federado del lado del cliente, necesitas una tarea con un nombre de población creada en el servidor de procesamiento federado remoto. En esta sección, explicamos cómo crear una tarea de este tipo en el servidor de procesamiento federado.

Diagrama de la topología cliente-servidor de la computación federada.

Cuando creen una tarea nueva para Task Builder, los desarrolladores de ODP deben proporcionar dos conjuntos de archivos:

  1. Es un modelo tff.learning.models.FunctionalModel guardado a través de la llamada a la API de tff.learning.models.save_functional_model. Puedes encontrar un ejemplo en nuestro repositorio de GitHub.
  2. Un archivo fcp_server_config.json que incluye políticas, configuración del aprendizaje federado y configuración de la privacidad diferencial. A continuación, se muestra un ejemplo de un archivo fcp_server_config.json:
{
  # Task execution mode.
  mode: TRAINING_AND_EVAL
  # Identifies the set of client devices that participate.
  population_name: "mnist_cnn_task"
  policies {
    # Policy for sampling on-device examples. It is checked every
    # time a device is attempting to start a new training.
    min_separation_policy {
      # The minimum separation required between two successful
      # consective task executions. If a client successfully contributes
      # to a task at index `x`, the earliest they can contribute again
      # is at index `(x + minimum_separation)`. This is required by
      # DP.
      minimum_separation: 1
    }
    data_availability_policy {
      # The minimum number of examples on a device to be considered
      # eligible for training.
      min_example_count: 1
    }
    # Policy for releasing training results to developers adopting ODP.
    model_release_policy {
      # The maximum number of training rounds.
      num_max_training_rounds: 512
    }
  }

  # Federated learning setups. They are applied inside Task Builder.
  federated_learning {
    # Use federated averaging to build federated learning process.
    # Options you can choose:
      # * FED_AVG: Federated Averaging algorithm
      #            (https://arxiv.org/abs/2003.00295)
      # * FED_SGD: Federated SGD algorithm
      #            (https://arxiv.org/abs/1602.05629)
    type: FED_AVG
    learning_process {
      # Optimizer used at client side training. Options you can choose:
      # * ADAM
      # * SGD
      client_optimizer: SGD
      # Learning rate used at client side training.
      client_learning_rate: 0.02
      # Optimizer used at server side training. Options you can choose:
      # * ADAM
      # * SGD
      server_optimizer: SGD
      # Learning rate used at server side training.
      server_learning_rate: 1.0
      runtime_config {
        # Number of participating devices for each round of training.
      report_goal: 2
      }
      metrics {
        name: "sparse_categorical_accuracy"
      }
    }
    evaluation {
      # A checkpoint selector controls how checkpoints are chosen for
      # evaluation. One evaluation task typically runs per training
      # task, and on each round of execution, the eval task
      # randomly picks one checkpoint from the past 24 hours that has
      # been selected for evaluation by these rules.
      # Every_k_round and every_k_hour are definitions of quantization
      # buckets which each checkpoint is placed in for selection.
      checkpoint_selector: "every_1_round"
      # The percentage of a populate that should delicate to this
      # evaluation task.
      evaluation_traffic: 0.2
      # Number of participating devices for each round of evaluation.
      report_goal: 2
    }
  }

  # Differential Privacy setups. They are enforced inside the Task
  # Builder.
  differential_privacy {
    # * fixed_gaussian: DP-SGD with fixed clipping norm described in
    #                   "Learning Differentially Private Recurrent
    #                   Language Models"
    #                   (https://arxiv.org/abs/1710.06963).
    type: FIXED_GAUSSIAN
    #   The value of the clipping norm.
    clip_norm: 0.1
    # Noise multiplier for the Gaussian noise.
    noise_multiplier: 0.1
  }
}

Puedes encontrar más muestras en nuestro repositorio de GitHub.

Después de preparar estas dos entradas, invoca el Task Builder para construir artefactos y generar tareas nuevas. En nuestro repositorio de GitHub, encontrarás instrucciones más detalladas.