This post summarizes a two month long journey in the depths of Android’s Bluetooth Stack. It wasn’t nice.
Technically, Bluetooth Classic is supported even on pretty old devices. Bluetooth Low Energy is available as an API since API Level 18 and supported by a broad range of devices. Theoretically. The first part here is dedicated to Bluetooth LE, while the second part discusses Bluetooth Classic in the Android world.
Bluetooth LE on Android
The next Parapgraph is no longer true: Android 5.0 supports the peripheral role. I left this paragraph in there for historical reasons.
Bluetooth LE depends on a Server and a Client device. Technically, every device that can function as a client has the ability to also function as a server. The main difference is that servers advertise their services using a special kind of packet. Android doesn’t work as a Server (or Peripheral) for no single sane reason. Even though the API exists, using it leads to an immediate crash.
Searching for servers a device can connect to, like a heartrate sensor is called discovery and is the first phase when it comes to working with Bluetooth LE. The Android Framework supports searching for Services by UUID (a UUID is unique to a service and identifies it) or without an UUID, thus returning all available devices that expose some kind of BLE functionality. The corresponding method calls in the
BluetoothAdapter class are
public boolean startLeScan (UUID serviceUuids, BluetoothAdapter.LeScanCallback callback) public boolean startLeScan (BluetoothAdapter.LeScanCallback callback)
It’s nowhere documented that this API doesn’t work that way, not even on a Nexus 5. This is a known bug. It works on some devices using no filtering, on other devices only using a specific UUID. Have fun figuring out which way is the right one.
Of course, discovery sometimes doesn’t work at all though all calls indicate that everything is working fine. If that happens, one of the following solutions might work, but are not guaranteed to:
- Cycle Bluetooth
- Cycle WiFi
- Reboot the device
For a more in depth discussion of this specific problem, there are a few threads on Stack Overflow:
- Bluetooth Low Energy Service Discovery with Android 4.3 on Nexus 4,
Depending on the device, a call to
close() might be necessary on a
BluetoothGatt, though calling both may be worse than calling none at all. (See this answer).
So, you finally found the device you’ve been looking for and are trying to subscribe to a value. What is subscribing? Well, basically BLE knows three interaction modes: Writing a value, Reading a value and subscribing to value changes. It wouldn’t be our favorite most open operating system in the world if not at least one of those modes was completely broken, unfixable and without any resolution in sight. Let me introduct
Subscribing to Characteristics
iOS devices are, contrary to Android devices, capable of advertising services, thus functioning as a server. Unfortunately, it’s impossible for an Android device to subscribe to those values. Why is that? It’s not completely clear, but there seems to be a faulty protocol implementation on the Android Side at work. This is unique to iOS in some ways, as connecting to other peripherals sometimes works (Though not reliable. You can submit a pull request to fix that.). Bug Report: Android 4.3 - iOS failed communication over BLE.
Reading and Writing Values
This functionality works as advertised.
Power Cycle all the things. This is a friendly reminder that the Bluetooth Stack on most Android devices is about as stable as a House of Cards.
The classic Bluetooth stack is considerably more stable than the BLE stack. It works mostly as advertised, with no big hurdles to master.
Working with Connections
Here comes the tricky part. Sometimes (I really can’t tell when and why), the
Socket-Instances returned from the Framework to communicate with a remote Bluetooth device just close randomly. At first I thought that now would be the perfect time to reconnect but.. this is not necessary in most cases. There is a workaround that looks like Peter Steinberger would be proud of the Author. It involves reflection, magic and a lot of positive thinking to put this in production code. It’s documented in a Stack Overflow answer to the question: IOException: read failed, socket might closed - Bluetooth on Android 4.3.
I have no idea what to think about this whole situation. It’s an incredible mess with no one taking responsibility or trying to fix that.