Monitor data in the background

Passive data updates are suited for apps that need to monitor Health Services data in the background. They are intended for use cases that span hours, days, or even longer. If you need to store or process health data when your app isn't running and the user is not explicitly engaged in an exercise, use Health Service's passive client.

For examples of passive data usage, see the Passive Data and Passive Goals samples on GitHub.

Add dependencies

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

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

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha02"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha02")
}

Check capabilities

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

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

Register for passive data

You can receive passive data through a service, a callback, or both. A service lets your app receive data in the background when no part of your app is visible in the foreground. When you receive data in the background, it is delivered in batches. The callback receives data at a slightly faster rate, but only while the app is running and the callback is successfully notified.

Whichever method you use, first create a PassiveListenerConfig that determines which data types to receive, as shown in the following example:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

To receive data using a callback, define and register the callback, as shown in the following example:

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

Using a service is similar, but instead of creating a class derived from PassiveListenerCallback, derive from PassiveListenerService, as shown in the following example:

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

Next, declare the service in your AndroidManifest.xml file. Require a Health Services permission, which ensures that only Health Services is able to bind to the service:

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

Interpret time

The data you receive from Health Services is batched, so you may receive data points of different types, or multiple data points of the same type, in the same batch. Use the timestamps included within these objects rather than the time they were received by your app to determine the correct ordering of events.

Obtain timestamps for each DataPoint by first calculating the boot timestamp, as shown in the following example:

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

This value can then be passed to getStartInstant() or getEndInstant().

Restore registrations after boot

Passive data registrations don't persist across reboots. To receive data after a device is restarted, re-create your registrations using a BroadcastReceiver that listens for the ACTION_BOOT_COMPLETED system broadcast.

In the receiver, don't attempt to restore the registrations directly. Instead, delegate this functionality to a WorkManager worker. When the device is starting up, Health Services might take 10 seconds or more to acknowledge a passive data registration request, and this might exceed the allowable execution time of a BroadcastReceiver. In contrast, WorkManager workers have a 10-minute execution limit.

The following snippet shows what a BroadcastReceiver might look like:

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
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

To arrange for the system to execute this code when the device boots up, make two changes to the AndroidManifest.xml file.

First, add the following permission as a child of <manifest>:

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

Second, add the following receiver intent filter as a child of <application>:

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

Activity state

The passive client can also provide high-level information on user state, such as whether the user is sleeping. To receive these updates, follow these steps:

  1. Request the ACTIVITY_RECOGNITION permission.
  2. Call setShouldUserActivityInfoBeRequested(true) in the PassiveListenerConfig builder.

Override the onUserActivityInfoReceived() method in your callback or service and make use of the returned UserActivityInfo, as shown in the following example:

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

Passive goals

You can configure a passive client to notify the app when passive goals are reached, such as the user completing 10,000 steps in a day.

To do this, create a goal, as shown in the following example:

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

Add this goal to your PassiveListenerConfig, as shown in the following example:

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

Override the onGoalCompleted() method in your callback or service and make use of the returned PassiveGoal, as shown in the following example:

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}