18 February, 2014

Why Android BLE method startLeScan failed to filter by custom 128bit uuid?

Let's check the startLeScan method declaration.

public boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)

Starts a scan for Bluetooth LE devices, looking for devices that advertise given services.
Devices which advertise all specified services are reported using the onLeScan(BluetoothDevice, int, byte[]) callback.
Requires BLUETOOTH_ADMIN permission.
Parameters
serviceUuidsArray of services to look for
callbackthe callback LE scan results are delivered
Returns
  • true, if the scan was started successfully
So we can filter ble device by their advertising service UUIDs?

for example, maybe I only care about the Heart Rate Device, so I want to find the device by uuid 0x180d.
Or I want to use custom 128-bit uuid to expose some data to other ble clients.

But the truth, you cannot.

Here we see the code inside the android code:
void onScanResult(String address, int rssi, byte[] adv_data)
The main part:
List <UUID>remoteUuids = parseUuids(adv_data);
 for (ScanClient client: mScanQueue) {
     if (client.uuids.length > 0) {
         int matches = 0;
         for (UUID search: client.uuids) {
             for (UUID remote: remoteUuids) {
                 if (remote.equals(search)) {
                     ++matches;
                     break; // Only count 1st match in case of duplicates
                 }
             }
         }

         if (matches < client.uuids.length) continue;
     }
Here we can see the android system will compare the UUIDs from adv data with the filtered UUIDs. And from code, we can see the comparation logic is AND not OR

What about the method parseUuids:
List  <UUID> parseUuids(byte[] adv_data) {
    List < UUID > uuids = new ArrayList < UUID > ();

    int offset = 0;
    while (offset < (adv_data.length - 2)) {
        int len = adv_data[offset++];
        if (len == 0) break;

        int type = adv_data[offset++];
        switch (type) {
        case 0x02: // Partial list of 16-bit UUIDs
        case 0x03: // Complete list of 16-bit UUIDs
            while (len > 1) {
                int uuid16 = adv_data[offset++];// bug here 
                uuid16 += (adv_data[offset++] << 8);
                len -= 2;
                uuids.add(UUID.fromString(String.format(
                    "%08x-0000-1000-8000-00805f9b34fb", uuid16)));
            }
            break;

        default:
            offset += (len - 1); // bug here 
            break;
        }
    }

    return uuids;
}  
Now, from the code, we know the parseUuids method only care about 16bit UUIDs, whatever complete or incomplete. Actually the method above, there are some bugs: In java, when converting byte to int, please convert it as below:
byte & 0xFF

So can we filter 16-bit UUIDs now?
I tested it, failed with Samsung I9300 android 4.3. I will figure it out. Worked with Nexus 5 android 4.4.2

Can we use 16bit UUIDs? 
As I know, they are reserved by Bluetooth SIG.

How to fix this issue:
1, waiting for android to fix
2, LeScan without any filter, but in the callback, do the filter: try to parse 06 and 07 service type, by bit opertaion and use method UUID(long mostSigBits, long leastSigBits)
Actually adv data can contain atmost one 128bit uuid.