The Missing Manual: Android Bluetooth RFCOMM

Dave Smith
Dave Smith

Update: As of Android 4.0.3 (API Level 15), the methods BluetoothDevice.fetchUuidsWithSdp() and BluetoothDevice.getUuids() have been made part of the public SDK, so developers will no longer need to use the tactics described in this article to obtain Bluetooth UUID data, unless they need to remain backwards compatible.

In addition, now that the constants are properly part of the public SDK, the spelling of "bluetooth" in the Intent strings has been corrected.

Bluetooth communication has been a part of the Android SDK since the 2.0 release late last year. The APIs available in the android.bluetooth package are primarily focused around three application functions:

  1. Discovering other devices
  2. Connecting to devices
  3. Transferring data using the RFCOMM layer

With this capability, developers can write applications that transfer data between devices using peer-to-peer connections, or even communicate with other Bluetooth enabled electronics. The SDK provides a very good sample project geared towards illustrating these basic functions in the context of a chat program. But with all the assistance the SDK documentation provides, there are a few important pieces that are missing from the big picture of working in the RFCOMM connection stack.

A Little Bluetooth Background

Bluetooth is a very flexible wireless standard enabling devices in close proximity to discover, connect, and transfer information across miniature peer-to-peer networks. As part of the discovery and connection process, the Bluetooth stack relies on a protocol called Service Discovery (SDP) to gather information about the devices it is discovering to determine whether they have the right capabilities to warrant connecting with. As part of SDP, devices must publish a UUID for each service they have available.

A typical discover/connect operation would have the following steps:

  1. Scan for discoverable devices in range
  2. Use SDP on a specific device to find the UUID of the service you want to connect with
  3. Connect to the specific device referencing your preferred service

RFCOMM is a layer of the Bluetooth Protocol Stack that provides serial data transfer services (via "sockets"). It is the base of many of the common profiles such as Dialup Networking (DUN) and Serial Port Profile (SPP). In essence, though, it is a way for developers to create a full duplex serial data stream between two points.

And Now...Back to Android

As previously mentioned, in order to establish a connection to a remote device using Bluetooth, you must be able to discover and reference a specific service record on that device. This gives rise to two possible paths by which this can be done:

  1. Know the UUID of the remote service ahead of time
  2. Read the SDP responses to determine the UUID of what you need

In the sample program provided in the SDK, the former option is used. Because the application is a point to point link between two Android devices, and the sample works for both sides of the conversation, the UUID is hard-coded into the application. This method does work, and in many cases is the proper way to handle things. But what if the device your connecting to isn’t under your control, like an embedded sensor or media device? Isn’t this why SDP exists in the first place?

It turns out, there are some holes in the current SDK API and documentation that make discovering unknown services a little difficult, but there are solutions.

Discovery via a Hidden Function

The Android SDK does include two methods in the BluetoothDevice class:

  • public boolean fetchUuidsWithSdp()
  • public ParcelUuid[] getUuids()

Both of these methods are designed to poke the underlying service and get references to the service records — and in the current SDK (2.2 as of this writing) they are both hidden. Neither of these methods can be called directly, even though they are in the code. They have to be called using reflection, and there is no guarantee that they will stay the same in future version of the SDK since they are not public.

BluetoothDevice.getUuids() is a synchronous method, and will return an array of the UUID records to the caller directly. The values returned by this method are the values stored in the local cache for that device, no attempt is made to query the remote device at calling time. This also means that this method will often return null you try to get this information before you’ve ever attempted to pair/connect with that device.

BluetoothDevice.fetchUuidsWithSdp() is an asynchronous method, and you will have to register for the Broadcast Intent that the system fires for the available UUID that is found. This method makes a direct query on the device itself for a current set of service UUIDs:

String action = "android.bleutooth.device.action.UUID";
IntentFilter filter = new IntentFilter(action);
registerReceiver(mReceiver, filter);

Notice the misspelling of the word “bleutooth”, which is not a typo; that is the true Intent action string. These Intents will include two extras that you can use to get the data you need:

  • android.bluetooth.device.extra.DEVICE
    • Contains the BluetoothDevice that is being queried.
  • android.bluetooth.device.extra.UUID
    • Contains an array of ParcelUuid objects for the services.
    • Actual UUID instance can be obtained with via ParcelUuid.getUuid()
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        BluetoothDevice deviceExtra =
            intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
        Parcelable[] uuidExtra =
            intent.getParcelableArrayExtra("android.bluetooth.device.extra.UUID");

        //Parse the UUIDs and get the one you are interested in
    }
};

UUID via Failed Connect

The only currently exposed in the SDK that also calls fetchUuidsWithSdp() under the hood is BluetoothSocket.connect(). By creating and attempting to connect to a BluetoothSocket, you will get the same UUID Broadcast Intent, at which point you could read it, find the services you need, and then connect again. This is a very counterintuitive method since the socket requires a service’s UUID to be created!

Closing Remarks

Hopefully, Android will clear up their documentation and expose the proper functions in future releases. But for now, thanks to the visibility the AOSP gives us into the source code, we have a few workarounds to accomplish the goal of using Android to add rich connectivity into applications!