Search code examples
androidandroid-jetpack-composewear-osambient

Capability Info, Health Service, and Ambient Mode


I'm trying to write a fitness companion watch app that would collect heart rate, and calories via HealthServices API, and send them to the device, where we display a workout. I've been following suggested examples: https://github.com/android/wear-os-samples/tree/main/AlwaysOnKotlin, https://github.com/android/health-samples/tree/2220ea6611770b56350d26502faefc28791f3cbd/health-services/ExerciseSample, and https://github.com/googlecodelabs/ongoing-activity .

I'm trying to achieve the following workflow:

  • Launch app on Wear device when X happens on the phone
  • Start exercise client on Wear
  • Send heart rate/calories update on a regular basis back to phone
  • Show summary screen, and stop exercise client when Y happens on the phone.

All of these work somewhat well until the watch goes into ambient mode. Then I run into the following problems:

  • When watch is in ambient mode, the capabilities client on the phone cannot locate watch, and tell it to start exercise. Nor can it tell it to stop exercise. What is a suggested workaround for this?

I use message client on phone to send message to the wearable. But nothing happens here, since the current node is empty.

currentNode?.also { nodeId ->
            val sendTask: Task<*>? =
                messageClient
                    ?.sendMessage(nodeId, WORKOUT_STATUS_MESSAGE_PATH, "START.toByteArray())
  • When trying to simulate ambient mode by pressing 'hand' on the watch simulator, the ambient mode listener does not actually trigger to tell me the right thing. The screen gets "stuck" instead of updating to what I want it to.

Code for the ambient mode in MainActivity (I'm still learning Compose, so right now Main activity is where it's at, to eliminate other Compose specific errors):

In Manifest:

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

In Main Activity:

class MainActivity : FragmentActivity(), AmbientModeSupport.AmbientCallbackProvider {

...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    ambientController = AmbientModeSupport.attach(this)

    setContent {
        val ambientEvent by mainViewModel.ambientEventFlow.collectAsState()
        StatsScreen(ambientEvent)
    }
        ...
}

override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback = AmbientModeCallback()

inner class AmbientModeCallback : AmbientModeSupport.AmbientCallback() {
    override fun onEnterAmbient(ambientDetails: Bundle) {
        Timber.e("ambient event: enter: $ambientDetails")
        mainViewModel.sendAmbientEvent(AmbientEvent.Enter(ambientDetails))
    }

    override fun onExitAmbient() {
        Timber.e("ambient event: exit")
        mainViewModel.sendAmbientEvent(AmbientEvent.Exit)
    }

    override fun onUpdateAmbient() {
        Timber.e("ambient event: update")
        mainViewModel.sendAmbientEvent(AmbientEvent.Update)
    }
}

I don't see anything printed in this callback, and then consequently, by StateScreen doesn't really do anything when the device enters in the ambient mode.


Solution

  • On Android 13 you need BODY_SENSORS_BACKGROUND permission. See: https://developer.android.com/about/versions/13/behavior-changes-13#body-sensors-background-permission