Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Implement companion device manager support #1134

Merged

Conversation

vhakulinen
Copy link
Contributor

@vhakulinen vhakulinen commented Dec 10, 2023

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:

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

@vhakulinen
Copy link
Contributor Author

vhakulinen commented Dec 10, 2023

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.

@marcosinigaglia
Copy link
Member

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

@vhakulinen
Copy link
Contributor Author

vhakulinen commented Dec 11, 2023

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
Copy link
Contributor Author

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
Copy link
Contributor Author

vhakulinen commented Dec 13, 2023

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
Copy link
Contributor Author

vhakulinen commented Dec 15, 2023

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 vhakulinen changed the title Implement companion device manager support [RFC] Implement companion device manager support Dec 15, 2023
@vhakulinen
Copy link
Contributor Author

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.

@marcosinigaglia
Copy link
Member

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
Copy link
Member

@vhakulinen
Copy link
Contributor Author

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

vhakulinen and others added 3 commits January 19, 2024 14:52
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.

Companion scanning is intentionally behind different API entry (`scan`
vs `companionScan`). This is because on Android you might end up needing
to scan for a peripheral that you already know as a companion. This
happens when you have a companion that is not bonded.

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
@vhakulinen vhakulinen marked this pull request as ready for review January 19, 2024 13:12
@vhakulinen
Copy link
Contributor Author

There.

Rebased, added docs and applied the iOS stubs.

@marcosinigaglia marcosinigaglia merged commit 32c55fd into innoveit:master Apr 8, 2024
@vhakulinen
Copy link
Contributor Author

In 62ac622, 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)?

@marcosinigaglia
Copy link
Member

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?

@vhakulinen
Copy link
Contributor Author

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

@marcosinigaglia
Copy link
Member

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.

@vhakulinen
Copy link
Contributor Author

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

@marcosinigaglia
Copy link
Member

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.

@iamfat
Copy link

iamfat commented Apr 13, 2024

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

@vhakulinen
Copy link
Contributor Author

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

@iamfat
Copy link

iamfat commented Apr 13, 2024

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.

@vhakulinen
Copy link
Contributor Author

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

@iamfat
Copy link

iamfat commented Apr 13, 2024

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.
react-native-ble-manager@11.5.0.patch

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

@marcosinigaglia
Copy link
Member

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
Copy link
Member

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

@marcosinigaglia
Copy link
Member

@iamfat can you check the PR #1205 ?

@vhakulinen
Copy link
Contributor Author

Any chance to get the events removed in 62ac622 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.

@marcosinigaglia
Copy link
Member

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.

@vhakulinen
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants