Dylan Hart

Scanning BLE on Android

BLE Packets and Beacons (27 Sep 2016)

Getting Started with Scanning

Scanning for devices requires the BLUETOOTH_ADMIN and ACCESS_COURSE_LOCATION permissions. Once you have these permissions it’s pretty easy to start scanning:

// get ble scanner
BluetoothAdapter btAdapter = BluetoothAdapter.getAdapter();
BluetoothLeScanner bleScanner = btAdapter.getBluetoothLeScanner();

// start the scan
ScanCallback scanCallback = /* todo */;
bleScanner.startScan(scanCallback);

The ScanCallback receives incoming advertising packets in the onScanResult method.

Understanding the Advertising Packet Structure

The broadcast data section of the packet is 31 bytes long and consists of several data sections.

Broadcast Data
Flag Data Data #1 Data #2 ...
0 8 16 2430

Each data segment is layed out according to the following format:

Data Segment
Length (1 byte) Type (1 byte) Content
0 1 2 ... length

Note: length does not include the one byte it uses, only the bytes after

The type of the data segment determines it’s content. A table of types can be found on the bluetooth website. I will describe type 0xFF - Manufacturer Data in the following sections. Some of the types like Device Local Name are already parsed by the Android API.

Note: 16 bit numbers tend to be represented in little endian

There are two separate advertising packets that are sent: the normal broadcast packet that is sent all the time and the scan response packet that is sent when the device is scanned. Each packet has 31 bytes to advertise data.

Implementing ScanCallback

The ScanRecord contains the data described above. The Android API automatically concatentates the scan result packets data to the end of the first packet.

class BleCallback extends ScanCallback {
    // called when a beacon is scanned
    public void onScanResult(int callbackType, ScanResult result) {
        // the broadcast data from both packets
        ScanRecord record = result.getScanRecord();

        // the ScanRecord class provides some methods to
        // access common data.

        // for example: (0x004c is Apple's designated identifier)
        byte[] appleData = record.getManufacturerSpecificData(0x004c);
        if (appleData != null) {
            // parse the data
        }

    }
}

Example: Parsing iBeacons

Once we have the data we can it! The following will describe parsing the iBeacon advertising data.

iBeacon Data Segment
Length Type Company Data Type UUID Major Minor Tx Power
26 0xFF 0x4c00 0x0215 {uuid bytes} {major number} {minor number} {tx power}
0 1 2 4 6 22 24 26

Source: kontakt.io

Once this format is understood extracting the data is easy!

// note: getManufacturerData() returns a byte array starting
//       at index 4 in the previous table.

// extract UUID
ByteBuffer bb = ByteBuffer.wrap(appleData, 2, 16);
UUID uuid = new UUID(bb.getLong(), bb.getLong());

// extracting the other data is also easy and left as
// an exercise to the reader.

// note: major and minor are in big endian

Conclusion

Scanning for BLE devices and parsing advertising data on Android is quite simple. I wish you luck on developing your applications!

© Dylan Hart, 2017