kable icon indicating copy to clipboard operation
kable copied to clipboard

Peripheral Name is sometimes null

Open dominikkv opened this issue 2 years ago • 2 comments

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
}

dominikkv avatar Apr 18 '24 10:04 dominikkv

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.

kevinskrei avatar Jan 16 '25 21:01 kevinskrei

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?

twyatt avatar Jan 21 '25 20:01 twyatt