Bluetooth provides users with a quick and easy way of exchanging data between a wide range of different devices, but there’s a couple of reasons why Bluetooth is particularly popular among mobile users:

  • It’s wireless – because no-one wants to carry cables around with them on the off-chance that they might need to exchange data with another device at some point during the day.
  • It’s not dependent on other networks. You don’t have to hunt down an open Wi-Fi network every time you want to use Bluetooth.
  • Bluetooth doesn’t use your mobile network, so don’t worry about burning through your monthly data allowance.

In this article I’m going to show you how to give your Android apps the ability to discover and connect with other Bluetooth-enabled devices. What your app does once it makes this connection will vary from app-to-app, but I’ll also be outlining the steps you’ll typically take to send data from one device to another – you can then tweak this formula to suit what you specifically want to achieve with your app’s Bluetooth connection.

Note that this article uses Classic Bluetooth, which will be suitable for the majority of use cases. However, if you’re designing an application that targets devices with stricter power requirements, such as Google Beacons, heart rate monitors or fitness devices, then you may want to look into Bluetooth Low Energy (BLE) instead.

Why should I care about Bluetooth?

Adding Bluetooth functionality to your app can improve the user experience in a number of ways.

The most obvious is giving your users an easy way of sharing your app’s content, for example if you’ve developed a calendar app then your users might appreciate being able to share their schedules with friends, family and colleagues.

Sometimes users may already have a way of sharing your app’s content, for example by using their device’s stock apps, but this doesn’t automatically mean they won’t appreciate being able to access the same functionality from inside your app. Imagine you’ve created a camera app – users can already share photos via the stock Gallery or Photos apps, but having to launch a separate app every time they want to share a photo is going to get really frustrating, really fast. In this scenario, integrating Bluetooth functionality into your app has the potential to greatly improve the user experience.

Alternatively, you could set your sights on developing an app that’ll improve the user’s Bluetooth experience as a whole (if you need some inspiration, then take a look at some of the Bluetooth applications already available on Google Play).

While exchanging content may be the first thing that springs to mind when you think about Bluetooth, you can use Bluetooth for much more than simply moving files between devices. For example, you could design an app that uses Bluetooth to control other devices, such as an automation app that can execute tasks on the various Bluetooth-enabled devices around the user’s home or office. This area is particularly exciting since we’re seeing a greater variety of Bluetooth-enabled devices than ever before, which means more opportunities to design new and unique experiences for your users.

Basically, there’s lots of ways that you can use Bluetooth to enhance your applications – and Bluetooth functionality doesn’t always have to be limited to sending files from one device to another.

Bluetooth Permissions

If your app is going to do anything Bluetooth-related, then it’ll need to request the BLUETOOTH permission, which allows your app to perform essential tasks such as enabling Bluetooth on the user’s device, connecting to other devices, and transferring data.

Your app may also need to request the BLUETOOTH_ADMIN permission. Specifically, you’ll need to request this permission before your app can perform any of the following tasks:

  • Initiating device discovery. We’ll be looking at issuing discovery requests later in this article, but essentially this is where a device scans the local area for other Bluetooth-enabled devices to connect to.
  • Performing device pairing.
  • Changing the device’s Bluetooth settings.

You declare one, or both of these permissions by adding them to your app’s Manifest:

<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
...
</manifest>

Does the device even support Bluetooth?

Another important step, is verifying that the current device actually supports Bluetooth. While the majority of Android devices do feature Bluetooth hardware and software, the Android platform runs on such a wide range of devices that you should never assume that your app will have access to certain functionality – even when it’s something as common as Bluetooth.

To check whether a device supports Bluetooth, your app should attempt to acquire the device’s BluetoothAdapter, using the BluetoothAdapter class and the static getDefaultAdapter method.

If getDefaultAdapter returns null then the device doesn’t support Bluetooth, and you should notify the user that they’ll be unable to use your app’s Bluetooth features as a result.

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {

//Display a toast notifying the user that their device doesn’t support Bluetooth//

Toast.makeText(getApplicationContext(),"This device doesn’t support Bluetooth",Toast.LENGTH_SHORT).show();
}
else {

//If BluetoothAdapter doesn’t return null, then the device does support Bluetooth//

...
...

If Bluetooth isn’t available on the current device, then in the interests of providing a good user experience you should disable all of your app’s features that rely on Bluetooth. The last thing you want is the user trying to access these features, discover they’re not working and subsequently leave  a negative review claiming that your application is broken.

Enabling Bluetooth

Once you’ve verified that the device does actually support Bluetooth, you’ll need to check whether Bluetooth is enabled, by calling the isEnabled method.

This method will either return true (if it’s enabled) or false (if it’s disabled). If isEnabled returns false, then you’ll need to issue a dialogue requesting that the user switches their device’s Bluetooth on.

The system will then call your Activity’s onActivityResult method and pass it the user’s response. The onActivityResult method takes the following parameters:

  • The request code you passed to startActivityForResult. This can be anything you want; in the following example I’m going to use ENABLE_BT_REQUEST_CODE.
  • The resultCode. If Bluetooth has been successfully enabled, then the resultCode will be RESULT_OK. If Bluetooth wasn’t enabled (either due to an error or because the user chose not to enable it) then the resultCode will be RESULT_CANCELED.
  • An Intent that carries the result data.

In the following code, we’re checking whether Bluetooth is enabled and then issuing a dialogue if it isn’t:

if (!bluetoothAdapter.isEnabled()) {

    //Create an intent with the ACTION_REQUEST_ENABLE action, which we’ll use to display our system Activity//
    intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

    //Pass this intent to startActivityForResult(). ENABLE_BT_REQUEST_CODE is a locally defined integer that must be greater than 0,
    //for example private static final int ENABLE_BT_REQUEST_CODE = 1//
    startActivityForResult(enableIntent, ENABLE_BT_REQUEST_CODE);
    Toast.makeText(getApplicationContext(), "Enabling Bluetooth!", Toast.LENGTH_LONG).show();
}

Now let’s take a look at our onActivityResult() implementation:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

    //Check what request we’re responding to//
    if (requestCode == ENABLE_BT_REQUEST_CODE) {

        //If the request was successful…//
        if (resultCode == Activity.RESULT_OK) {

            //...then display the following toast.//
            Toast.makeText(getApplicationContext(), "Bluetooth has been enabled”,
            Toast.LENGTH_SHORT).show();
        }

        //If the request was unsuccessful...//
        if(resultCode == RESULT_CANCELED){

            //...then display this alternative toast.//
            Toast.makeText(getApplicationContext(), "An error occurred while attempting to enable Bluetooth",
            Toast.LENGTH_SHORT).show();
        }
    }
}

Finding devices to connect to

If your app is going to exchange data over Bluetooth, then it needs to find remote devices to exchange data with. This means either:

  • Querying the list of paired devices. If the local device has a list of known devices, then your app can retrieve this information and display it to the user. The user can then decide which device (if any) they want to connect to.
  • Scanning the area for nearby Bluetooth-enabled devices, by initiating device discovery. If another device is in the local area and that device is currently in a discoverable state, then this device will respond to your discovery request.
  • Making the local device discoverable. When the local device is discoverable, any device that’s scanning the area will be able to “see” your device, and potentially connect to it.

In the following section, we’re going to look at how each of these methods works in more detail, and how you can implement them in your app.

Retrieving the list of paired devices

It’s possible that the user may want to connect to a device that they’ve already discovered, so you should always check the list of devices that the user has previously connected to, before looking for new devices.

You retrieve this list by calling the getBondedDevices method, which will return a set of BluetoothDevice objects representing devices that are paired to the local adapter. You can then capture each device’s unique public identifier (using getName) and its MAC address (using getAddress) and present this information to the user.

In the following snippet I’m checking for a list of paired devices and then retrieving information about each device in this list. Since you’ll eventually want to display this information to the user, I’m also laying the groundwork for adding these details to a ListView, so the user will be able to select the device they want to connect to.

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();

// If there’s 1 or more paired devices...//
if (pairedDevices.size() > 0) {

    //...then loop through these devices//
    for (BluetoothDevice device : pairedDevices) {
        //Retrieve each device’s public identifier and MAC address. Add each device’s name and address to an ArrayAdapter, ready to incorporate into a
        //ListView
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}

Discovering new devices

If you’ve queried the list of paired devices and either a) didn’t find any devices or b) the user chose not to connect to any of these known devices, then you’ll need to look for new devices to connect to.

At this point you have two options: either make the local device discoverable and wait for an incoming discovery request, or take the initiative and issue a discovery request yourself.

Entering discoverable mode

If you want the local device to accept incoming connection requests, then you’ll need to issue a dialogue requesting that the user makes their device discoverable. You do this, by calling startActivityForResult(Intent, int) with the ACTION_REQUEST_DISCOVERABLE intent.

Once the user responds to this dialogue, the system will call the onActivityResult method and pass the requestCode and the resultCode. This resultCode will either be:

  • RESULT_OK. The device is now discoverable. This field also contains information about how long the device will be discoverable for.
  • RESULT_CANCELED. The user decided not to make their device discoverable, or an error occurred.

Let’s take a look at an example:

public static final int REQUEST_DISCOVERABLE_CODE = 2;
 …
 …
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

//Specify how long the device will be discoverable for, in seconds.//
discoveryIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 400);
startActivity(discoveryIntent);
}

By default a device will remain discoverable for 120 seconds, but you can request a different duration using the EXTRA_DISCOVERABLE_DURATION field and an integer value, as I’ve done in the above code. If you do include the EXTRA_DISCOVERABLE_DURATION field, then the maximum value you can use is 3600 – try to use anything higher, and EXTRA_DISCOVERABLE_DURATION will default to 120.

You should also never set EXTRA_DISCOVERABLE_DURATION to 0 as this will make the device permanently discoverable, which is a great way of draining the user’s battery and potentially compromising their privacy to boot.

Issuing a discovery request

Alternatively, your app can tell the local device to go looking for new devices to connect to, by issuing a discovery request.

Your app can start the discovery process, by calling the startDiscovery method. Since the discovery process is asynchronous, it’ll immediately return a boolean value that you can use to inform the user whether discovery was started successfully.

if (bluetoothAdapter.startDiscovery()) {

    //If discovery has started, then display the following toast....//
    Toast.makeText(getApplicationContext(), "Discovering other bluetooth devices...",
    Toast.LENGTH_SHORT).show();
} else {

    //If discovery hasn’t started, then display this alternative toast//
    Toast.makeText(getApplicationContext(), "Something went wrong! Discovery has failed to start.",
    Toast.LENGTH_SHORT).show();
}

To ensure your app gets notified whenever a new device is discovered, you’ll need to register a BroadcastReceiver for the ACTION_FOUND intent.

//Register for the ACTION_FOUND broadcast//
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(broadcastReceiver, filter);

//Create a BroadcastReceiver for ACTION_FOUND//
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();

//Whenever a remote Bluetooth device is found...//
if (BluetoothDevice.ACTION_FOUND.equals(action)) {

    //….retrieve the BluetoothDevice object and its EXTRA_DEVICE field, which contains information about the device’s characteristics and capabilities//
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

    //You’ll usually want to display information about any devices you discover, so here I’m adding each device’s name and address to an ArrayAdapter,
    //which I’d eventually incorporate into a ListView//
    adapter.add(bluetoothDevice.getName() + "\n" + bluetoothDevice.getAddress());
}
  }
};

The onDestroy looks like this:

@Override
protected void onDestroy() {
   super.onDestroy();
   ...
   ...

   //Cut down on unnecessary system overhead, by unregistering the ACTION_FOUND receiver//
   this.unregisterReceiver(broadcastReceiver);
}

Discovery consumes a lot of the Bluetooth adapter’s resources, so you should never attempt to connect to a remote device while discovery is in progress – always call cancelDiscovery before attempting to connect to a remote device.

Device discovery also significantly reduces the bandwidth available for any existing connections, so you should also never launch discovery while the local device is still connected to another device, as this existing connection will experience reduced bandwidth and high latency as a result.

Making the connection

Once the user has found the device they want to connect to, it’s finally time to create a Bluetooth connection.

Bluetooth follows the client-server model, where one device acts as the server and the other acts as the client. How your app connects to a remote device will vary depending on whether the local device is acting as the server or the client:

  • The server. The device uses a BluetoothServerSocket to open a listening server socket and wait for incoming connection requests. Once the server accepts a connection request, it’ll receive the client’s BluetoothSocket information.
  • The client. This device uses the BluetoothSocket to initiate an outgoing connection. Once the server accepts the client’s connection request, the client will provide the BluetoothSocket information.

Once the server and the client have a connected BluetoothSocket on the same RFCOMM channel, your app is ready to start communicating with the remote device.

Note that if these two devices haven’t been paired previously, then the Android framework will automatically display a pairing request as part of the connection procedure, so this is one thing you don’t have to worry about!

In this section we’re going to look at how to establish a connection from both sides of the equation: when the local device is functioning as the client, and when the local device is functioning as the server.

Client

To initiate a connection with a remote “server” device, you need to obtain a BluetoothDevice object and then use it to acquire a BluetoothSocket. You do this, by calling createRfcommSocketToServiceRecord(UUID), for example:

BluetoothSocket socket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid);

The UUID (Universally Unique Identifier) parameter is a standardized 128-bit format string ID that uniquely identifies your app’s Bluetooth service. Whenever a client attempts to connect to a server, it’ll carry a UUID that identifies the service it’s looking for. The server will only accept a connection request if the client’s UUID matches the one registered with the listening server socket.

You can generate a UUID string using an online UUID generator, and then convert that string to a UUID like this:

private final static UUID uuid = UUID.fromString("your-unique-UUID");

When you call the createRfcommSocketToServiceRecord(UUID) method, the UUID passed here must match the UUID that the server device used to open its BluetoothServerSocket.

After you’ve completed these steps, your app can initiate an outgoing connection request by calling the connect() method. The system will then perform a Service Discovery Protocol (SDP) lookup on the remote device and search for a service that has a matching UUID. If it finds this service then a connection will be established over a shared RFCOMM channel. Note that the connect() method will block the current thread until a connection is either accepted or an exception occurs, so you should never run connect() from the main UI thread.

If the connection fails, or the connect() method times out, then the method will throw a {java.io.IOException}.

RFCOMM can only support one connected client per channel at a time, so once you’re done with your BluetoothSocket you’ll typically want to call close(). This will close the socket and release all its resources, but crucially it won’t close the Bluetooth connection that you’ve just made to the remote device.

Server

In this scenario, both devices have a server socket open and are listening for incoming connections. Either device can initiate a connection, and the other device will automatically become the client.

To setup the local device as a server, your app needs to acquire a BluetoothServerSocket, by calling listenUsingRfcommWithServiceRecord. For example:

bluetoothServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(myName, myUUID);

The listenUsingRfcommWithServiceRecord method takes two parameters. We’ve already looked at the UUID, and the string parameter is just the name of your service. This name is arbitrary, so you may want to use the name of your application. The system will automatically write this string to a new SDP database entry on the local device.

At this point, the server device will be able to start listening for incoming connection requests by calling the accept() method. Note that accept will block any other interaction until either a connection has been accepted or an exception has occurred, so you shouldn’t execute accept() on the main UI thread.

Once the server has accepted an incoming connection request, accept() will return a connected BluetoothSocket.
Again, RFCOMM will only allow one connected client per channel, so you should make sure you’re not unnecessarily hogging system resources by calling close() on the BluetoothServerSocket once you’ve acquired a BluetoothSocket.

Transferring data

Once the server device and client device each have a connected BluetoothSocket, your app is ready to start communicating with the remote device.

The specifics will vary depending on how you want your app to use its newly-forged Bluetooth connection, but as a rough guideline, you transfer data between two remote devices by completing the following steps:

  1. Call getInputStream and getOutputStream on the BluetoothSocket.
  2. Use the read() method to start listening for incoming data.
  3. Send data to a remote device by calling the thread’s write() method and passing it the bytes you want to send.

Note that both the read() and write() methods are blocking calls, so you should always run them from a separate thread.

Wrapping Up

Armed with this information, you should be ready to create applications that can access the device’s Bluetooth hardware and software, and use it to discover and connect with other Bluetooth-enabled devices in the local area.

Let us know in the comments how you plan on using Android’s Bluetooth support in your own apps!

Leave a comment