hungbui gravatar image

Posted 2016-11-14 15:42:17 +0200

blogs->nordicers

What to keep in mind when developing your BLE Android app

BLE is native supported on Android from v4.3. It has been continuously improved as Android evolving. It's getting more and more complex with additional features. It's important that a Bluetooth app should work smoothly on all Android versions, from 4.3 to the latest one. One may find it's tricky to keep track of all the issues from earlier Android versions and the changes in BLE APIs of different API levels.

To help you with this, our app developers have provided a note, discussing about most important changes and added features on newer Android version 5 and 6, including some testings we have done on different Android phones regarding BLE performance.

Some interesting topic:

  1. Required permissions for BLE app.

  2. Scanning: different scan mode, report delay, callback type, etc

  3. Connection: autoConnect, connection parameter update, MTU, number packets per connection parameters.

  4. GATT server, advertising, how to set it up.

This document is strongly recommended to whom developing Android apps:

BLE_on_Android_v1.0.1.pdf

(Updated 05Dec2016)

Question and issue, please post as a separated case and post the link here.

15 comments

luisOn gravatar image

Posted Nov. 15, 2016, 1:54 p.m.

Very useful document! Thanks for sharing!

romanrobles gravatar image

Posted Nov. 17, 2016, 8:09 p.m.

Thank you. Truly helpful.

Yacire93 gravatar image

Posted Nov. 20, 2016, 7:13 p.m.

Excellent! Thanks to Nordic Team.

torfinn gravatar image

Posted Nov. 26, 2016, 3:57 p.m.

Thanks for this very useful document! Is there an equivalent document for iOS, or is there one planned? We are developing our BLE sensor for both iOS and Android and these insights help tremendously.

hungbui gravatar image

Posted Nov. 28, 2016, 2:20 p.m.

Unfortunately, we don't have a guide now for iOS but we are considering to do one.

Emil gravatar image

Posted Nov. 30, 2016, 10:39 p.m.

Nice document! I have some corrections, additions, proposals as well as some warnings.

First the sub chapters are numbered incorrectly. (I'll refer to chapters using the Content table).

The link https://static.googleusercontent.com/media/source.android.com/en/devices/Android-6.0-Bluetooth-HCI-Reqs.pdf does not work. https://static.googleusercontent.com/media/source.android.com/en//devices/Android-6.0-Bluetooth-HCI-Reqs.pdf is the correct address.

In chapter 3, connecting to a BLE device using an address directly, as of Android Nougat it is no longer true that it will assume it is a public address. It varies between Android builds but it sometimes set the bit to random address. I wonder about the sentence "the logic being that random addresses can change any time, and if it is hardcoded, it must be a public one". Is that something someone at Google has confirmed or is that just an assumption? Actually I think this is an afterthought when they added BLE support since they didn't need to care about address type before. There are still other bugs where they mess up with the address type in their code...

I'd like to add to "The device has been scanned at least once before connection attempt." that Bluetooth must not have been restarted since it was latest discovered by a scan. Also, a third condition is that it also works without the need to scan before if the device is bonded (I've tested static random addresses and public addresses, not sure about random resolvable addresses).

In chapter 4, about the autoConnect parameter. I think the terminology is a bit misleading here: "If it's true, this method will not connect to the device, like it would with this parameter set to false, but rather adds its address to the whitelist. Whenever a device from the whitelist is discovered, the system will try to connect to it." It definitely attempts to connect to this device (with a HCI LE Create Connection). The only difference regarding that is that the address is provided by the white list instead of in the HCI LE Create Connection command. Also the standard configuration (which may be different between Android phone vendors) is to use more active scanning parameters when using autoConnect=false which leads to faster connection setups.

There are more differences: once the device disconnects with a reason other than the gatt object was disconnected/closed, a new connection attempt will be made automatically if either the autoConnect parameter was true or the connect() method was called on the gatt object, i.e. if you used autoConnect=false no further connection attempt will be made. With autoConnect=false the connection attempt will also get a timeout of 30 seconds. autoConnect=true has no timeout. Lastly, autoConnect=false will temporarily cancel all other pending connections to devices in the white list and autoConnect=false attempts are queued up. You should also be aware of https://code.google.com/p/android/issues/detail?id=228633 and certainly of https://code.google.com/p/android/issues/detail?id=69834 if you use autoConnect=true.

In chapter 4.3, you could add that a new restriction in Android is that connections interval lower than 11.25 ms are not possible anymore (which is important if you use the Connection Parameter Update Request from the slave side) due to audio streaming performance they had or something like that.

In chapter 4.2/4.5 (streaming data using Write Without Response). If the device loses the connection while the internal queue is full, no further GATT operations will work on that gatt object once it reconnects. See https://code.google.com/p/android/issues/detail?id=223558. Regarding older devices where overflow is not handled well, as well as on iOS, an easy workaround is to send each 15th packet or so as a Write With Response. Then you don't need to create any special acknowledgement logic on the peripheral side. It of course reduces speed a bit, but rather that than dropped packets.

In chapter 4.5. With Qualcomm Bluetooth chips, that is used in for example Nexus 5X, I've measured 12 packets per connection event.

When referring to particular lines in Android source code, link to a specific commit rather than master. Otherwise the links will soon not be valid anymore. If you link to a file as a whole I guess you may link to a file in the master branch however.

There are some race conditions in the Android SDK if you call methods too quickly after each other. See https://code.google.com/p/android/issues/detail?id=223582. If you need to have stable long-running connectivity to Android devices you should avoid restarting advertising immediately after a connection loss but rather wait a small time in between to prevent that the Android stack hangs. See https://code.google.com/p/android/issues/detail?id=219910.

If you have found bugs in Android (as I see in the pdf), be sure to tell Google about them or submit a pull request. Otherwise it is very likely that they will never be fixed.

hungbui gravatar image

Posted Dec. 2, 2016, 4:46 p.m.

Thanks a lot Emil for your detailed feedback !! I will forward your comment to our developer. We will udpate the document shortly and I will let you know when we do that.

AFNowakowski gravatar image

Posted Dec. 2, 2016, 6:07 p.m.

Hi, thanks for the long comment. Here's my reply:

  1. I have fixed chapter numbering. It was broken when the document got the Nordic'y look&feel and no one noticed. Sorry!
  2. I had some problems with the URL with double slash as Word apparently "fixes" the URL to have just one, but "/-/" trick works. Also not quite my fault, except I did not notice. Also is fixed now.
  3. "In chapter 3, connecting to a BLE device using an address directly, as of Android Nougat it is no longer true that it will assume it is a public address. It varies between Android builds but it sometimes set the bit to random address." - my source in Google says it's still a thing. How do you know it varies between builds? Are you talking about the different public 7.1-betas or something more internal?
  4. "I wonder about the sentence "the logic being that random addresses can change any time, and if it is hardcoded, it must be a public one". Is that something someone at Google has confirmed or is that just an assumption? Actually I think this is an afterthought when they added BLE support since they didn't need to care about address type before. There are still other bugs where they mess up with the address type in their code..." - I got this information from Google and it seems logical. If an app wants to connect to a BLE device that has never (since Bluetooth restart) been scanned before and it knows the address, it most probably mean that it's a public address, despite the fact that public addresses are super rare, as I can imagine. Here's what I got:

    Message received 5 Oct 2016: Only the MAC address is used to identify a device (no type). When an address is passed to lower levels in C, the system tries to match it with a security record which contains the address type. If such record does not exist a PUBLIC address is assumed - how could someone have a random address without scanning? It's a known problem and fixing it is in our backlog. Currently the best way to connect is to scan and use the returned BluetoothDevice object. Mind, that the security record is forgotten when Bluetooth adapter is restarted (unless it's a PUBLIC address or the device is bonded).

    Since then I also got the information, that in OOB pairing using NFC the address type encoded in the NFC message is also ignored in Android 7.1. The fix has already been made on AOSP and will be available in Android 7.2, or whatever comes next.

  5. "Also, a third condition is that it also works without the need to scan before if the device is bonded (I've tested static random addresses and public addresses, not sure about random resolvable addresses)" - true, I added The device has a PUBLIC address or is bonded, or

  6. "It definitely attempts to connect to this device (with a HCI LE Create Connection). The only difference regarding that is that the address is provided by the white list instead of in the HCI LE Create Connection command. Also the standard configuration (which may be different between Android phone vendors) is to use more active scanning parameters when using autoConnect=false which leads to faster connection setups." - as far as I remember in a test I made using a BLE sniffer, when I was trying to connect to an advertising HTC One M8 phone form a Nexus with autoConnect=true, there was no Connection Req at all. However, this chapter based only on my experience so I will change it to what you wrote. Thank you.

  7. "There are more differences:.[...]" - let me just copy the whole paragraph to the PDF :) Although, as you wrote in the second URL, the race condition is now fixed.

  8. "Regarding older devices where overflow is not handled well, as well as on iOS, an easy workaround is to send each 15th packet or so as a Write With Response. Then you don't need to create any special acknowledgement logic on the peripheral side. It of course reduces speed a bit, but rather that than dropped packets." - Nice idea. Added to the PDF.

  9. "In chapter 4.5. With Qualcomm Bluetooth chips, that is used in for example Nexus 5X, I've measured 12 packets per connection event." - wow, that's hell of a chip. Almost as good as Nordic's :)

  10. "When referring to particular lines in Android source code, link to a specific commit rather than master. Otherwise the links will soon not be valid anymore. If you link to a file as a whole I guess you may link to a file in the master branch however." - true... usually I gave a commit link + link to the most recent version. But it is true they may change.

  11. "There are some race conditions in the Android SDK if you call methods too quickly after each other. See https://code.google.com/p/android/issues/detail?id=223582. If you need to have stable long-running connectivity to Android devices you should avoid restarting advertising immediately after a connection loss but rather wait a small time in between to prevent that the Android stack hangs. See https://code.google.com/p/android/issues/detail?id=219910." - yes, adding delays help. If you check the source code for the Android DFU library I add a lot of those. For some devices it's enough to run a method from UI thread (I assume the small delay is enough, doesn't have to be the UI thread)

Thanks again for your long response. I'll update the PDF next week.

Emil gravatar image

Posted Dec. 2, 2016, 8:19 p.m.

And here's my feedback on the feedback ;)

"my source in Google says it's still a thing. How do you know it varies between builds? Are you talking about the different public 7.1-betas or something more internal?"

When I test again I see that it happens on all Nougat devices I have (3: Nexus 5X, Nexus 6, Sony Xperia Z3). I now notice however that the address type used depends on autoConnect. If the white list is used, random address seems always be assumed. But for autoConnect=false, public address is assumed instead. In earlier Androids it was always public address. If Google says it should be public then it's a bug. This is the commit (2016-02-02) that changed this: https://android.googlesource.com/platform/system/bt/+/d36b421035fe3b7d086f5d7737d8ba9fbdc471b3%5E%21/ and https://android-review.googlesource.com/#/c/200376/. See the last else-block, i.e. under "not a known device, i.e. attempt to connect to device never seen before". Now, instead of always choosing public address it seems they do some heuristics assuming random address, but if it is neither a non-resolvable, resolvable or static random address, then it is certainly a public address. The problem is that the bitcheck is written incorrectly to always return false, so random address is always chosen. That was however fixed by https://android.googlesource.com/platform/system/bt/+/42e055353ca397f3aaf350b99e79d42f79a478e1%5E!/ but that commit does not seem to be included in the latest nougat releases, so for now the address is always random.

And the simple answer to the Google employee's question "how could someone have a random address without scanning?" is simply that for example a static random address may definitely be known beforehand.

"... it most probably mean that it's a public address, despite the fact that public addresses are super rare, as I can imagine"

Why do you think public addresses are super rare? Just because Nordic's softdevice defaults to a static random address doesn't mean other stacks do the same ;)

"as far as I remember in a test I made using a BLE sniffer, when I was trying to connect to an advertising HTC One M8 phone form a Nexus with autoConnect=true, there was no Connection Req at all."

An easier way to inspect such things are to simply check Android's Bluetooth HCI snoop log. There you see all LE Create Connection commands and exactly what scan parameters are being used. Kitkat had terrible default scan parameters for autoConnect btw, like 11.25 ms each second or so.

""In chapter 4.5. With Qualcomm Bluetooth chips, that is used in for example Nexus 5X, I've measured 12 packets per connection event." - wow, that's hell of a chip. Almost as good as Nordic's :)"

Actually, Broadcom's chips (Nexus 5 for example) aren't actually that bad eihter. It is just the fact that by default Android sets both Minimum_CE_Length and Maximum_CE_Length to 0, and then the chip schedules (heuristically) to use max somewhere between 4 and 7 packets per event or so. If Maximum_CE_Length is set to something bigger, those chips manage to send packets during the whole connection event unless it overlaps with another connection. Some guys at Mediatek are working on a pull request changing this: https://android-review.googlesource.com/#/c/274900/.

AFNowakowski gravatar image

Posted Dec. 2, 2016, 8:39 p.m.

Why do you think public addresses are super rare? Just because Nordic's softdevice defaults to a static random address doesn't mean other stacks do the same ;)

I just assumed, that if you produce thousands of devices it is much easier to use random addresses then register them. But in fact i have no idea. Maybe for central devices it is better to ensure uniqueness, also don't know.

And the simple answer to the Google employee's question "how could someone have a random address without scanning?" is simply that for example a static random address may definitely be known beforehand.

Of course there are a lot of options, like simply saving it in the app from last connection. But from the stack perspective, after resetting Bluetooth, is a new, unknown one. The solution should be to add the address type flag, not assume anything. But for now, until it's fixed, something has to be decided or calculated heuristicaly.

Very interesting finding about the random address and autoConnect. I'll take a deeper look on Monday. Have a pleasant weekend and thanks again. I feel sorry for those who will read this ;)

Posted March 11, 2017, 9:48 p.m.

Include the pairing process too especially when you read characteristics that are encrypted.

IgorGanapolsky gravatar image

Posted April 13, 2017, 10:29 p.m.

When you say:

check Android's Bluetooth HCI snoop log

how do you do that on Nougat phones? I find it almost impossible to access that directory if you're not rooted.

AFNowakowski gravatar image

Posted April 13, 2017, 11:07 p.m.

As far as i know the snoop log is now available when you do "Take bug report".

Emil gravatar image

Posted April 24, 2017, 11:49 p.m.

Hi. I found a pretty serious issue that seems to happen quite randomly but very seldom. I've tried to find the cause in Android's source code but haven't yet. I also haven't found a reliable way to reproduce it either...

What happens is that the normal connectGatt-method with 3 parameters sometimes tries to establish a connection over Bluetooth Classic and not BLE, which of course fails (with status 133). Since Marshmallow there is a new variant of connectGatt with 4 parameters. The last parameter defines the "transport" in case the device is a dual-mode device (according to the documentation). This new parameter can be TRANSPORT_AUTO, TRANSPORT_BREDR or TRANSPORT_LE. Calling the 3-parameter connectGatt method internally uses TRANSPORT_AUTO.

For some reason I can see in logcat that the Bluetooth stack at some point starts to print "bt_btif_config: btif_get_device_type: Device [xx:xx:xx:xx:xx:xx] type 3" when a connection is started, where 3 means dual mode. It should print 2 (LE), since the advertising flags for this device tells BR/EDR is not supported.

Some people have wrote about it at https://issuetracker.google.com/issues/37120498 and http://stackoverflow.com/questions/28018722/android-could-not-connect-to-bluetooth-device-on-lollipop

My tip is to simply always use the new connectGatt method and set TRANSPORT_LE. Then this bug doesn't happen.

And regarding HCI snoop log,

how do you do that on Nougat phones? I find it almost impossible to access that directory if you're not rooted.

There is a script at https://android.googlesource.com/platform/system/bt/+/master/tools/scripts/ which converts a bug report into a wireshark-openable file. This patch https://android-review.googlesource.com/#/c/273832/ moved the snoop log. I still really don't understand why it was moved, since that makes it much more cumbersome to retrieve the log while developing.

AFNowakowski gravatar image

Posted April 25, 2017, 3:20 a.m.

Thanks again Emil! From my side I'll just add that nRF Connect 4.11 is using connectGatt method with 4 parameters (transport set to LE) since v 4.11. I'm using reflections on Android 4.4-5.1, where this method was hidden. On 4.3 it was not accessible.

Sign in to comment.

User menu

    or sign up

Recent questions