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 de forma correcta con los mecanismos de privacidad diferencial adecuados. 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 un IsolatedProcess que no tiene acceso directo a la red, a los discos locales ni a otros servicios que se ejecutan en el dispositivo, pero tiene acceso a las siguientes fuentes de datos persistentes 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: Datos del usuario proporcionados por 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, así como sus resultados de procesamiento, en la tabla local REQUESTS.
    • Los desarrolladores pueden escribir datos adicionales asociados con una solicitud anterior 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. El ODP intercepta las solicitudes a esas URLs y, luego, invoca el código para generar datos que se escriben en la tabla EVENTS.

Las apps cliente y los SDKs pueden invocar a la ODP para mostrar contenido HTML en un SurfaceView con las APIs de la 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 de 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 mostrado a la app cliente. La app cliente puede usar esta referencia opaca en una futura llamada a requestSurfacePackage para mostrar cualquier contenido de la pantalla en 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 del usuario 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 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 de tu 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 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>

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

Configuración de interruptores y marcas

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

  • _global_killswitch: Es el interruptor global para todas las funciones de ODP. Se establece en "false" para usar ODP.
  • _federated_compute_kill_switch: _es el interruptor que controla todas las funciones de entrenamiento (aprendizaje federado) de la ODP. Se establece en "false" 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 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 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 paquete como ejecutándose 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 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:

Genera contenido HTML para mostrarlo en un elemento 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 vistas.

Cuando una app realiza una llamada a requestSurfacePackage con un SurfacePackageToken que devuelve 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 marco cercado. Un desarrollador no tiene acceso a LocalData ni a UserData durante esta fase. Esto evita que el desarrollador incorpore UserData potencialmente sensible dentro de las URLs de recuperación de recursos en el HTML generado. Los desarrolladores pueden usar IsolatedService#getEventUrlProvider para generar URLs de seguimiento que se incluirán en el 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á 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 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 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 la 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 la ODP. Cada trabajo de procesamiento federado se puede identificar por su nombre de propagación.

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

  • Ya debería existir una tarea con este nombre de propagació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 el resultado 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.

Descargar contenido de 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 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 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 propagar 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 de syncToken debe ser una marca de tiempo en segundos, fijada 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 country 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 ajustada a un límite de hora y debe superar el syncToken de la descarga anterior. Si el syncToken no cumple con ambos requisitos, el contenido descargado se descarta sin procesar.

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 en el 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.

Cuando se crea una tarea nueva para el compilador de tareas, los desarrolladores de la ODP deben proporcionar dos conjuntos de archivos:

  1. Un modelo tff.learning.models.FunctionalModel guardado a través de una 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 incluya políticas, configuración de aprendizaje federado y configuración 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. Hay instrucciones más detalladas disponibles en nuestro repositorio de GitHub.