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 | 24 | 30 | ||||||||||||||||||||||||||
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!