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 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:

dependencies {
  implementation 'androidx.health:health-services-client:1.0.0-alpha03'
  // 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()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS 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.STEPS)
            }

            @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) {
       // Check that the Intent is for passive data
       if (intent?.action != PassiveMonitoringUpdate.ACTION_DATA) {
           return
       }
       val update = PassiveMonitoringUpdate.fromIntent(intent) ?: return

       // List of available data points
       val dataPoints = update.dataPoints

       // List of available user state info
       val userActivityInfoList = update.userActivityInfoUpdates
   }
}

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

<receiver
   android:name=".BackgroundDataReceiver"
   android:exported="true">
   <intent-filter>
       <action android:name="hs.passivemonitoring.DATA" />
   </intent-filter>
</receiver>

Once you have a BroadcastReceiver, register for updates using the PassiveMonitoringClient and provide a PassiveMonitoringConfig. 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.

val dataTypes = setOf(DataType.HEART_RATE_BPM, DataType.STEPS)
val config = PassiveMonitoringConfig.builder()
   .setDataTypes(dataTypes)
   .setComponentName(ComponentName(context, BackgroundDataReceiver::class.java))
   // To receive UserActivityState updates, ACTIVITY_RECOGNITION permission is required.
   .setShouldIncludeUserActivityState(true)
   .build()
lifecycleScope.launch {
   HealthServices.getClient(context)
       .passiveMonitoringClient
       .registerDataCallback(config)
       .await()
}

Use passive data

The dataPoints property in PassiveMonitoringUpdate contains a list of data points for all of the types subscribed to. 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 used with getStartInstant() or getEndInstant() for each data point accordingly.

Use UserActivityState information

PassiveMonitoringUpdate can provide high-level information on the user state, such as whether the user is sleeping or exercising. To receive these updates do the following:

  1. Ensure that the ACTIVITY_RECOGNITION permission is granted.
  2. Set setShouldIncludeUserActivityState(true) in the PassiveMonitoringConfig builder.

The userActivityInfoUpdates property returns a list of states and timestamps associated with the transition. If no state changes have occurred since the last PassiveMonitoringUpdate, the list returned may be empty.

// Inspect the last reported state change, if present
passiveMonitoringUpdate.userActivityInfoUpdates.lastOrNull()?.let { userActivityInfo ->
   // When the transition to this state took place
   val stateChangeInstant = userActivityInfo.stateChangeTime

   // The high-level state of the user, e.g. USER_ACTIVITY_ASLEEP, USER_ACTIVITY_EXERCISE
   val state = userActivityInfo.userActivityState

   if (state == UserActivityState.USER_ACTIVITY_EXERCISE) {
       // Obtain info about the exercise and whether it is owned by the app
       val exerciseInfo = userActivityInfo.exerciseInfo
   }
}

Subscribe to passive goals

Creating and registering passive goals 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 passive goals. A registration will only be replaced if the event matches a previous one requested by the same app.

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

<receiver
   android:name=".BackgroundGoalsReceiver"
   android:exported="true">
   <intent-filter>
       <action android:name="hs.passivemonitoring.GOAL" />
   </intent-filter>
</receiver>

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.

PassiveGoals can either be one-off goals, or be set to be repeated. The following example is a one-off goal for when 1000 steps is reached. However, changing the TriggerType to REPEATED would cause events to be broadcast every thousand steps.

For daily metrics, such as DAILY_STEPS and DAILY_CALORIES, setting these as REPEATED will result in the goal being triggered once per-day. For example, a DAILY_STEPS goal of 10000 steps will not trigger twice if 20000 steps are walked, and will reset at midnight local time.

// Create a goal for when 1000 steps are reached.
val thousandStepGoal = PassiveGoal(
   DataTypeCondition(
       DataType.STEPS,
       Value.ofLong(1000),
       ComparisonType.GREATER_THAN_OR_EQUAL),
   PassiveGoal.TriggerType.ONCE
)
lifecycleScope.launch {
    passiveClient.registerPassiveGoalCallback(
        thousandStepGoal,
        ComponentName(context, BackgroundGoalReceiver::class.java)
    ).await()
}

In the corresponding receiver code, first check the Intent action to ensure that it is for a PassiveGoal, then subsequently reconstruct the PassiveGoal from the Intent to determine what criteria caused the event:

class BackgroundGoalReceiver : BroadcastReceiver() {
   override fun onReceive(context: Context, intent: Intent) {
       // Check that the Intent is for a passive goal
       if (intent?.action != PassiveGoal.ACTION_GOAL) {
           return
       }
       // Obtain the goal to determine which goal caused the event
       val goal = PassiveGoal.fromIntent(intent) ?: return
       // Check against goals defined by the app
       if (goal == thousandStepGoal) {
           // Take appropriate action on reaching goal
       }
   }
}

Restore registrations after boot

Passive data and goal 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>