Peripheral Name is sometimes null
Hi,
on an Android device, I scan for BT devices with a service & name prefix filter. When an advertisement is received, I create a peripheral. In very rare occasions, the name of the peripheral is null, despite the name of the advertisement I used to create the peripheral was not null!
I am always testing with the same device, same identifier. When the name is null, the peripheral is still "usable", meaning I can connect and use it as usual.
private val scanner = Scanner {
filters = listOf(
Filter.Service(SERVICE_DEVICE_INFORMATION),
Filter.NamePrefix("MYPREFIX-"),
)
}
private val scannerFlow = scanner.advertisements
// ===========
scannerFlow.catch { e ->
log.e(e, "BTLog") { "Error while scanning" }
}.onCompletion {
log.d("BTLog") { "BT scan deactivated." }
}.collect { advertisement ->
log.d("BTLog") { "Advertisement: ${advertisement.name} - ${advertisement.identifier}" } // name not null
val peripheral = scope.peripheral(advertisement) { }
log.d("BTLog") { "Peripheral: ${peripheral.name} - ${peripheral.identifier}" } // name sometimes null
}
It appears to me that the above is correct on Android. Pixel 9 Pro - Android 15. This happens frequently when reconnecting a device that has previously disconnected.
Sadly, this seems to be a limitation of Android. Looks like the name may be cleared under some conditions, including: if BLE is turned off, or if BLE cache has recently been cleared.
The Android implementation (that Kable calls into) looks like:
/**
* Get the friendly Bluetooth name of the remote device.
*
* <p>The local adapter will automatically retrieve remote names when
* performing a device scan, and will cache them. This method just returns
* the name for this device from the cache.
*
* @return the Bluetooth name, or null if there was a problem.
*/
public String getName() {
if (DBG) log("getName()");
final IBluetooth service = getService();
final String defaultValue = null;
if (service == null || !isBluetoothEnabled()) {
Log.e(TAG, "BT not enabled. Cannot get Remote Device name");
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else {
try {
final SynchronousResultReceiver<String> recv = SynchronousResultReceiver.get();
service.getRemoteName(this, mAttributionSource, recv);
String name = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
if (name != null) {
// remove whitespace characters from the name
return name
.replace('\t', ' ')
.replace('\n', ' ')
.replace('\r', ' ');
}
} catch (RemoteException | TimeoutException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
}
}
return defaultValue;
}
Perhaps it would make sense for Kable to do its own caching of the name and be more conservative about clearing its value?