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.
- Displayed output:
- Developers can return HTML that is rendered by ODP in a
WebView
inside aSurfaceView
. 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.
- Developers can return HTML that is rendered by ODP in a
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
.
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:
- A
<property>
tag inAndroidManifest.xml
that points to an XML resource in the package that contains ODP configuration information. - A
<service>
tag inAndroidManifest.xml
that identifies the class that extendsIsolatedService
, as shown in the following example. The service in the<service>
tag must have the attributesexported
andisolatedProcess
set totrue
. - 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 theREQUESTS
table. - a list of
EventLogRecord
objects to be added to theEVENTS
table, each containing a pointer to a previously writtenRequestLogRecord
.
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.
When creating a new task for the Task Builder, ODP developers should provide two sets of files:
- 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.
- 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.