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 realizadas para el usuario (a menos que existan 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 mediante los mecanismos adecuados de privacidad diferencial. Para obtener más detalles, consulta la explicación sobre la personalización integrada en el dispositivo.

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

  • RemoteData: Datos de par clave-valor inmutables descargados de backends remotos operados por desarrolladores, si corresponde.
  • LocalData: Datos clave-valor mutables que el desarrollador conserva de forma local, si corresponde.
  • UserData: Son los datos del usuario que proporciona la plataforma.

Se admiten los siguientes resultados:

  • Salida persistente: Estos resultados se pueden usar en el procesamiento local futuro, lo que genera resultados visibles, el entrenamiento de modelos facilitado por el aprendizaje federado o el análisis estadístico multidispositivo facilitado por las estadísticas federadas.
    • Los desarrolladores pueden escribir solicitudes y sus resultados de procesamiento en la tabla local REQUESTS.
    • Los desarrolladores pueden escribir datos adicionales asociados con una solicitud previa en la tabla EVENTS.
  • Salida que se muestra:
    • Los desarrolladores pueden mostrar HTML que la ODP renderiza en un WebView dentro de un SurfaceView. La app que realiza la invocación no podrá ver el contenido que se renderiza allí.
    • Los desarrolladores pueden incorporar las URLs de eventos proporcionadas por el ODP en el resultado HTML para activar el registro y el procesamiento de las interacciones del usuario con el HTML renderizado. La ODP intercepta las solicitudes a esas URLs y, luego, invoca el código para generar los datos que se escriben en la tabla EVENTS.

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

El servicio de ODP administra la app cliente que desea invocar a ODP para mostrar contenido personalizado en su IU. Descarga contenido de los extremos proporcionados por el desarrollador y, luego, invoca 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 de 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. IsolatedService debe crear una instancia de IsolatedWorker para cada solicitud.

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

Diagrama de la relación entre OnDevicePersonalizationManager y IsolatedWorker.

Una app cliente llama a la ODP con el método execute con un IsolatedService nombrado. El servicio de ODP reenvía la llamada al método onExecute de IsolatedWorker. IsolatedWorker muestra los registros que se conservarán y el contenido que se mostrará. El servicio de ODP escribe el resultado persistente en la tabla REQUESTS o EVENTS, y muestra una referencia opaca al resultado que se muestra en la app cliente. La app cliente puede usar esta referencia opaca en una llamada requestSurfacePackage futura para mostrar el contenido de la pantalla dentro de su IU.

Salida persistente

El servicio de ODP conserva 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 que se muestra. Cada Row contiene una lista de pares (key, value). Cada valor es un escalar, una cadena o un objeto blob. Los valores numéricos se pueden informar después de la agregación, y los datos de cadena o BLOB se pueden informar después de aplicar la privacidad diferencial local o central. Los desarrolladores también pueden escribir eventos de interacción de usuarios posteriores en la tabla EVENTS; cada registro de la tabla EVENTS está asociado con una fila de la tabla REQUESTS. El servicio de ODP registra de manera 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 de tu paquete y habilitar el modo de desarrollador.

Configuración del manifiesto del paquete

Para usar la 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 configurados como 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 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 la 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 la ODP descargará contenido para propagar la tabla RemoteData, como se muestra en el siguiente ejemplo. Si usas las funciones de procesamiento federado, también debes especificar el extremo de la 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>

Habilitar el modo de desarrollador

Para habilitar el modo de desarrollador, sigue las instrucciones de la sección Habilita las opciones para desarrolladores de la documentación de Android Studio.

Cambiar y marcar configuración

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

  • _global_killswitch: El interruptor global para todas las funciones de ODP; se establece en falso para usar la ODP
  • _federado_compute_kill_switch: _el interruptor que controla todas las funcionalidades de entrenamiento (aprendizaje federado) de la ODP; se establece en falso para usar el entrenamiento
  • _caller_app_allowlist: Controla quién puede llamar a ODP. Se pueden agregar apps (nombre del paquete, certificado [opcional]) aquí o configurarlo como * para permitir todo.
  • _isolated_service_allowlist: Controla qué servicios se pueden ejecutar en el proceso de servicio aislado.

Puedes ejecutar los siguientes comandos para configurar todos los interruptores y las marcas para usar la 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 la ODP.

Interacciones con IsolatedService

La clase IsolatedService es una clase base abstracta que todos los desarrolladores que deseen desarrollar con ODP deben extender y declarar en su manifiesto de paquetes como ejecutándose en un proceso aislado. El servicio de ODP inicia este servicio en un proceso aislado y le envía solicitudes. 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 finalización de descargas y los eventos activados por el HTML renderizado. Todos estos métodos tienen implementaciones predeterminadas de no operación, 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:

Cómo generar contenido HTML para mostrar en una SurfaceView

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

Si se realiza correctamente, se llama al receptor con un SurfacePackage para un elemento View renderizado 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 muestra un OnDevicePersonalizationManager#execute anterior, el servicio de ODP llama a IsolatedWorker#onRender para recuperar el fragmento HTML que se renderizará en un marco vallado. Un desarrollador no tiene acceso a LocalData ni a UserData durante esta fase. Esto evita que el desarrollador incorpore UserData potencialmente sensible en las URLs de recuperación de recursos en el HTML generado. Los desarrolladores pueden usar IsolatedService#getEventUrlProvider para generar URLs de seguimiento para incluirlas en el código HTML generado. Cuando se renderiza el HTML, el servicio de ODP interceptará las solicitudes enviadas a estas URLs y llamará a IsolatedWorker#onEvent. Se puede invocar a getRemoteData() cuando se implementa onRender().

Seguimiento de eventos en contenido HTML

La clase EventUrlProvider proporciona APIs para generar URLs de seguimiento de eventos que los desarrolladores pueden incluir en sus resultados HTML. Cuando se renderice el HTML, ODP invocará a 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 la ODP dentro del HTML renderizado, llama a IsolatedWorker#onEvent y registra el EventLogRecord que se muestra en la tabla EVENTS.

Cómo escribir 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 con un puntero a un objeto RequestLogRecord escrito anteriormente .

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

Cómo administrar 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 desarrolladores que adoptan ODP. Puedes invocar getRemoteData(), getLocalData(), getUserData() y getLogReader() cuando implementes onTrainingExample().

Para programar o cancelar trabajos de procesamiento federados, 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 la URL del servidor de procesamiento federado ya debería estar especificado en la configuración del manifiesto del paquete con la etiqueta federated-compute-settings.

Interacciones con el resultado persistente

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

Cómo leer tablas locales

La clase LogReader proporciona APIs para leer las tablas REQUESTS y EVENTS. Estas tablas contienen datos que IsolatedService escribió 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 facilita las estadísticas federadas.

Interacciones con el contenido descargado

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

Descarga contenido de los servidores

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

Los desarrolladores que adoptan la ODP pueden elegir qué subconjunto del contenido descargado se debe agregar a la tabla RemoteData y cuál debe descartarse. 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 propagar la tabla LocalData como deseen. Por ejemplo, pueden almacenar en caché algunos resultados calculados previamente.

Formato de solicitud de descarga

La ODP sondea periódicamente el extremo de URL declarado en el manifiesto del paquete del desarrollador para recuperar contenido y propagarlo en 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 propagarán. El valor syncToken debe ser una marca de tiempo en segundos, sujeta a un límite de hora UTC. Como parte de la solicitud de descarga, la ODP proporciona el syncToken de la descarga completada anteriormente y el país del dispositivo como los parámetros syncToken y país en la URL de descarga. El servidor puede usar el syncToken anterior para implementar descargas incrementales.

Formato de archivo de descarga

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 UTC fijada en un límite de una hora y debe exceder el syncToken de la descarga anterior. Si el syncToken no cumple con ambos requisitos, se descarta el contenido descargado sin procesarlo.

El campo de contenido 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 cliente, necesitas una tarea con un nombre de propagación creado 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 procesamiento federado.

Al crear una tarea nueva para el Compilador de tareas, los desarrolladores de ODP deben proporcionar dos conjuntos de archivos:

  1. Un modelo tff.learning.models.FunctionalModel guardado a través de la llamada a la API tff.learning.models.save_functional_model. Puedes encontrar una muestra en nuestro repositorio de GitHub.
  2. Un fcp_server_config.json que incluye políticas, configuración de aprendizaje federado y de privacidad diferencial. El siguiente es un ejemplo de un 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 Compilador de tareas para construir artefactos y generar tareas nuevas. Puedes encontrar instrucciones más detalladas en nuestro repositorio de GitHub.