The Missing Manual: Android Bluetooth RFCOMM

ANDROID 4.0 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 fo 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.  Niether 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.


1
2
3
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()

1
2
3
4
5
6
7
8
9
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!

Tags: , ,

3 Responses to “The Missing Manual: Android Bluetooth RFCOMM”

  1. Peter says:

    Hi

    I tried using your method to get the UUID of a bluetooth enabled weightscale but no luck. It all returns null. In fact the filter “android.bleutooth.device.action.UUID” is never called. I added another filter BluetoothDevice.ACTION_ACL_CONNECTED to be sure i get a connection and that works. But still the UUID is null. I am testing this on a HTC Desire Android 2.2.

    I agree that is it very hard to deal with incoming bluetooth connections in Android. It was so much easier in Windows Mobile 6.5!

  2. Dave says:

    Hi Peter -

    My first mention will be that throughout this process, Logcat is your friend. The OS will log out when it fires the UUID broadcast, so you can see if it is really firing or if the application just isn’t catching it.

    I have noticed when trying to force the intent callback by calling BluetoothSocket.connect() with a fake UUID, the timing is a bit unruly on certain devices. On the HTC Evo, for instance, it can sometimes take up to one minute after the OS logged the broadcast redirect before the BroadcastReceiver in the app picks it up.

    I’m going to assume though, based on your comment about null returns, that you are trying to access the hidden methods using reflection. Here is some sample code to do this that I have tested on a handful of 2.2 devices, so perhaps the answer to your issues lies within there. The first is using fetchUuidsWithSdp, which WILL force the system to fire the misspelled UUID broadcast intent that you can catch using the receiver code in the post (you may also try defining a BroadcastReceiver in your manifest, instead of inside an Activity class like I did):

    1
    2
    3
    4
    5
    Class cl = Class.forName("android.bluetooth.BluetoothDevice");
    Class[] par = {};
    Method method = cl.getMethod("fetchUuidsWithSdp", par);
    Object[] args = {};
    method.invoke(device, args);

    Notice we don’t pay attention to the return value from the invoked method, because the interesting information will come back when the intent is fired. The next example is using getUuids, which WILL NOT fire said system intent. The UUID data is returned as an array from this method call:

    1
    2
    3
    4
    5
    Class cl = Class.forName("android.bluetooth.BluetoothDevice");
    Class[] par = {};
    Method method = cl.getMethod("getUuids", par);
    Object[] args = {};
    ParcelUuid[] retval = (ParcelUuid[])method.invoke(device, args);

    Hope some of that is useful!

  3. Ian says:

    Dave,

    Thanks for the great information here, I appreciate you sharing it.

    I’ve been trying to find a way to initiate the low level ACL connection with a device that is “paired, but not connected” and within range. I was hopeful that if I could identify the UUID and connect to it, that it would re-attempt the ACL, but alas it didn’t.

    Thanks again!