[Bug] NullPointerException in BondStateMachine.isSkipConfirmationAccessory crashes Bluetooth when pairing a device with no name (LOS21 / iodéOS 5.25)

Environment

  • ROM: iodeOS 5.25 (LineageOS 21 base), Android 14
    • Device: Samsung Galaxy S20, iodeOS GSI (lineage_arm64_ab / tdgsi_arm64_ab)
      • Build flavor: lineage_arm64_ab-userdebug
        • Security patch: 2026-06-01
      • Summary
      • Pairing a Bluetooth device that does not advertise a name at bond time (e.g. Garmin fenix) crashes the com.android.bluetooth process. The bond never completes and I have to re-pair the device several times a day.
    • Root cause
    • BondStateMachine.isSkipConfirmationAccessory() calls .equals() on the remote device name without a null check. When getName() returns null, this throws an NPE and the Bluetooth stack dies. This method is LineageOS-specific (introduced in change 382143, “BondStateMachine: Allow skipping confirm for some remotes”), so it is not an AOSP bug.
  • Stack trace (from device, Android 14 / LOS21)
  • `java.lang.NullPointerException: ‘boolean java.lang.Object.equals(java.lang.Object)’ on a null object reference`
    • at BondStateMachine.isSkipConfirmationAccessory(BondStateMachine.java:471)
      • at BondStateMachine.sendDisplayPinIntent(BondStateMachine.java:483)
        • at BondStateMachine$PendingCommandState.processMessage(BondStateMachine.java:344)
      • Steps to reproduce
        1. Use a BT device that initially exposes no name during bonding (Garmin fenix).
          1. Initiate pairing.
            1. com.android.bluetooth crashes and the bond fails.
          2. Workaround (confirms the root cause)
          3. Scanning for the device in the Bluetooth settings first (so its name gets cached and getName() is no longer null) avoids the crash.
        2. Suggested fix
        3. Add a null guard before the name comparison in isSkipConfirmationAccessory() (e.g. if (name != null && name.equals(…))). This is already fixed in the lineage-22.x branch - please cherry-pick / backport the existing fix to lineage-21.0, since LOS21 devices are still widely used.

I compared the buggy lineage-21.0 code with the fixed lineage-22.1 and prepared a ready-to-apply backport patch, in case it helps the team push it upstream via Gerrit.

File: android/app/src/com/android/bluetooth/btservice/BondStateMachine.java

diff
--- a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -469,7 +469,11 @@
     private boolean isSkipConfirmationAccessory(BluetoothDevice device) {
         for (Pair<String, Integer> entry : accConfirmSkip) {
-            if (device.getName().equals(entry.first)
+            String name = device.getName();
+            if (name == null) {
+                return false;
+            }
+            if (name.equals(entry.first)
                     && device.getBluetoothClass().getDeviceClass() == entry.second) {
                 return true;
             }
         }
```

Suggested commit message:

```
BondStateMachine: Guard against null device name in isSkipConfirmationAccessory

device.getName() can return null (e.g. when a peer initiates bonding before its name is resolved, such as a Garmin fenix). Calling .equals() on it throws a NullPointerException that crashes com.android.bluetooth and prevents the bond from completing.

Backport of the null guard already present on lineage-22.x.
```

This is a direct backport of the guard that already exists on lineage-22.1 (there the same method wraps the name in a `if (name == null) return false;` check before calling .equals()).

I think you will have much better luck with a custom build rather than the GSI. User @ronnz98 makes iodéOS 7.x / Android 16 builds for this device, currently I think their most recent builds are 7.5 which is a few months back:

Thanks! That really helps!