Guia do desenvolvedor sobre personalização no dispositivo

A personalização no dispositivo (ODP, na sigla em inglês) foi criada para proteger as informações dos usuários finais dos apps. Os aplicativos usam a ODP para personalizar os produtos e serviços para os usuários finais, mas não podem ver as personalizações exatas feitas para o usuário, a menos que haja interações diretas fora da ODP entre o aplicativo e o usuário final. Para aplicativos com modelos de machine learning ou análises estatísticas, a ODP oferece um conjunto de serviços e algoritmos para garantir que eles sejam devidamente anonimizados usando os mecanismos de privacidade diferencial adequados. Para mais detalhes, consulte a explicação em Personalização no dispositivo.

O ODP executa o código do desenvolvedor em um IsolatedProcess que não tem acesso direto à rede, discos locais ou outros serviços em execução no dispositivo, mas tem acesso às seguintes fontes de dados armazenadas localmente:

  • RemoteData: dados imutáveis de chave-valor transferidos por download de back-ends remotos operados por desenvolvedores, se aplicável.
  • LocalData: dados de chave-valor mutáveis persistidos localmente pelo desenvolvedor, se aplicável.
  • UserData: dados do usuário fornecidos pela plataforma.

As saídas a seguir são compatíveis:

  • Saída persistente: essas saídas podem ser usadas em processamentos locais futuros, produzindo saídas exibidas, treinamento de modelo facilitado pelo aprendizado federado ou análise estatística entre dispositivos facilitada pela análise federada.
    • Os desenvolvedores podem gravar solicitações e os resultados de processamento na tabela local REQUESTS.
    • Os desenvolvedores podem gravar outros dados associados a uma solicitação anterior na tabela EVENTS.
  • Saída exibida:
    • Os desenvolvedores podem retornar o HTML que é renderizado pelo ODP em uma WebView dentro de uma SurfaceView. O conteúdo renderizado não vai ficar visível para o app que está invocando.
    • Os desenvolvedores podem incorporar URLs de eventos fornecidos pelo ODP na saída HTML para acionar o registro e o processamento das interações do usuário com o HTML renderizado. A ODP intercepta solicitações para esses URLs e invoca o código para gerar dados que são gravados na tabela EVENTS.

Os apps clientes e os SDKs podem invocar a ODP para mostrar conteúdo HTML em uma SurfaceView usando as APIs ODP. O conteúdo renderizado em um SurfaceView não fica visível para o app que fez a chamada. O app cliente ou SDK pode ser uma entidade diferente da que está desenvolvendo com o ODP.

O serviço ODP gerencia o app cliente que quer invocar o ODP para mostrar conteúdo personalizado na interface. Ele faz o download do conteúdo dos endpoints fornecidos pelo desenvolvedor e invoca a lógica para o pós-processamento dos dados baixados. Ele também media todas as comunicações entre o IsolatedProcess e outros serviços e apps.

Os apps clientes usam métodos na classe OnDevicePersonalizationManager para interagir com o código do desenvolvedor em execução em uma IsolatedProcess. O código do desenvolvedor em execução em um IsolatedProcess estende a classe IsolatedService e implementa a interface IsolatedWorker. O IsolatedService precisa criar uma instância de IsolatedWorker para cada solicitação.

O diagrama a seguir mostra a relação entre os métodos em OnDevicePersonalizationManager e IsolatedWorker.

Diagrama da relação entre OnDevicePersonalizationManager e IsolatedWorker.

Um app cliente chama a ODP usando o método execute com um IsolatedService nomeado. O serviço ODP encaminha a chamada para o método onExecute do IsolatedWorker. O IsolatedWorker retorna registros para serem mantidos e conteúdo para serem mostrados. O serviço ODP grava a saída persistente na tabela REQUESTS ou EVENTS e retorna uma referência opaca à saída exibida ao app cliente. O app cliente pode usar essa referência opaca em uma chamada requestSurfacePackage futura para exibir qualquer conteúdo de exibição na interface.

Saída persistente

O serviço ODP persiste um registro na tabela REQUESTS depois que a implementação do desenvolvedor de onExecute retorna. Cada registro na tabela REQUESTS contém alguns dados comuns por solicitação gerados pelo serviço ODP e uma lista de Rows retornada. Cada Row contém uma lista de pares de (key, value). Cada valor é um escalar, uma string ou um blob. Os valores numéricos podem ser informados após a agregação, e os dados de blob ou string podem ser informados após a aplicação da privacidade diferencial local ou central. Os desenvolvedores também podem gravar eventos de interação do usuário subsequentes na tabela EVENTS. Cada registro na tabela EVENTS é associado a uma linha na tabela REQUESTS. O serviço ODP registra de maneira transparente um carimbo de data/hora, o nome do pacote do app de chamada e o APK do desenvolvedor do ODP com cada registro.

Antes de começar

Antes de começar a desenvolver com o ODP, configure o manifesto do pacote e ative o modo de desenvolvedor.

Configurações do manifesto do pacote

Para usar o ODP, é necessário:

  1. Uma tag <property> em AndroidManifest.xml que aponta para um recurso XML no pacote que contém informações de configuração do ODP.
  2. Uma tag <service> em AndroidManifest.xml que identifica a classe que estende IsolatedService, conforme mostrado no exemplo abaixo. O serviço na tag <service> precisa ter os atributos exported e isolatedProcess definidos como true.
  3. Uma tag <service> no recurso XML especificado na etapa 1 que identifica a classe de serviço da etapa 2. A tag <service> também precisa incluir outras configurações específicas do ODP dentro da própria tag, como mostrado no segundo exemplo.

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>

Manifesto específico do ODP no recurso XML

O arquivo de recurso XML especificado na tag <property> também precisa declarar a classe de serviço em uma tag <service> e especificar o endpoint do URL de onde o ODP vai fazer o download do conteúdo para preencher a tabela RemoteData, conforme mostrado no exemplo abaixo. Se você estiver usando os recursos de computação federadas, também vai precisar especificar o endpoint de URL do servidor de computação federado a que o cliente de computação federado vai se conectar.

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

Ativar modo de desenvolvedor

Ative o modo de desenvolvedor seguindo as instruções na seção Ativar opções para desenvolvedores da documentação do Android Studio.

Configurações de flag e de interruptor

A ODP tem um conjunto de chaves e sinalizações usadas para controlar determinadas funcionalidades:

  • _global_killswitch: a chave global para todos os recursos do ODP. Defina como "false" para usar o ODP.
  • _federated_compute_kill_switch: _a chave que controla todas as funcionalidades de treinamento (aprendizado federado) do ODP. Defina como "false" para usar treinamento.
  • _caller_app_allowlist: controla quem tem permissão para chamar a ODP. Os apps (nome de pacote, certificado [opcional]) podem ser adicionados aqui ou definidos como * para permitir todos.
  • _isolated_service_allowlist: controla quais serviços podem ser executados no processo de serviço isolado.

Execute os comandos abaixo para configurar todos os switches e sinalizações para usar ODP sem restrições:

# 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 do lado do dispositivo

Confira a documentação de referência da API do Android para ODP.

Interações com IsolatedService

A classe IsolatedService é uma classe de base abstrata que todos os desenvolvedores que pretendem desenvolver em relação à ODP precisam estender e declarar no manifesto do pacote como em execução em um processo isolado. O serviço ODP inicia esse serviço em um processo isolado e faz solicitações para ele. O IsolatedService recebe solicitações do serviço ODP e cria um IsolatedWorker para processar a solicitação.

Os desenvolvedores precisam implementar os métodos da interface IsolatedWorker para processar solicitações de apps cliente, finalizações de download e eventos acionados pelo HTML renderizado. Todos esses métodos têm implementações padrão de no-op, para que os desenvolvedores possam pular a implementação dos métodos em que não estão interessados.

A classe OnDevicePersonalizationManager fornece uma API para apps e SDKs interagirem com um IsolatedService implementado pelo desenvolvedor em execução em um processo isolado. Confira a seguir alguns casos de uso pretendidos:

Gerar conteúdo HTML para exibição em um SurfaceView

Para gerar conteúdo a ser mostrado com OnDevicePersonalizationManager#execute, o app de chamada pode usar o objeto SurfacePackageToken retornado em uma chamada requestSurfacePackage subsequente para solicitar que o resultado seja renderizado em um SurfaceView .

Em caso de sucesso, o receptor é chamado com um SurfacePackage para uma visualização renderizada pelo serviço ODP. Os aplicativos clientes precisam inserir o SurfacePackage em um SurfaceView na hierarquia de visualização.

Quando um app faz uma chamada requestSurfacePackage com um SurfacePackageToken retornado por uma chamada OnDevicePersonalizationManager#execute anterior, o serviço ODP chama IsolatedWorker#onRender para buscar o snippet HTML que será renderizado em um frame isolado. Um desenvolvedor não tem acesso a LocalData ou UserData durante essa fase. Isso impede que o desenvolvedor incorpore UserData potencialmente confidenciais nos URLs de busca de recursos no HTML gerado. Os desenvolvedores podem usar IsolatedService#getEventUrlProvider para gerar URLs de rastreamento a serem incluídos no HTML gerado. Quando o HTML for renderizado, o serviço ODP interceptará solicitações para esses URLs e chamará IsolatedWorker#onEvent. É possível invocar getRemoteData() ao implementar onRender().

Rastrear eventos no conteúdo HTML

A classe EventUrlProvider fornece APIs para gerar URLs de acompanhamento de eventos que os desenvolvedores podem incluir nas saídas HTML. Quando o HTML é renderizado, o ODP invoca IsolatedWorker#onEvent com o payload do URL do evento.

O serviço ODP intercepta solicitações para URLs de eventos gerados pelo ODP no HTML renderizado, chama IsolatedWorker#onEvent e registra o EventLogRecord retornado na tabela EVENTS.

Gravar resultados persistentes

Com OnDevicePersonalizationManager#execute, o serviço tem a opção de gravar dados no armazenamento persistente (tabelas REQUESTS e EVENTS). Estas são as entradas que podem ser inseridas nessas tabelas:

  • um RequestLogRecord a ser adicionado à tabela REQUESTS.
  • Uma lista de objetos EventLogRecord que serão adicionados à tabela EVENTS, cada um contendo um ponteiro para um RequestLogRecord gravado anteriormente .

Os resultados persistentes no armazenamento no dispositivo podem ser consumidos pelo aprendizado federado para treinamento de modelos.

Gerenciar tarefas de treinamento no dispositivo

O serviço ODP chama IsolatedWorker#onTrainingExample quando um job de treinamento de computação federada é iniciado e quer receber exemplos de treinamento fornecidos por desenvolvedores que adotaram a ODP. É possível invocar getRemoteData(), getLocalData(), getUserData() e getLogReader() ao implementar onTrainingExample().

Para programar ou cancelar jobs de computação federada, use a classe FederatedComputeScheduler, que fornece APIs para todos os IsolatedService do ODP. Cada job de computação federado pode ser identificado pelo nome de preenchimento.

Antes de programar um novo job de computação federada:

  • Uma tarefa com esse nome de população já precisa ter sido criada no servidor de computação federado remoto.
  • O endpoint do URL do servidor de computação federado já precisa estar especificado nas configurações do manifesto do pacote com a tag federated-compute-settings.

Interações com a saída persistente

A seção a seguir descreve como interagir com a saída persistente no ODP.

Ler tabelas locais

A classe LogReader fornece APIs para ler as tabelas REQUESTS e EVENTS. Estas tabelas contêm dados gravados por IsolatedService durante chamadas onExecute() ou onEvent(). Os dados nessas tabelas podem ser usados para treinamento de modelos facilitado pelo aprendizado federado ou análises estatísticas entre dispositivos facilitadas pela análise federada.

Interações com conteúdo salvo

A seção a seguir descreve como interagir com conteúdo transferido por download no ODP.

Fazer o download de conteúdo dos servidores

O serviço ODP faz o download do conteúdo do URL declarado no manifesto do pacote do IsolatedService periodicamente e chama onDownloadCompleted após a conclusão do download. O download é um arquivo JSON que contém pares de chave-valor.

Os desenvolvedores que adotarem o ODP podem escolher qual subconjunto do conteúdo transferido por download deve ser adicionado à tabela RemoteData e qual deve ser descartado. Os desenvolvedores não podem modificar o conteúdo transferido por download. Isso garante que a tabela RemoteData não contenha dados do usuário. Além disso, os desenvolvedores podem preencher a tabela LocalData como quiserem. Por exemplo, eles podem armazenar em cache alguns resultados pré-computados.

Formato da solicitação de download

A ODP pesquisa periodicamente o endpoint do URL declarado no manifesto do pacote do desenvolvedor para buscar conteúdo e preencher a tabela RemoteData.

Espera-se que o endpoint retorne uma resposta JSON conforme descrito mais adiante. A resposta JSON precisa conter um syncToken que identifique a versão dos dados enviados, junto com uma lista de pares de chave-valor a serem preenchidos. O valor de syncToken precisa ser um carimbo de data/hora em segundos, limitado a um limite de hora UTC. Como parte da solicitação de download, a ODP fornece o syncToken do download concluído e o país do dispositivo como os parâmetros syncToken e country no URL de download. O servidor pode usar o syncToken anterior para implementar downloads incrementais.

Formato do arquivo de download

O arquivo baixado é um arquivo JSON com a estrutura a seguir. O arquivo JSON precisa conter um syncToken para identificar a versão dos dados que está sendo transferido por download. O syncToken precisa ser um carimbo de data/hora UTC limitado a uma hora e precisa exceder o syncToken do download anterior. Se o syncToken não atender aos dois requisitos, o conteúdo transferido por download será descartado sem processamento.

O campo de conteúdo é uma lista de tuplas (chave, dados, codificação). O key precisa ser uma string UTF-8. O campo encoding é um parâmetro opcional que especifica como o campo data é codificado. Ele pode ser definido como "utf8" ou "base64" e é considerado "utf8" por padrão. O campo key é convertido em um objeto String, e o campo data é convertido em uma matriz de bytes antes de chamar 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 do lado do servidor

Nesta seção, descrevemos como interagir com as APIs do servidor de computação federada.

APIs Federated Compute Server

Para programar um job de computação federada no lado do cliente, você precisa de uma tarefa com um nome de população criado no servidor de computação federada remoto. Nesta seção, abordamos como criar essa tarefa no servidor de computação federada.

Diagrama da topologia cliente-servidor de computação federada.

Ao criar uma nova tarefa para o Criador de tarefas, os desenvolvedores de ODP precisam fornecer dois conjuntos de arquivos:

  1. Um modelo tff.learning.models.FunctionalModel salvo ao chamar a chamada de API tff.learning.models.save_functional_model. Confira um exemplo no nosso repositório do GitHub.
  2. Um fcp_server_config.json que inclui políticas, configuração de aprendizado federado e configuração de privacidade diferencial. Este é um exemplo de um 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
  }
}

Você pode encontrar mais amostras em nosso repositório do GitHub.

Depois de preparar essas duas entradas, invoque o Criador de tarefas para criar artefatos e gerar novas tarefas. Instruções mais detalhadas estão disponíveis no nosso repositório do GitHub.