Exposure Notifications API

The Exposure Notifications API is a joint effort between Apple and Google to provide the core functionality for building Android apps to notify users of possible exposure to confirmed COVID-19 cases.

For an overview of the goals of the system, see the COVID-19 information & resources page.

This guide shows you how to use the Exposure Notifications API to build Android apps that notify users of possible exposure to confirmed COVID-19 cases.

Note: This guide is released in advance of the SDK and is updated in parallel with subsequent SDK versions. Prepare your implementation cycle accordingly.

This guide contains the following sections:

  • Architecture: This section describes how the system is distributed across different subsystems.
  • Exposure Notifications API v1.5 modes: This section describes the ExposureWindow and v1 modes introduced with v1.5 of the API.
  • Data structures and Methods: These sections describe how to interact with the API from your app.
  • Broadcast receivers: This section describes how to define broadcast receivers in your Android manifest and how to respond to received broadcasts.
  • Error handling: This section describes the error codes to check for when you call methods, provides suggestions about how to respond to the error codes, and how to handle other issues.
  • Reference design: This section provides links to the project on GitHub, which provides examples about how to interface with the API.
  • Glossary: This section provides definitions of terms specific to the Exposure Notifications API.

Architecture

The Exposure Notifications system spans several subsystems. This section describes this distribution.

Google Play services

Bluetooth functionality happens within on-device Google Play services. Bluetooth functionality includes all broadcasts and scans for BLE beacons, along with local database storage. Your app must have the BLUETOOTH and INTERNET permission in its manifest, but your app doesn't require and can't include ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, nor BLUETOOTH_ADMIN. For more information about restrictions on your app, see the API’s Terms of Service.

For your app to work on all devices, set your minSdkVersion to version 6.0 (API level 23) or higher. The framework works on some devices as low as version 5.0 (API level 21), so using this version is an option to increase the number of devices eligible. If you decide to set the minSdkVersion to API level 21, you should test your app on the most popular Android 5.0 devices in your country to ensure that your app works as intended.

The complete list of the parts of the system that Google Play services handles is as follows:

  • Management of daily random keys:

  • Management of Bluetooth broadcast and collection:

    • Enables broadcast beacons.
    • Scans for other beacons.
    • Stores observed RPIs in an on-device data store.
    • Identifies whether the user was in close contact with a confirmed case.
    • Calculates and provides an exposure risk level to the app.
  • Presentation of consent requests to users at the following points:

    • Performs this action the first time an app activates the system, before scanning and broadcasting begins.
    • Performs this action before user keys are provided to the app for upload to the internet-accessible server, after the user has been positively diagnosed with COVID-19.

Mobile app

Your app does the following:

  • Enable users to start and stop broadcast and scan functions.

  • Provide Temporary Exposure Keys and associated metadata (for both version 1.1 and 1.5) from your internet-accessible server to Google Play services.

  • Retrieve keys from the on-device data store and submit them to your internet-accessible server after one of the following conditions is present:

    • A user has been confirmed by a medical provider to have tested positive.

    • A health authority authorized a user-initiated self-report based on symptoms (if your app supports this use case).

    In both situations, the user must provide consent before the keys are provided to the app.

  • Schedule polls of your internet-accessible server for keys.

  • Register a receiver to receive broadcasts of the com.google.android.gms.exposurenotification.ACTION_EXPOSURE_STATE_UPDATED and com.google.android.gms.exposurenotification.ACTION_EXPOSURE_NOT_FOUND intents, and respond with a presentation of information to the user. See Broadcast receivers for more information.

  • Show a notification to the user with instructions about what to do next when the user has been exposed to another user who has tested positive for COVID-19.

Authenticated medical provider validation mechanism

Your app must provide functionality that confirms that the user has been positively diagnosed with COVID-19. This functionality controls the release of Temporary Exposure Keys stored on a device whose user is positively diagnosed. The released keys are sent to your app, which then uploads them to your internet-accessible server.

For more information about this part of the subsystem, see Exposure Notifications verification server.

Internet-accessible server

Your app must be able to communicate with an internet-accessible server that you own and that performs the following functions:

  • Collect diagnosis keys from users who have been diagnosed with COVID-19.
  • When polled, distribute diagnosis keys of confirmed cases to devices.

Exposure Notifications API v1.5 modes

With the release of v1.5, the Exposure Notifications API has changed significantly and now supports the following modes:

  • ExposureWindow mode: supports new ways of working with data introduced with v1.5. We recommend migrating to this mode.

  • v1 mode: supports all v1.1 functionality, even when running on a device that has the v1.5 API installed.

When calling the API, the app determines which mode to select based on a token provided to provideDiagnosisKeys:

.
ExposureWindow mode v1 mode
Token concept token must be TOKEN_AUse any token != TOKEN_A.

Using different tokens groups keys in different groups.

Obtain a summary risk score Not applicable. Compute the risk scores yourself using ExposureWindows and their ScanInstances instead. getExposureSummary().
Get detailed exposure information getExposureWindows(). getExposureInformation().
provideDiagnosisKeys quota
  • Six per day (where "day" = midnight to midnight of the device's local time zone)
  • 1,000,000 per day for allowlisted accounts
  • 20 per day over all tokens
  • 1,000,000 per day for allowlisted accounts
Exposure Notifications API versions supporting this mode Starting in v1.5. Starting in v1.

Data structures

The API contains the data classes described in the following list.

Name and description
Status This class contains codes that represent the state of the service on the device. For more information on how to use the information in this class, see Error handling.
ExposureConfiguration

(v1 mode)

In v1 mode, the ExposureConfiguration class configures the behaviors of getExposureSummary() and ExposureInformation(). If relying on those functions, populate this configuration object and pass it to provideDiagnosisKeys().

Note: This configuration has no impact on ExposureWindows reported through getExposureWindows().

Configuration fields are as follows.

minimumRiskScore ExposureSummary.getMaximumRiskScore(), ExposureSummary.getSummationRiskScore(), and ExposureInformation.getTotalRiskScore() do not use exposure incidents with scores lower than the value set in this field. Other returned fields are unaffected by this setting.
attenuationScores An array in which you specify how much risk to associate with the Bluetooth attenuation value from an exposure.
daysSinceLastExposureScores An array in which you specify how much risk to associate to an exposure based on the number of days since the exposure occurred.
daysSinceLastExposureWeight The daysSinceLastExposureWeight field is reserved for future use.
durationScores An array in which you specify how much risk to associate with an exposure based on the duration of the exposure.
durationScores The durationWeight field is reserved for future use
transmissionRiskScores An array of custom values that specify how much risk to associate with a risk factor you designate. For example, you might associate a custom value called highestRisk with a user who has recently tested positive, and a custom value called mediumRisk with a user who came into contact with a positively-diagnosed person.
durationAtAttenuationThresholds A two-value array representing the low and high attenuation thresholds to apply when calculating:
  • ExposureSummary.getAttenuationDurationsInMinutes
  • ExposureInformation.getAttenuationDurationsInMinutes

In v1 mode, the following fields are used in the following calculation to determine a RiskScore between 0 and 4096, inclusive:

RiskScore = attenuationScore * daysSinceLastExposureScore * durationScore * transmissionRiskScore

TemporaryExposureKey

The TemporaryExposureKey class contains the following fields:

keyData Used to generate broadcasts that are collected on the other devices. These connect to provide a record of the interaction between two devices. This information remains on a device until and unless the user tests positive, at which point the user can choose to share that information with the internet-accessible server.
rollingStartNumber The time at which the key was generated, in 10-minute intervals since UTC epoch. This time will align to UTC midnight.
rollingPeriod The number of 10-minute intervals that a key is valid for. The expiration date for a key can be calculated by adding rollingPeriod to rollingStartNumber. For keys that are valid for a full day, this value will be equal to 144. If a key was expired early, the value will be less than 144.
reportType

(v1.5 and higher only)

Type of diagnosis that this key was uploaded with. We recommend that the app set this value before uploading each key to the server. The values range from 0 to 5 and have the following definitions. The app must handle all of these values without crashing if received from the server.
  • 0: UNKNOWN. Report type metadata is missing from the diagnosis key.
  • 1: CONFIRMED_TEST. A medical provider has confirmed the user had a positive diagnostic test for COVID-19.
  • 2: CONFIRMED_CLINICAL_DIAGNOSIS. A medical provider has confirmed the user had symptoms consistent with a COVID-19 diagnosis.
  • 3: SELF_REPORT. The user has self-reported symptoms consistent with COVID-19 without confirmation from a medical provider.
  • 4: RECURSIVE. This value is reserved for future use.
  • 5: REVOKED. As of v1.5, REVOKED is not used. In future releases, REVOKED may eliminate exposures associated with that key from the detected exposures. This may never be implemented or may be implemented differently.
transmissionRiskLevel

(v1 mode)

In v1 mode, specifies the level of risk of cross-exposure for the duration of the interaction between devices.

The following are suggested uses. If used, your app should set this value before uploading each key to the server.

  • 0: Unused
  • 1: Confirmed test - Low transmission risk level
  • 2: Confirmed test - Standard transmission risk level
  • 3: Confirmed test - High transmission risk level
  • 4: Confirmed clinical diagnosis
  • 5: Self report
  • 6: Negative case
  • 7: Recursive case
  • 8: Unused/custom

This value is unused in ExposureWindows. See Transmission risk for more information.

ExposureSummary

(v1 mode)

In v1 mode, the ExposureSummary class summarizes the exposure to the set of keys associated with the same token. (This data is unavailable in ExposureWindow mode. Your app should instead compute this data from the ExposureWindow information.)

The ExposureSummary class contain the following fields.

daysSinceLastExposure The number of days since the last match to a diagnosis key.
matchedKeyCount The number of matched diagnosis keys.
maximumRiskScore The highest transmission risk level of all exposure incidents.
attenuationDurations

An array that contains the sum of all durations in minutes for all exposures for three ranges of radio signal attenuation:

  • Index 0: sum of durations for all exposures when attenuation was less than the low threshold.
  • Index 1: sum of durations for all exposures when attenuation was greater than or equal to the low threshold and less than the high threshold.
  • Index 2: sum of durations for all exposures when attenuation was greater than or equal to the high threshold.

The threshold values (low and high) are defined in the ExposureConfiguration object.

For more information on how attenuations are calculated, see the Bluetooth LE attenuation Overview.

summationRiskScore A summation of all risk scores of all exposures to keys provided with a given token.

Your app can present this information to users.

ExposureInformation

(v1 mode)

In v1 mode, the ExposureInformation class stores information in the following fields about an interaction with another device:

dateMillisSinceEpoch The day that the interaction occurred.
durationMinutes The duration of the exposure in five-minute increments.
attenuationValue The time-weighted average of the attenuation.

For more information on how attenuations are calculated, see Bluetooth LE attenuation Overview.

transmissionRiskLevel A transmission risk value.
totalRiskScore The total risk calculated for the exposure, represented as an integer between 0 and 4096, inclusive. See ExposureConfiguration for more information about the risk score.

You can use this information to gauge a level of risk of the exposure to filter out low-risk interactions, such as sub-five-minute interactions with a low signal strength attenuation that occurred 13 days earlier.

Each time this information is requested using getExposureInformation(), Google Play services displays a notification to the user.

ExposureWindow

(ExposureWindow mode)

The ExposureWindow class stores a duration of up to 30 minutes, during which beacons from a diagnosis key were observed. Each ExposureWindow corresponds to a single diagnosis key. However, one key can lead to several ExposureWindows due to the 30-minute cutoffs between exposure windows in a longer exposure, and the key itself is not exposed. See getExposureWindows() for more information.

ExposureWindows contain the following fields:

day Day of the exposure, using UTC, encapsulated as the time of the beginning of that day.
scanInstances Sightings of this ExposureWindow, time-ordered.

Each sighting corresponds to a scan of a few seconds, during which a beacon with the diagnosis key causing this exposure was observed.

reportType

Report type of the diagnosis key that caused this exposure. The app must handle the following values without crashing if received from the server.:

  • Keys with report type set to CONFIRMED_CLINICAL_DIAGNOSIS or SELF_REPORT are returned as is.
  • Keys with no report type set are returned with reportType=CONFIRMED_TEST.
  • Keys with a RECURSIVE report type may be dropped because this report type is reserved for future use.
  • Keys with REVOKED or invalid report types do not lead to exposures.

    Note: As of v1.5, REVOKED is not used.

infectiousness

(Planned for a future release but not implemented in v1.5)

Infectiousness of the diagnosis key that caused this exposure (STANDARD or HIGH), computed from its days since onset of symptoms using a configurable mapping.
calibrationConfidence

(Planned for a future release but not implemented in v1.5)

Confidence of the BLE TX_power calibration of the transmitting device, defined as follows:
  • LOWEST: Used an Android-wide average calibration.
  • LOW: Used an average calibration over devices from the same manufacturer, or the beacon originates from a version of the Android Exposure Notifications API lower than 1.5.
  • MEDIUM: Used a calibration based on a single-antenna orientation for a similar device model, or the beacon comes from a version of the iOS Exposure Notification API lower than 1.5.
  • HIGH: Used a calibration determined using significant calibration data for this device model.

    This field is not present in v1.5.

ScanInstance

(ExposureWindow mode)

In ExposureWindow mode, the ScanInstance class stores information about the sighting of beacons from a diagnosis key within a BLE scan (of a few seconds). The key itself is not exposed.

ScanInstances contain the following fields:

typicalAttenuation

Aggregation of the attenuations of all of a given diagnosis key's beacons received during the scan, in dB.

In v1.5, the aggregation applied is an average of the measurements in the dB domain.

minAttenuation Minimum attenuation of all of a given diagnosis key's beacons received during the scan, in dB.
secondsSinceLastScan

Seconds elapsed since the previous scan, coarsened to 60-second increments and capped at five minutes.

This value is typically used as a weight. For example:

  • Summing those values over all ScanInstances of an ExposureWindow provides the duration of that ExposureWindow.
  • Summing those values over all ScanInstances in a given attenuation range and over all exposures recreates the attenuationDurations of v1.

The previous scan may not have led to a sighting of that diagnosis key, or may not be part of the same ExposureWindow.

The coarsening is implemented in a way such that scans separated by 2.5 minutes will typically have secondsSinceLastScan alternating between 120 and 180.

Methods

The API provides functionality with the use of the methods shown in the following list. These are provided in a roughly chronological order of usage by an app.

To enable asynchronous operations, return-values from methods are wrapped in a Task object.

These methods might produce errors. Be sure to follow best practices and address errors as they occur. For more information about errors, see Error handling.

Name and description
start() Tells Google Play services to start the broadcast and scan process. The first time that this method is called after installation of the app, it prompts Google Play services to display a dialog box, where the user is asked to give permission to broadcast and scan.
isEnabled() Indicates whether exposure notifications are enabled on the device. Note that this does not check whether Bluetooth or location settings are on.
stop() Disables the broadcast and scan process. You can call this directly. It's also called when a user uninstalls the app. When it’s called as part of the uninstallation process, the database and keys are deleted from the device.
getTemporaryExposureKeyHistory()

Retrieves key history from the data store on the device to upload to your internet-accessible server. Calling this method prompts Google Play services to display a dialog that requests consent from the user to gather and upload their exposure keys.

The keys that are returned depend on the API version. These key include the past 14 days, but not the current day’s key. (This is different for allowlisted accounts, which also return the current day’s key.)

The consent granted by the user lasts for 24 hours. The dialog prompting for consent appears only once for every 24-hour period, regardless of how many times the app calls the method.

Planned for a future release but not currently activated in v1.5: The keys returned include the current day’s key, which is tagged with an expiration date (rollingPeriod). When the current key is released, the device stops using it to generate BLE beacons, and replaces it with a new key. Calling this causes a new key to be selected for the remainder of the current day. As a result, if this method is called multiple times, several keys with different rollingPeriod and identical rollingStartNumber can be released for those days it was called on.

provideDiagnosisKeys()

The provideDiagnosisKeys() method inserts one batch of files (prior to v1.5) or one or more batches of files (v1.5 and higher) containing key information into the on-device database.

These keys must correspond to confirmed cases retrieved from your internet-accessible server. Each batch (same start, country, and batch_count) must include the complete set of files in the batch, otherwise it discards all of them. Information about the file format is in Exposure Key export file format and verification.

This method accepts a token string used to identify a set of keys in future calls to the API. It effectively determines how keys accumulate over calls to provideDiagnosisKeys():

  • Keys provided with the same token accumulate into the same set, and are aged out of those sets as they pass out of the 14-day window.
  • Keys with different tokens are treated independently.

In v1.5 and higher of the Exposure Notifications API, the token is also used to select between two behaviors:

  • When token=TOKEN_A, this enables the ExposureWindow class and deactivates the ExposureSummary and ExposureInformation class.
  • When token is equal to any other value, this maintains the ExposureSummary and ExposureInformation features and deactivates the ExposureWindows feature.

Calls to this method are limited to six per day for token=TOKEN_A and 20 per day when token != TOKEN_A (Allowlisted accounts are allowed 1,000,000 calls per day.)

This quota applies per call, not per file or batch of files uploaded. In other words, a single call with multiple files or multiple batches counts as only one call. Days are defined as midnight to midnight of a device's local time zone.

Note: When downloading the files from the internet-accessible server, store them inside the app-specific directory on internal storage. Optionally, you can create a subdirectory under the filesDir and delete the subdirectory after you've finish processing.
getExposureSummary()

(v1 mode)

In v1 mode, the getExposureSummary() method retrieves the ExposureSummary object that matches the token from provideDiagnosisKeys() that you provide to the method. The ExposureSummary object provides a high-level overview of the exposure that a user has experienced.

This method does not support token=TOKEN_A. Use getExposureWindows() instead.

There are no quotas for this method.

getExposureInformation()

(v1 mode)

In v1 mode, the getExposureInformation() method provides a more in-depth version of the information provided by getExposureSummary(). If you pass in a token from provideDiagnosisKeys() for an exposure key, getExposureInformation() provides a list of ExposureInformation objects, from which you can gauge the level of risk of the exposure with the user.

Google Play services displays a notification to the user each time this method is invoked.

This method does not support token=TOKEN_A. Use getExposureWindows() instead.

There are no quotas for this method.

getExposureWindows()

(ExposureWindow mode)

The getExposureWindows() method retrieves the list of exposure windows corresponding to the keys given to provideDiagnosisKeys() with token=TOKEN_A.

Because long exposures to one key are split into windows of up to 30 minutes of scans, a given key may lead to several exposure windows if beacon sightings for it spanned more than 30 minutes. The link between them (in other words, the fact that they all correspond to the same key) is lost because those windows are shuffled before being returned and the underlying keys are not exposed.

This function accepts a token, which must always be set to TOKEN_A.

This method has no quota, and calling this method does NOT by itself trigger a user notification. However, information about how frequently the app called this function is included in the weekly summary notification.

Broadcast receivers

Your app must register a receiver to receive broadcasts of the ACTION_EXPOSURE_STATE_UPDATED and ACTION_EXPOSURE_NOT_FOUND intents. After provideDiagnosisKeys() is called, if a match is found, the ACTION_EXPOSURE_STATE_UPDATED broadcast is sent; if no match is found, the ACTION_EXPOSURE_NOT_FOUND broadcast is sent. Present information to the user in response to the broadcasts as needed. The following code sample shows how to do this:

public class ExposureNotificationBroadcastReceiver extends BroadcastReceiver
{
  @Override
  public void onReceive(Context context, Intent intent)
  {
    String action = intent.getAction();
    if (ExposureNotificationClient.ACTION_EXPOSURE_STATE_UPDATED
        .equals(action))
      {
        // Handle exposure found action
      }
    else if (ExposureNotificationClient.ACTION_EXPOSURE_NOT_FOUND
        .equals(action))
      {
        // Handle exposure not found action
      }
  }
}

When you declare the receiver, you must add the com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK permission to the receiver declaration in your Android manifest.

The following code snippet shows the entry for your manifest:

<receiver
    android:name=".exposurenotification.app.ExposureNotificationBroadcast"
    android:permission="com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK">
    <intent-filter>
        <action android:name="com.google.android.gms.exposurenotification.ACTION_EXPOSURE_STATE_UPDATED" />
        <action android:name="com.google.android.gms.exposurenotification.ACTION_EXPOSURE_NOT_FOUND" />
    </intent-filter>
</receiver>

For more information about broadcasts, see Broadcasts overview.

Error handling

To handle the errors of method calls, do the following:

  1. Add a failure callback to the returned Task using Task.addOnFailureListener().
  2. Cast the returned object to the APIException class.
  3. Get the Status using the getStatus() method, which provides information used to troubleshoot.

The Status provides:

For example, the status might indicate that the user has not opted-in (startResolutionForResult() would trigger the consent dialog), the service isn’t running, or that there's insufficient storage on the device to complete an operation.

client.start().addOnFailureListener(new OnFailureListener() {
  @Override
  public void onFailure(@NonNull Exception exception) {
    if (exception instanceof ApiException) {
      Status status = ((ApiException) exception).getStatus();
      if (status.hasResolution()) {
        status.startResolutionForResult(activity, REQUEST_START_CODE);
      } else {
        // Handle status.getStatusCode()
      }
    }
  }
});

In the preceding code, if there is an ApiException with a possible resolution. You launch it and handle the onActivityResult() of the given activity, as shown in the following code:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  switch (requestCode) {
    case REQUEST_START_CODE:
      if (resultCode == Activity.RESULT_OK) {
        // resolution solved (for example, the user gave consent)
        // retry the action, for example start EN API again.
        startClient();
      } else {
        // resolution rejected or cancelled
      }
      break;
  }
}

Force-stop handling

On devices that have version v1.5 or higher of the Exposure Notifications framework, the framework can automatically start the active Exposure Notifications process by binding to a service when it is force-stopped. While the app remains in the background, this action ensures that the app can execute any periodic tasks or receive broadcast events.

To enable this functionality, rebuild your app against the v1.5 or higher API.

Reference design

For examples that show how to integrate with the Exposure Notifications API, see the reference design source code hosted on GitHub.

Glossary

Allowlisted accounts
We have partnered with public health agencies for purposes of collaboration and testing of Exposure Notifications apps while they are in development. An allowlist grants specific permissions for these Google partners.
BLE beacons
Bluetooth Low Energy (BLE) beacons enable Bluetooth-equipped devices to share information when they're within range of each other.
Diagnosis key
The Temporary Exposure Key provided by the server that have been confirmed to have a positive diagnosis of COVID-19. It's used by the app to check for exposure.
Rotating Proximity Identifiers (RPIs)
A random ID derived from the device's Temporary Exposure Key. The RPI is generated on the device and a new RPI is generated every 10-20 minutes.
Temporary Exposure Key (TEK)
A key that's randomly generated once every 24 hours. It remains on the device for up to 14 days. In the event that there's a positive diagnosis of COVID-19, and upon permission granted from the user, these keys are provided to the app.
Report type (reportType)
Classifies the method of COVID-19 diagnosis for a given diagnosis key.
Transmission Risk (v1 mode)
In v1 mode, a value defined by your app that specifies how much risk is associated with a given exposure based on a value of importance to your health authority. This enables you to add an additional, non-API-specified value to the computation of total risk.