react-native-ble-manager icon indicating copy to clipboard operation
react-native-ble-manager copied to clipboard

[RFC] Implement companion device manager support

Open vhakulinen opened this issue 1 year ago • 11 comments

Android's CompanionDeviceManager is used to get access[1,2] (association) to a Bluetooth LE (and classic Bluetooth) devices without requiring too broad permission set (notably location on older android versions).

Using CompanionDeviceManager is also a requirement for implementing background connection management, tho' it also requires the application to implement CompationDeviceService[3].

CompationDeviceManager availability is not guaranteed, hence the few API version and feature detection checks.

iOS stubs are missing at the moment.

1: https://developer.android.com/develop/connectivity/bluetooth/companion-device-pairing 2: https://developer.android.com/reference/android/companion/CompanionDeviceManager 3: https://developer.android.com/reference/android/companion/CompanionDeviceService

Assuming this feature is welcome, TODO before ready:

  • [x] iOS stubs
  • [ ] Check the error handling (at least the onDeviceFound exception handling gives perhaps too long error message)
  • [x] docs

vhakulinen avatar Dec 10 '23 13:12 vhakulinen

I managed to adapt our application to use the companion scan - so at least its somewhat working.

~My motivation to get this in is to then use the CompationDeviceSerivce for managing bluetooth devices while the app is not in foreground.~ Scratch that, the companion device service is just for the newer android versions. The additional permissions might or might not help with keeping the app alive in the background.

vhakulinen avatar Dec 10 '23 13:12 vhakulinen

Hi @vhakulinen , interesting feature, do you test it?

marcosinigaglia avatar Dec 11 '23 08:12 marcosinigaglia

Yes, I'm working on this in our app.

Connecting to a device with the companion scanner works. The current iteration requires to subscribe to BleManagerCompanionPeripheral to receive the selected peripheral from the companion manager (compared to BleManagerConnectPeripheral for "manual" scanning).

I haven't tested with older phones, but based on the documentation you shouldn't need any location permissions for API version >=26.

I also managed to keep our app running in the background (i.e. after the user closes the app) through combination of permissions/uses-feature and a foreground service:

Permissions:

    <!-- We're using companion device manager, if available. -->
    <uses-feature android:name="android.software.companion_device_setup"/>
    <!-- With companion devices, we're exempted from background limitations.
         See https://developer.android.com/guide/components/foreground-services#bg-access-restrictions -->
    <uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
    <uses-permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
    <!-- TODO: is this required? Probably? -->
    <uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
    <!-- To keep our ConnectivityService running. -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>

Service declaration in manifest:

      <service
          android:name=".ConnectivityService"
          android:foregroundServiceType="connectedDevice"
          android:exported="false">
      </service>

Then the service it self is basically this, except with the foreground service type changed to the connected device one: https://developer.android.com/guide/components/foreground-services#start.

As I mentioned earlier, I ditched any plans to use the CompationDeviceService because its only for the latest API version and I need to support older devices too. Perhaps I'll get back to it at later time. It would allow waking the app when a companion device "appears" (i.e. someone's(?) scan notices its advertisement packets).

Edit: correct link

vhakulinen avatar Dec 11 '23 13:12 vhakulinen

Connecting to a device with the companion scanner works. The current iteration requires to subscribe to BleManagerCompanionPeripheral to receive the selected peripheral from the companion manager (compared to BleManagerConnectPeripheral for "manual" scanning).

Sorry, should be BleManagerDiscoverPeripheral instead of BleManagerConnectPeripheral

vhakulinen avatar Dec 11 '23 13:12 vhakulinen

Slight issue with devices that don't support/require bonding: if you've associated a device and dont bond with it, you'll need to scan for the device again after the bluetooth adapter gets turned off/on. In that case, you already have a associated device but the bluetooth adapter can't connect to it without scanning first (except maybe with the latest android API version). And its a bit pointless to scan for the device with the companion device manager because that would prompt the user to pick the device again.

To solve this, the scan function would need to decide whether to use the companion scanner or the default one.

vhakulinen avatar Dec 13 '23 17:12 vhakulinen

Updated based on the earlier comment.

The companion device manager thingy is now behind different API (i.e. scan vs. companionScan).

Still missing iOS stubs.

vhakulinen avatar Dec 15 '23 16:12 vhakulinen

Now would be good time for feedback (even if its just "looks good").

Two things that are missing: documentation (i.e. updates to README) and iOS stubs.

vhakulinen avatar Dec 15 '23 19:12 vhakulinen

Hi @vhakulinen , thanks for your time. I'll take a look the next week, it seems everything ok. I can do the iOS stubs. Maybe you can add a little paragraph in the doc to explain how the "companion device" works.

marcosinigaglia avatar Dec 16 '23 09:12 marcosinigaglia

Hi @vhakulinen you can integrate https://github.com/innoveit/react-native-ble-manager/tree/feat/ios-companion for the iOS stubs.

marcosinigaglia avatar Dec 30 '23 18:12 marcosinigaglia

Sorry for the delay, I'm busy with other urgent things at the moment. I'll continue this once I'm able to.

vhakulinen avatar Jan 08 '24 18:01 vhakulinen

There.

Rebased, added docs and applied the iOS stubs.

vhakulinen avatar Jan 19 '24 13:01 vhakulinen

In 62ac6221eb574a966152e618b38769fc52467c94, you removed the BleManagerCompanionPeripheral event.

This causes diverge between code thats using "normal" methods of connecting, which have the BleManagerConnectPeripheral event available. Can you consider adding either the companion specific event back, or chain up the companion scanner to the BleManagerConnectPeripheral event (possibly with some flag indicating that it actually is a companion device)?

vhakulinen avatar Apr 08 '24 10:04 vhakulinen

Hi, from my tests that I have done the companionScan does not also connect the device so the logic remains the same. In your case does the device connect automatically after it is chosen?

marcosinigaglia avatar Apr 08 '24 11:04 marcosinigaglia

Oh sorry, my bad. You're right, it doesn't automatically connect. So same argument, but with BleManagerConnectPeripheral replaced with discover event.

vhakulinen avatar Apr 08 '24 13:04 vhakulinen

So are you suggesting to add also the BleManagerDiscoverPeripheral to the companionScan success? I'm not sure that is better if we mix that thing. You can scan normally and have the same result with the companion device, is not filtered by the standard scan.

marcosinigaglia avatar Apr 08 '24 13:04 marcosinigaglia

Thats why it would need to have some flag in the returned object. Or restore the event that was removed.

vhakulinen avatar Apr 08 '24 13:04 vhakulinen

Ok, you think that is better to know if a discovered peripheral is a companion. You can check the associated peripheral but maybe is easier with a flag. I'll take a look. Thank for the PR.

marcosinigaglia avatar Apr 08 '24 13:04 marcosinigaglia

api version should be checked before loading companion device manager... now the module will cause app crash in android 7.1.1

iamfat avatar Apr 13 '24 11:04 iamfat

It is being checked, or did I miss it on some code path?

vhakulinen avatar Apr 13 '24 13:04 vhakulinen

public CompanionDeviceManager getCompanionDeviceManager() will cause the problem. Change it to public Object getCompanionDeviceManager(), add @RequiresApi(api = Build.VERSION_CODES.O) on getAssociatedPeripherals and removeAssociatedPeripheral, and cast Object to CompanionDeviceManager on each getCompanionDeviceManager() call could solve the crash problem.

iamfat avatar Apr 13 '24 13:04 iamfat

the getCompanionDeviceManager is only called after the API checks. Isn't that enough?

vhakulinen avatar Apr 13 '24 14:04 vhakulinen

I don't know why... but the cast operation in getCompanionDeviceManager did cause the NoClassDefFound error when loading the module.

below is the patch i did, it fixed the problem in emulator 25. [email protected]

API check on getAssociatedPeripherals and removeAssociatedPeripheral are not necessary. Not casting Context.COMPANION_DEVICE_SERVICE to CompanionDeviceManager in getCompanionDeviceManager fixed everything.

iamfat avatar Apr 13 '24 14:04 iamfat

Hi, I confirm the problem, probably at runtime is trying to load the class because is in the method signature. I'll make a PR soon.

marcosinigaglia avatar Apr 15 '24 13:04 marcosinigaglia

I think also the code is only working with API >= 33.

marcosinigaglia avatar Apr 15 '24 13:04 marcosinigaglia

@iamfat can you check the PR https://github.com/innoveit/react-native-ble-manager/pull/1205 ?

marcosinigaglia avatar Apr 15 '24 13:04 marcosinigaglia

Any chance to get the events removed in 62ac6221eb574a966152e618b38769fc52467c94 back?

If its just matter of adding them back, or if you have an alternative design in mind but lack the time, I can make a PR for it. Our app is structured around those (and the other discovered) events, and I'm currently stuck with our own fork form this PR.

vhakulinen avatar Apr 23 '24 10:04 vhakulinen

Hi @vhakulinen , we can restore it but I don't understand why this event is useful and the result of the promise and the getAssociatedPeripherals is not enough to figure out if a device is a companion device. Sorry, I don't want to be a pain in the ass it's just to understand if maybe it can be solved in another way.

marcosinigaglia avatar Apr 23 '24 15:04 marcosinigaglia

Sorry, I might've been unclear what I'm asking for.

What I want is to have a event based solution for discovering a companion device, i.e. BleManagerDiscoverPeripheral but for the companion scanner. This way you can limit the divergence between code paths on devices that support companion device manager and devices that dont.

vhakulinen avatar Apr 23 '24 18:04 vhakulinen