79239832

Date: 2024-11-30 15:12:32
Score: 2
Natty:
Report link

After a long struggle and feedback by several very helpful testers I think that I have found a reliable way to implement a CompanionDeviceService with startObservingDevicePresence.

In regard to your question, the behavior differs wildly across different Android versions and I highly recommend to test it on several devices. However, you should in any case start coroutines to handle your communication and (if applicable) show a notifiation to act as a foreground service and only use onDeviceAppeared as a launcher and onDeviceDisappeared as a hint on when to clean up.

However, things are complicated, so allow me to use this to share all my findings.

Background

For a few months now I am working on an open source BLE remote control for Sony cameras. I implemented it as a CompanionDeviceService and rely entirely on startObservingDevicePresence to start it. At the time of writing this, Android 15 is the most recent OS and I support a minimum of Android 12 (earliest startObservingDevicePresence). I am running a public beta and am confident about it enough by now to hopefully do a "proper release" in 1-2 weeks.

So, all of the following refers to BLE and you can find the code and user feedback I refer to on github.

How onDeviceAppeared/onDeviceDisappeared behaved in my tests

I have tested my app on a Pixel 6 with Android 15, a Pixel 3 with stock Android 12 and various LineageOS 20/21 images and on a few other Android 12 devices. I also got some logs from users with Android 14 devices and a Pixel 9 Pro with Android 15.

Across these devices I have found at least 4 different patterns for the onDeviceAppeared/onDeviceDisappeared callbacks:

Takeaways

The best solution seems to be to use the "outer" onDeviceAppeared and onDeviceDisappeared events to start/stop a service, but handle actual Bluetooth disconnects and reconnects independently inbetween. I think that this is actually the intended way to use these events: As indicator for when to start or stop the service. In fact, I think that these indicate the time during which you have the priviledged state of a companion service and are very unlikely to be killed. But the very different ways the events occur on different Android version and the barely existant documentation makes it really hard to figure out what you can expect. Even worse, the additional events since late Android 14 mean that you will have to ignore some cases.

My solution

So, I ended up literally counting the onDeviceAppeared events to only react to the first onDeviceAppeared and the last onDeviceDisappeared. I initialize my Bluetooth classes and call connectGatt on the first onDeviceAppeared. I free resources and terminate the service on the last onDeviceDisappeared (although, I think that Android would kill it eventually anyway).

Inbetween I let the "autoConnect" parameter of connectGatt keep the connection alive and observe its state through onConnectionStateChange of my BluetoothGattCallback. Especially any online/offline notification for the user is based on this. I actually even remove the notification when the device disconnects, so the users sees my service vanishing immediately (even though on Android 13/14 it actually stays around for a few minutes). So far, this seems to work well enough.

Note that this is not perfect for the Android 12 case where there is no matching onDeviceDisappeared after the very first connection. If you need that, you might have to handle Android 12 separately, but otherwise this only means that your service will stick around a bit longer afer the first startObservingDevicePresence and will eventually be cleaned by the system anyway

Also note, that according to the response I got on the Android issue tracker things might become easier with Android 16 when you can directly use onDevicePresenceEvent instead, but it will be a while until enough users have Android 16 to rely on. So, readers from the future with your fancy flying cars: Is it now nice to write BLE apps for Android or do you have more yearly API changes to worry about as we have seen since Android 4?

Reasons:
  • Blacklisted phrase (1): any help
  • Long answer (-1):
  • Has code block (-0.5):
  • Ends in question mark (2):
  • Low reputation (0.5):
Posted by: Sebastian Staacks