On-Device Personalization developer guide

On-Device Personalization (ODP) is designed to protect end users' information from applications. Applications use ODP to customize their products and services for end users, but they won't be able to see the exact customizations made for the user (unless there are direct interactions outside of ODP between the application and the end user). For applications with machine learning models or statistical analyses, ODP provides a set of services and algorithms to ensure that they are properly anonymized using the appropriate Differential Privacy mechanisms. For further details, see the explainer on On-Device Personalization.

ODP runs developer code in an IsolatedProcess which has no direct access to the network, local disks or other services running on the device but has access to the following locally persisted data sources:

  • RemoteData - Immutable key-value data downloaded from remote, developer operated backends, if applicable.
  • LocalData - Mutable key-value data locally persisted by the developer, if applicable.
  • UserData - User data provided by the platform.

The following outputs are supported:

  • Persistent output: These outputs can be used in future local processing, producing displayed outputs, Federated Learning facilitated model training, or Federated Analytics facilitated cross-device statistical analysis.
    • Developers can write requests as well as their processing results to the local REQUESTS table.
    • Developers can write additional data associated with a prior request to the EVENTS table.
  • Displayed output:
    • Developers can return HTML that is rendered by ODP in a WebView inside a SurfaceView. The content rendered there won't be visible to the invoking app.
    • Developers can embed ODP-provided Event URLs within the HTML output to trigger the logging and processing of user interactions with the rendered HTML. ODP intercepts requests to those URLs and invokes code to generate data that gets written to the EVENTS table.

Client apps and SDKs can invoke ODP to display HTML content in a SurfaceView using ODP APIs. Content rendered in a SurfaceView is not visible to the calling app. The client app or SDK can be a different entity than the one developing with ODP.

The ODP service manages the client app that wishes to invoke ODP to show personalized content within its UI. It downloads content from developer provided endpoints and invokes logic for postprocessing of the downloaded data. It also mediates all communications between the IsolatedProcess and other services and apps.

Client apps use methods in the OnDevicePersonalizationManager class to interact with the developer's code running in an IsolatedProcess. Developer's code running in an IsolatedProcess extends the IsolatedService class and implements the IsolatedWorker interface. The IsolatedService needs to create an instance of IsolatedWorker for each request.

The following diagram shows the relationship between the methods in OnDevicePersonalizationManager and IsolatedWorker.

Diagram of the relationship between OnDevicePersonalizationManager and IsolatedWorker.

A client app calls ODP using the execute method with a named IsolatedService. The ODP service forwards the call to the onExecute method of the IsolatedWorker. The IsolatedWorker returns records to be persisted and content to be displayed. The ODP service writes the persistent output to the REQUESTS or EVENTS table, and returns an opaque reference to the displayed output to the client app. The client app can use this opaque reference in a future requestSurfacePackage call to display any of the display content within its UI.

Persistent output

The ODP service persists a record in the REQUESTS table after the developer's implementation of onExecute returns. Each record in the REQUESTS table contains some common per-request data generated by the ODP service, and a list of Rows returned. Each Row contains a list of (key, value) pairs. Each value is a scalar, String or blob. The numeric values can be reported after aggregation, and the string or blob data can be reported after applying local or central Differential Privacy. Developers can also write subsequent user interaction events to the EVENTS table—each record in the EVENTS table is associated with a row in the REQUESTS table. The ODP service transparently logs a timestamp and the package name of the calling app and the ODP developer's APK with each record.

Before you begin

Before you begin developing with ODP, you need to set up your package manifest and enable developer mode.

Package manifest settings

To use ODP the following is required:

  1. A <property> tag in AndroidManifest.xml that points to an XML resource in the package that contains ODP configuration information.
  2. A <service> tag in AndroidManifest.xml that identifies the class that extends IsolatedService, as shown in the following example. The service in the <service> tag must have the attributes exported and isolatedProcess set to true.
  3. A <service> tag in the XML resource specified in Step 1 that identifies the service class from Step 2. The <service> tag must also include additional ODP-specific settings inside the tag itself, as shown in the second example.

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>

ODP-Specific Manifest in XML Resource

The XML resource file specified in the <property> tag must also declare the service class in a <service> tag, and specify the URL endpoint from which ODP will download content to populate the RemoteData table, as shown in the following example. If you are using the federated compute features, you also need to specify the federated compute server URL endpoint to which the federated compute Client will connect.

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

Enable Developer Mode

Enable developer mode by following the instructions in the Enable Developer Options section of the Android Studio documentation.

Switch and Flag settings

ODP has a set of switches and flags that are used to control certain functionalities:

  • _global_killswitch: the global switch for all ODP features; set to false to use ODP
  • _federated_compute_kill_switch: _the switch controlling all training (federated learning) functionalities of ODP; set to false to use training
  • _caller_app_allowlist: controls who is allowed to call ODP, apps (pkg name, [optional] certificate) can be added here or set it as * to allow all
  • _isolated_service_allowlist: controls which services can run in the Isolated Service process.

You can run the following commands to configure all switches and flags to use ODP without restrictions:

# 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 \"*\"

Device-side APIs

Check out the Android API reference documentation for ODP.

Interactions with IsolatedService

The IsolatedService class is an abstract base class that all developers intending to develop against ODP must extend, and declare in their package manifest as running in an isolated process. The ODP service starts this service in an isolated process and makes requests to it. The IsolatedService receives requests from the ODP service and creates an IsolatedWorker to handle the request.

Developers need to implement the methods from the IsolatedWorker interface to handle client app requests, download completions, and events triggered by the rendered HTML. All these methods have default no-op implementations, so developers can skip implementing the methods that they are not interested in.

The OnDevicePersonalizationManager class provides an API for apps and SDKs to interact with a developer-implemented IsolatedService running in an isolated process. The following are some intended use cases:

Generate HTML content to display in a SurfaceView

To generate content to display, with OnDevicePersonalizationManager#execute, the calling app can use the returned SurfacePackageToken object in a subsequent requestSurfacePackage call to request the result to be rendered in a SurfaceView .

On success, the receiver is called with a SurfacePackage for a View rendered by the ODP service. Client applications need to insert the SurfacePackage into a SurfaceView within their View hierarchy.

When an app makes a requestSurfacePackage call with a SurfacePackageToken returned by a prior OnDevicePersonalizationManager#execute call the ODP service calls IsolatedWorker#onRender to fetch the HTML snippet to be rendered within a fenced frame. A developer does not have access to LocalData or UserData during this phase. This prevents the developer from embedding potentially sensitive UserData within asset fetch URLs in the generated HTML. Developers can use IsolatedService#getEventUrlProvider to generate tracking URLs to include in the generated HTML. When the HTML is rendered, the ODP service will intercept requests to these URLs and call IsolatedWorker#onEvent. One can invoke getRemoteData() when implementing onRender().

Track events within HTML content

The EventUrlProvider class provides APIs to generate event tracking URLs that developers may include in their HTML output. When the HTML is rendered, ODP will invoke IsolatedWorker#onEvent with the payload of the event URL.

The ODP service intercepts requests to ODP generated event URLs within the rendered HTML, calls IsolatedWorker#onEvent and logs the returned EventLogRecord into the EVENTS table.

Write persistent results

With OnDevicePersonalizationManager#execute, the service has the option to write data to persistent storage (REQUESTS and EVENTS tables). Here are the entries one can write into these tables:

  • a RequestLogRecord to be added to the REQUESTS table.
  • a list of EventLogRecord objects to be added to the EVENTS table, each containing a pointer to a previously written RequestLogRecord .

Persistent results in on-device storage can be consumed by Federated Learning for model training.

Manage on-device training tasks

The ODP service calls IsolatedWorker#onTrainingExample when a federated compute training job starts and wants to get training examples provided by developers adopting ODP. You can invoke getRemoteData(), getLocalData(), getUserData() and getLogReader() when implementing onTrainingExample().

To schedule or cancel federated compute jobs, you can use the FederatedComputeScheduler class which provides APIs for all ODP IsolatedService. Each federated compute job can be identified by its population name.

Before you schedule a new federated compute job:

  • A task with this population name should already be created on the remote federated compute server.
  • federated compute server URL endpoint should already be specified in the package manifest settings with the federated-compute-settings tag.

Interactions with persistent output

The following section describes how to interact with persistent output in ODP.

Read local tables

The LogReader class provides APIs to read the REQUESTS and EVENTS tables. These tables contain data that was written by IsolatedService during onExecute() or onEvent() calls. The data in these tables can be used for Federated Learning facilitated model training, or Federated Analytics facilitated cross-device statistical analysis.

Interactions with downloaded content

The following section describes how to interact with downloaded content in ODP.

Download content from servers

The ODP service periodically downloads content from the URL declared in the package manifest of the IsolatedService, and calls onDownloadCompleted after the download is finished. The download is a JSON-file containing key-value pairs.

Developers adopting ODP can choose which subset of the downloaded content should be added to the RemoteData table and which should be dropped. Developers cannot modify the downloaded contents - this ensures that the RemoteData table does not contain any user data. In addition, developers are free to populate the LocalData table as they choose; for example, they can cache some precomputed results.

Download request format

ODP periodically polls the URL endpoint declared in the developer package manifest to fetch content to populate the RemoteData table.

The endpoint is expected to return a JSON response as described later. The JSON response must contain a syncToken that identifies the version of the data being sent, along with a list of key-value pairs to be populated. The syncToken value must be a timestamp in seconds, clamped to a UTC hour boundary. As part of the download request, ODP provides the syncToken of the previously completed download and the device country as the syncToken and country parameters in the download URL. The server can use the previous syncToken to implement incremental downloads.

Download file format

The downloaded file is a JSON file with the following structure. The JSON file is expected to contain a syncToken to identify the version of the data that is being downloaded. The syncToken must be a UTC timestamp clamped to an hour boundary, and must exceed the syncToken of the previous download. If the syncToken does not meet both requirements, the downloaded content is discarded without processing.

The contents field is a list of (key, data, encoding) tuples. The key is expected to be a UTF-8 string. The encoding field is an optional parameter that specifies how the data field is encoded - it can be set to either "utf8" or "base64", and is assumed to be "utf8" by default. The key field is converted to a String object and the data field is converted to a byte array before calling 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"
    },
    // ...
  ]
}

Server-side APIs

This section describes how to interact with the federated compute server APIs.

Federated Compute Server APIs

To schedule a federated compute job on the client side, you need a task with a population name created on the remote federated compute server. In this section, we cover how to create such a task on the federated compute server.

Diagram of the federated compute client-server topology.

When creating a new task for the Task Builder, ODP developers should provide two sets of files:

  1. A saved tff.learning.models.FunctionalModel model through calling tff.learning.models.save_functional_model API call. You can find one sample in our GitHub repository.
  2. A fcp_server_config.json which includes policies, federated learning setup and differential privacy setup. The following is an example of a 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
  }
}

You can find more samples in our GitHub repository.

After preparing these two inputs, invoke the Task Builder to construct artifacts and generate new tasks. More detailed instructions are available in our GitHub repository.