0

When using the BluetoothGattCallback on Android, I'm able to see when a bonded device connects and disconnects by overriding onConnectionStateChange and checking the newState field. However, I'm wondering if there's a means by which my app can be informed of a re-connect which is what I imagine when a bonded device becomes unreachable for a while.

Note that I'm using bonded devices here so that I'm able to obtain a stable MAC address.

About the answer

While I never managed to entirely test the answer, the answer given does make sense to me. A few things I've learned:

  • devices have to be bonded to reliably re-connect as their MAC addresses are randomised
  • with Android, a direct connection must first be achieved so that Android can cache connection information - subsequent connections can be performed with a re-connect
  • bonding on Android is difficult to get right (I'm yet to) - instead, I'm now leaning more toward leveraging the system UI to pair devices
  • use a library like the Nordid Android BLE Library - there are many quirks to Android

Old supplement to the question below - should now be ignored

The logs show that onConnectionStateChange is called:

16:21:33.681 D/BluetoothGatt: connect() - device: 57:D4:E9:34:A4:CB, auto: true
16:21:33.682 D/BluetoothGatt: registerApp()
16:21:33.683 D/BluetoothGatt: registerApp() - UUID=b4751d3b-ccdd-44c2-823e-deed18057af3
16:21:33.689 D/BluetoothGatt: onClientRegistered() - status=0 clientIf=6
16:22:44.154 D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=6 device=57:D4:E9:34:A4:CB

If the device later becomes disconnected, I also see the state change:

16:24:25.800 D/BluetoothGatt: onClientConnectionState() - status=8 clientIf=6 device=57:D4:E9:34:A4:CB

If the device reconnects given the auto-connection then a connection is re-established but I don't receive any notification:

16:26:30.519 D/BluetoothGatt: connect() - device: 57:D4:E9:34:A4:CB, auto: true
16:26:30.520 D/BluetoothGatt: registerApp()
16:26:30.521 D/BluetoothGatt: registerApp() - UUID=a973e2a7-b881-404a-875b-b2d25460e023
16:26:30.528 D/BluetoothGatt: onClientRegistered() - status=0 clientIf=7

So, how can an app detect a re-connect?

Christopher Hunt
  • 2,071
  • 1
  • 16
  • 20

1 Answers1

1

What you should do is to simply call connectGatt once with the autoConnect parameter set to true. When the device connects for the first time, you will get the onConnectionStateChange callback indicating that it's now connected. When the connection drops for some reason, you will get onConnectionStateChange indicating it's now disconnected. But you don't have to, and shouldn't, call connectGatt again. As soon as the peripheral advertises again, your Android device will automatically reconnect and you will get the onConnectionStateChange.

This will work until Bluetooth is turned off (as all Bluetooth objects are silently destroyed at that time), or your app process is terminated. After Bluetooth is turned on, you need to call connectGatt again.

Emil
  • 16,784
  • 2
  • 41
  • 52
  • Thanks for the reply. As you can see in my logs though, re: "As soon as the peripheral advertises again, your Android device will automatically reconnect and you will get the onConnectionStateChange." ... I don't see that `onConnectionStateChange` on reconnection... – Christopher Hunt Aug 13 '20 at 22:59
  • Are you sure then that your device advertises? You could also have a look at the hci snoop log to see what really happens. What I see in your log is that you try to call connectGatt again. By the way, is your peripheral using a random resolvable address? In that case, it might have changed. Please bond to make Android resolve address changes. – Emil Aug 13 '20 at 23:26
  • Thanks for the HCI snoop recommendation. I'll take a look. Pretty sure my app isn't calling `connectGatt` again and that this is automatic. On the bonding, I don't know how that applies in the context of Gatt so I'll have to research further. Perhaps by subscribing to a Gatt characteristic I get a bond? I don't know if the peripheral has a random resolveable address. – Christopher Hunt Aug 14 '20 at 01:26
  • Well, actually I was calling `connectGatt` again, and I've learnt a ton about bonding, primarily so I can get a stable device address. I shall report back further shortly. – Christopher Hunt Aug 14 '20 at 08:45
  • Really appreciate the comments and help here. I'm not quite at the point of being able to resolve the original question just yet. I was wondering if you'd have an inkling as to what an Android based peripheral device might refuse to bond. I'm getting a `BOND_NONE` when processing the `ACTION_BOND_STATE_CHANGED`. I will keep digging. – Christopher Hunt Aug 15 '20 at 00:32
  • I think it's easiest if the central calls `createBond`. Then you might get a pairing popup on both sides. – Emil Aug 15 '20 at 08:42
  • Yeah, that’s what I’m doing. I did see a pairing dialog show on both the central and peripheral and acknowledged them. I’ve not seen them since though and the central doesn’t have the peripheral as a paired device, nor does the peripheral show as bonded. I’m going to try and sniff the traffic to get more of an insight. – Christopher Hunt Aug 15 '20 at 21:09
  • OK, I believe I have bonding working for me now. I needed also to call `getBondedDevices` on starting up my Android service. However, I'm not seeing `onConnectionStateChange` happening for when my peripheral goes out of range or comes back within. In fact, I'm not even seeing onConnectionStateChange called upon having connected to a previously bonded device... so I'm still at a loss as to how I can detect bonded devices coming into and out of range... Any more ideas? – Christopher Hunt Aug 16 '20 at 02:06
  • Hey Emil - now that I've got a stable connection given bonded devices, I'm not seeing any `onConnectionStateChange` event if a peripheral device moves out of range. It is though the connection remains established. FYI I'm using the `BluetoothDevice.TRANSPORT_LE` as the 4th param to the `connectGatt` call, in case this is relevant. – Christopher Hunt Aug 17 '20 at 21:57
  • Are you moving it out of range enough to drop the connection? I have never heard of that the callback isn't triggered. A look at the hci log could maybe be interesting. – Emil Aug 18 '20 at 05:07
  • I've marked this as the answer because it has been very helpful in allowing me to understand that I needed to bond to reliably re-connect. Thanks. – Christopher Hunt Aug 24 '20 at 04:16
  • One thing: "with Android, a direct connection must first be achieved so that Android can cache connection information" - this is not true. Rather a scan should be made so Android temporarily "learns" the address type of the bdaddr. – Emil Aug 24 '20 at 08:40
  • I’ll have to look at the connectGatt java doc again. I’m sure it said that a direct connection was required prior to any subsequent auto connection. Various BLE libraries appear to support this behaviour also. – Christopher Hunt Aug 24 '20 at 12:22
  • Not true at all. See https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for a thorough explanation. – Emil Aug 24 '20 at 16:26
  • Looking at [the source for 8.1](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-cts-8.1_r20/core/java/android/bluetooth/BluetoothGatt.java#781) it states: "The autoConnect parameter determines whether to actively connect to the remote device, or rather passively scan and finalize the connection when the remote device is in range/available. Generally, the first ever connection to a device should be direct (autoConnect set to false) and subsequent connections to known devices should be invoked with the autoConnect parameter set to true.". Master is the same. – Christopher Hunt Aug 24 '20 at 23:52
  • Well the reason for that is that "direct connect" uses a scan with higher duty cycle, which means faster to connect, which is usually desired during a user interactive setup process. It's not at all a requirement to use it the first time a device is connected, autoConnect works too, but in that case be sure that the device uses a short advertising interval unless you are ok with that it can take some time to connect. – Emil Aug 25 '20 at 00:32