Advertising¶
val handle = blueLib.startAdvertising(
AdvertisingRequest(
advertiseData = AdvertiseData(
includeDeviceName = true,
serviceUuids = listOf(BluetoothUuid.HEART_RATE),
manufacturerData = mapOf(0x004C to byteArrayOf(0x02, 0x15)),
),
parameters = AdvertisingParameters(
mode = AdvertiseMode.LOW_LATENCY,
connectable = true,
useExtendedAdvertising = false,
),
),
)
…
blueLib.stopAdvertising(handle)
Legacy or extended?¶
| Legacy | Extended (Android 8+) | |
|---|---|---|
| Maximum payload | 31 bytes, including the 3 byte flags field Android prepends | BluetoothAdapter.leMaximumAdvertisingDataLength, usually 1650, often less on OEM stacks |
| Discoverable by older phones | yes | not on Android 7 and older |
| Set discoverable (Android 14+) | n/a | AdvertisingParameters(discoverable = true) |
| Scan response payload | yes | yes |
Choose legacy unless you need the room: an extended advertisement is invisible to a peripheral scanning with legacy settings, which surprises people on mixed device fleets.
BlueLib validates the payload before the platform call, because ADVERTISE_FAILED_DATA_TOO_LARGE is
not reported by every OEM stack — a too-large legacy payload is frequently truncated instead:
// Throws BlueLibValidationException.InvalidPayload with the byte counts and the fix.
blueLib.startAdvertising(request)
Parameters that matter¶
connectable = trueis required if a central should be able to connect; a non-connectable set is invisible tostartScanresults that try to connect.scannable = trueon non-connectable sets makes them appear in active scans; without it, the device is only visible to passive scanners.timeoutMillisstops advertising on its own (handled by both the legacysetTimeoutand the extendeddurationargument). Set it if nobody else will stop the set.txPowerLevelis a level (ULTRA_LOW…HIGH), not dBm: the platform picks the closest supported value, andAdvertisingHandle.txPowerDbmreports what it actually used.intervalon extended sets comes from the mode preset (LOW_POWER,BALANCED,LOW_LATENCY); the platform clamps whatever you pass to the controller's supported range.
Set lifetime and cancellation¶
startAdvertising suspends until the set starts and returns an AdvertisingHandle. Calling it from a
coroutine that gets cancelled mid-start stops the advertising set, so a cancelled flow cannot leave the
radio advertising.
// One set, stopped deterministically.
val handle = blueLib.startAdvertising(request)
try {
serve(handle)
} finally {
blueLib.stopAdvertising(handle)
}
// Or stop everything this instance started.
blueLib.startAdvertising(a)
blueLib.startAdvertising(b) // a different set, when extended advertising is supported
blueLib.stopAll() // internal call used by BlueLib.close()
Multiple concurrent sets require the MULTIPLE_ADVERTISEMENT capability; check it before opening the
second one:
Peripheral role checklist¶
Advertising alone does not make the device useful: a peripheral also needs
- a GATT server with the services the central will talk to (GATT server guide),
- a connectable, scannable advertising set,
- optionally a
BLE_ADVERTISE/connectedDeviceforeground service if it must survive while the app is not in the foreground, and BLUETOOTH_ADVERTISEon Android 12+ (blueLib.permissionsFor(BluetoothOperation.ADVERTISE)).
LE Audio and Auracast¶
Android 13 exposes hardware capability queries (isLeAudioSupported,
isLeAudioBroadcastSourceSupported, isLeAudioBroadcastAssistantSupported) which BlueLib reports through
the LE_AUDIO* features in PlatformReport. Actually controlling an Auracast broadcast
(BluetoothLeBroadcast) or acting as a broadcast assistant
(BluetoothLeBroadcastAssistant) requires privileged permissions that only system apps hold, so BlueLib
does not pretend to wrap them — see the Roadmap for the current status of those modules.