The Case of the Missing Bluetooth Address

Android 6.0 (Marshmallow) was released about 4 months ago and it brought some welcome changes - such as runtime permissions - as well as some not-so-welcome changes - such as the unceremonial removal of the BluetoothAdapter.getAddress() api.

The History

Prior to Android M, getting the Bluetooth address was a relatively simple affair:

BluetoothAdapter adapter = getAdapter();

if (adapter != null) {
    String address = adapter.getAddress();
} else {
    // Device does not support Bluetooth
}

Unfortunately, advertisers have been abusing this api to uniquely identify users in a way that would allow them to continue to track a user even if they uninstalled and reinstalled an app. Since the Bluetooth address comes from the device hardware, they are even able to stay on the trail of a user that factory reset their phone! Which is why BluetoothAdapter.getAddress() and WifiInfo.getMacAddress(), the two apis that gave developers access to hardware identifiers, were removed in Marshmallow.

The Problem

The abuse cases were bad and I’m glad Google is putting a stop to it, but there were legitimate uses for these apis and abruptly removing them has caused problems. Instead of throwing an exception and making the application crash, Google chose to return a dummy address - 02:00:00:00:00:00 - which is fine, if you are using the address for tracking purposes.

Apps that are using it for tracking will suddenly see a ton of activity from a “single” user, realize what has happened and filter out that data. Eventually they can update their app to track users a different way.

Legitimate uses of the Bluetooth address rely on BluetoothAdapter.getAddress() returning the actual, working Bluetooth address for the device. For instance, SocketScan uses the Bluetooth address during the scanner pairing process.

The way it works is that we display a barcode on the screen that contains the Bluetooth address of your Android device, when the scanner reads that barcode, it reconfigures itself and starts trying to connect to the Bluetooth device at the address it just received. It is a simple trick to make the scanner initiate the connection even though there is no screen on the scanner for you to select the device to which it should connect.

The problem with Google’s quick fix is that legitimate use cases of the Bluetooth address don’t just fail, they fail silently.

The Fix

Unfortunately, it appears the only way to get the Bluetooth address of a marshmallow device is to ask the user. However, there are a few things that can smooth the transition.

Cache the Bluetooth address while you still can. Marshmallow just crossed the 1% threshold of active Android devices, so there is a good chance your app still has access to the Bluetooth address. If you cache the Bluetooth address now, you can still access it after the device is upgrades to marshmallow. We are using a wrapper class that tries to retrieve the Bluetooth address from shared preferences before trying the adapter and if the adapter returns a valid address (i.e. not 02:00:00:00:00:00) it saves the address to shared preferences before returning it to the caller.

Make it easy for the user. Bluetooth addresses are not user-friendly. They are difficult to type and even more difficult to remember. Even though the user has to provide your app with their device’s Bluetooth address, it doesn’t mean they have to type it. We link the user directly to Settings > About Phone > Status where they can long-press the Bluetooth address† to copy it to their clipboard and paste it into the EditText when they return to our activity.

† The Bluetooth address is only available, if Bluetooth is ON.

Show me the code

Our BluetoothUtils is available as a gist.