Android Bluetooth API: all you need to know

Konstantinos Mihelis
5 min readMay 18, 2022

--

I recently wanted to use the Bluetooth API to help me exchange data between an Android Smartphone and a Raspberry Pi so I got into the details of the API and I will give you a brief review and point out things that you should watch out for.

First things first, the documentation is pretty good on its own. On the downside, it is not a popular API (not many devs have used Bluetooth in their career) so examples are too few and probably old enough to not be able to copy-paste them into your code (considering the fast-paced Android SDK evolution).

All in all, you can probably make an app that uses the Bluetooth APIs from start to finish and everything will work just fine using the documentation alone but if you stumble somewhere you are going to have a bad time.

So… some tips I consider useful regarding the whole process are the following: 🧐

You will of course start with the Permissions: It is hard to say how much time I have spent trying to get the permissions to work correctly. I wrote a separate article so I won't go into the details here.

The devices you wish to connect must first have some sort of visibility between them (that is not something made by default). So one device must be made to be discoverable and the other must initiate scanning to find it. Typically you use the BluetoothAdapter for this as shown here or use the Companion Device API to emit the runtime permissions process as well (API level targeted must be greater than 26 though).

Then, you must understand that the Bluetooth API works on a Client-Server architecture. One device acts as the server and awaits connections to it and the other acts as a client and connects on a socket. That is NOT the same thing as a pairing procedure. Being connected and being paired is something similar but different since connected devices are paired devices that have an open socket connection between them. The connection is made using a BluetoothSocket.

The server and client are considered connected to each other when they each have a connected BluetoothSocket on the same RFCOMM channel. At this point, each device can obtain input and output streams, and data transfer can begin…

Now in order to establish a connection, you must select whether you are going to use a secure channel and the UUID you will use.

Not all devices can secure data exchange via Bluetooth as stated in the documentation:

For example, for Bluetooth 2.1 devices, if any of the devices does not have an input and output capability or just has the ability to display a numeric key, a secure socket connection is not possible. In such a case, use createInsecureRfcommSocketToServiceRecord.

As for the UUID let's first examine what it is:

A UUID is a standardized 128-bit format for a string ID used to uniquely identify information. A UUID is used to identify information that needs to be unique within a system or a network because the probability of a UUID being repeated is effectively zero. It is generated independently, without the use of a centralized authority. In this case, it’s used to uniquely identify your app’s Bluetooth service.

and the UUID must be known both to the Server and the Client for the data exchange to happen:

The Universally Unique Identifier (UUID) is also included in the SDP entry and forms the basis for the connection agreement with the client device. That is, when the client attempts to connect with this device, it carries a UUID that uniquely identifies the service with which it wants to connect. These UUIDs must match in order for the connection to be accepted.

The above explanations got me certain that you need to generate your own UUID for your app and exchange it somehow with the other device (or have it hardcoded in your code). BUT the documentation of the connection service has a useful insight for single-board computers:

So although we missed this hint at our first attempts, we later hardcoded the proper UUID to our code and used it to connect to the Raspberry Pi. 🤷‍♂‍

The last thing to consider is how to transfer data after having an open connection between the devices (using the socket created). The documentation follows a simple approach. First, you initiate a connection in a Thread using the socket, and from this thread you run another thread (you can close the other one if you don't need continuous exchange of data between the devices) to do the data exchange.

The data exchange is made using the socket's input and output streams.

The official documentation gives this example for sending and receiving data from a device

But since it is not properly explained i will do it for you 🤜 🤛:

  • The open socket that was initiated when the connection is made carries two streams, one for receiving data (input stream) and one for sending data (output stream)
  • The data are read continuously (until we close the inputStream or close the socket) when we run the thread (since the run method of the thread is overwritten).
  • The write method is called only when needed and externally (via the Handler's built-in method). You can always do this differently of course. For example, I used a ViewModel for passing data to the UI.
  • The data are written/read from the socket using a buffer of a size we define. What is a bit difficult to understand here is that we specify the number of bytes that the data occupy in the buffer so that we don't read/write blank bytes. So the method mmInStream.read(mmBuffer) returns the bytes of the buffer that have been written!!

In the App I made I wanted simple text data so I used something more straightforward to pass my data to the UI:

If you can know for certain the number of bytes that you are going to use (like I did since I only received a string of "0" or "1") then you can just process the data as I did.

Important reminders:

  • The scan for nearby devices is a heavyweight process that drains a lot of resources so keep in mind to ALWAYS terminate it when not needed. The default behaviour is to scan for only 12 seconds though.
  • The active socket connection must also be terminated when not needed for the same reasons as above.

Google has a demo app that demonstrates the Bluetooth APIs here but it rather old (3 years at the time of writing) and rather complex. Also it is in Java…

Another Bluetooth sample App that i found in github and is in Kotlin is this.

If you want you can also take a look at my App here:

--

--