Passive data updates

Stay organized with collections Save and categorize content based on your preferences.

Passive data updates are suited for apps that need to monitor Health Services data in the background and is 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, you should use Health Service's passive client.

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.

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

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.0.0-beta01"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.0.0-beta01")
}

Check capabilities

Before registering for data updates, check that the device can provide the type of data your app needs. Checking capabilities allows you to 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 or a callback (or both). A service allows your app to 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, you first need to 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. 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.

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

Your BroadcastReceiver should look something like the following:

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.

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:

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:

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