Passive data updates

Passive data updates are suited for apps that need to monitor Health Services data in the background. This is meant for long-lived experiences where data updates can be infrequent and spread over time. You can use passive data updates when your app isn't in use or running at the time the update is sent. With these APIs, you can chooose to receive all data points, or only data points related to a particular DataType.

See the Passive data and Passive events samples on GitHub.

Add dependencies

To add a dependency on Health Services, you must add the Google Maven repository to your project. Fore more information, see Google's Maven repository.

Then in your module-level build.gradle file, add the following dependency:

dependencies {
  implementation 'androidx.health:health-services-client:1.0.0-alpha01'
  // For kotlin, this library helps bridge between Futures and coroutines.
  implementation "com.google.guava:guava:30.1.1-android"
  implementation "androidx.concurrent:concurrent-futures-ktx:1.1.0"
}

In your AndroidManifest.xml file, add the following inside of the manifest tag so your app can interact with Health Services. For more information, see Package visibility.

<queries>
    <package android:name="com.google.android.wearable.healthservices" />
</queries>

Check capabilities

Before registering for data updates, check that the device can provide the type of background data your app needs. Checking capabilities beforehand allows you to enable or disable certain features, or modify your app's UI to compensate for capabilities that are not available.

Kotlin

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    supportsStepsEvent =
        DataType.AGGREGATE_STEP_COUNT in capabilities.supportedDataTypesEvents
}

Java

HealthServicesClient healthClient = HealthServices.getClient(this /*context*/);
PassiveMonitoringClient passiveClient = healthClient.getPassiveMonitoringClient();

ListenableFuture<PassiveMonitoringCapabilities> capabilitiesFuture = passiveClient.getCapabilities();
Futures.addCallback(capabilitiesFuture,
        new FutureCallback<PassiveMonitoringCapabilities>() {
            @Override
            public void onSuccess(@Nullable PassiveMonitoringCapabilities result) {
                supportsHeartRate = result
                        .getSupportedDataTypesPassiveMonitoring()
                        .contains(DataType.HEART_RATE_BPM)
                supportsStepsEvent = result
                        .supportedDataTypesEvents()
                        .contains(DataType.AGGREGATE_STEP_COUNT)
            }

            @Override
            public void onFailure(Throwable t) {
                // display an error
            }
        },
        ContextCompat.getMainExecutor(this /*context*/));

Receive passive data

To receive data updates in the background, use the PassiveMonitoringClient. Your app must have a BroadcastReceiver declared in its AndroidManifest.xml. When you register to receive updates from Health Services, they will be delivered to this receiver.

Health Services batches updates, so you may receive data points of different types, or multiple data points of the same type. Also, data points and user activity states may reflect events that occurred in the past. For example, it can take some time to detect that the user is asleep, and the user activity update is published retroactively. Use the timestamps included in these objects to properly evaluate them.

Inside onReceive, unpack the data using PassiveMonitoringUpdate.fromIntent(intent). The returned PassiveMonitoringUpdate contains a list of data points for the requested measurements. It also contains a list of UserActivityInfo objects describing the changes in the user's activity state and when those changes happened, as shown in the following example:

class BackgroundDataReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val state = PassiveMonitoringUpdate.fromIntent(intent) ?: return
        // Log data points. In a production app, store these to a database, etc.
        state.dataPoints.forEach {
            Log.i(TAG, "Data point: $it")
        }
        val latestActivity = state.userActivityInfoUpdates
            .maxByOrNull { it.stateChangeTime }
            ?.userActivityState
        Log.i(TAG, "latest activity state: $latestActivity")
    }
}

Add the following receiver to your AndroidManifest.xml inside the <application> tag:

<receiver
    android:name=".BackgroundDataReceiver"
    android:exported="true" />

Once you have a BroadcastReceiver, register for updates using the PassiveMonitoringClient and provide a PendingIntent. When you want to stop receiving data, unregister.

Registration is tied to your app, and each app is allowed at most one registration at a time. If you register more than once, the previous registration will be replaced. This means your registration should include all the data types you want to receive, and your BroadcastReceiver should handle all of those types.

// To register for data
val intent = Intent(this /*context*/, BackgroundDataReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
    this /*context*/, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT
)
val dataTypes = setOf(DataType.HEART_RATE_BPM, DataType.AGGREGATE_STEP_COUNT)
lifecycleScope.launch {
   HealthServices.getClient(context)
       .passiveMonitoringClient
       .registerDataCallback(dataTypes, pendingIntent)
       .await()
}

// To unregister
lifecycleScope.launch {
   HealthServices.getClient(context)
       .passiveMonitoringClient
       .unregisterDataCallback()
       .await()
}

Receive passive events

Registering for events follows a similar process as registering for data. However, unlike registering for data, each event is a separate registration and apps can register for multiple events. A registration will only be replaced if the event matches a previous one requested by the same app.

When it's time to register, define the event(s) that interest you and register each one. Use the same event definitions when you want to unregister the events. You can register to be notified of only the next occurrence of the event, or for every subsequent occurrence.

Health Services doesn't provide any information identifying which event was triggered. If you need to handle multiple events, you can either use a different BroadcastReceiver for each event, or use the same BroadcastReceiver with different PendingIntents. The following example takes the second approach, using a different action string per event:

const val OVER_1M_STEPS = "com.example.passivedata.OVER_1M_STEPS"
const val FASTER_THAN_SOUND = "com.example.passivedata.FASTER_THAN_SOUND"

val millionStepsEvent by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.AGGREGATE_STEP_COUNT,
        threshold = Value.ofLong(1_000_000),
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    Event(condition, Event.TriggerType.ONCE) // only the next occurrence
}

val speedOfSoundEvent by lazy {
    val speedOfSound = 343.0 // meters per second
    val condition = DataTypeCondition.builder()
        dataType = DataType.SPEED,
        threshold = Value.ofDouble(speedOfSound),
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    Event(condition, Event.TriggerType.REPEATED) // every occurrence
}

// To register for events
val stepsIntent = Intent(this /*context*/, BackgroundDataReceiver::class.java).apply {
    action = OVER_1M_STEPS
}
val stepsPendingIntent = PendingIntent.getBroadcast(
    this /*context*/, 1, stepsIntent, PendingIntent.FLAG_UPDATE_CURRENT
)
val speedIntent = Intent(this /*context*/, BackgroundDataReceiver::class.java).apply {
   action = FASTER_THAN_SOUND
}
val speedPendingIntent = PendingIntent.getBroadcast(
    this /*context*/, 1, speedIntent, PendingIntent.FLAG_UPDATE_CURRENT
)
lifecycleScope.launch {
    val passiveClient = HealthServices.getClient(this /*context*/)
        .passiveMonitoringClient
    passiveClient
        .registerEventCallback(millionStepsEvent, stepsPendingIntent)
        .await()
    passiveClient
        .registerEventCallback(speedOfSoundEvent, speedPendingIntent)
        .await()
}

// To unregister
lifecycleScope.launch {
    val passiveClient = HealthServices.getClient(this /*context*/)
        .passiveMonitoringClient
    passiveClient.unregisterEventCallback(millionStepsEvent).await()
    passiveClient.unregisterEventCallback(speedOfSoundEvent).await()
}

Restoring registrations after boot

Passive data and event registrations don't persist across device restarts. If your app needs to maintain a passive data registration across device restarts, you can do this with a BroadcastReceiver that receives the ACTION_BOOT_COMPLETED system broadcast.

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

While the device is turning on, there could be a delay (of ten seconds or longer) when registering for data. This may exceed the allowable execution time of a BroadcastReceiver. For this reason, instead of registering for data right away, schedule registration to happen in the future using WorkManager. For more information, see Get started with WorkManager.

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: check permissions first.
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
               .registerDataCallback(...)
               .await()
       }
       return Result.success()
   }
}

Add the following receiver to your AndroidManifest.xml inside the <application> tag:

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>