From a7d9088cc6437951d9780fb25aa22b57250631b8 Mon Sep 17 00:00:00 2001 From: Thomas Kuehne Date: Tue, 30 Sep 2025 00:00:00 +0200 Subject: [PATCH] Bluetooth: log bond loss on API 36.1 / Android 16 QPR2 --- .../externalevents/KeyMissingReceiver.java | 56 +++++++++++++++++++ .../service/DeviceCommunicationService.java | 5 ++ .../service/btle/BleNamesResolver.java | 12 ++++ 3 files changed, 73 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/KeyMissingReceiver.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/KeyMissingReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/KeyMissingReceiver.java new file mode 100644 index 0000000000..7fe7da60a4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/KeyMissingReceiver.java @@ -0,0 +1,56 @@ +/* Copyright (C) 2025 Thomas Kuehne + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Parcelable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.service.btle.BleNamesResolver; + +/// Log why a previously bonded device couldn't provide keys to establish encryption +/// on API 36.1 and higher. On lower API levels this broadcast usually requires +/// Manifest.permission.BLUETOOTH_PRIVILEGED to receive. +public class KeyMissingReceiver extends BroadcastReceiver { + private static final Logger LOG = LoggerFactory.getLogger(KeyMissingReceiver.class); + + public static final String ACTION_KEY_MISSING = "android.bluetooth.device.action.KEY_MISSING"; + public static final String EXTRA_BOND_LOSS_REASON = "android.bluetooth.device.extra.BOND_LOSS_REASON"; + + @Override + public void onReceive(Context context, Intent intent) { + if (context == null){ + LOG.warn("context is null"); + }else if (intent == null){ + LOG.warn("intent is null"); + } else if (ACTION_KEY_MISSING.equals(intent.getAction())) { + Parcelable device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int reason = intent.getIntExtra(EXTRA_BOND_LOSS_REASON, -1); + + LOG.error("Bluetooth bond failed: KEY_MISSING {} {}", + BleNamesResolver.getBondLossReasonString(reason), + device); + } else { + LOG.warn("received unexpected broadcast action '{}'", intent.getAction()); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 18afda0c13..5d218b6363 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -78,6 +78,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.DeviceSettingsReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.GenericWeatherReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.IntentApiReceiver; +import nodomain.freeyourgadget.gadgetbridge.externalevents.KeyMissingReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.LineageOsWeatherReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.OmniJawsObserver; @@ -260,6 +261,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private MusicPlaybackReceiver mMusicPlaybackReceiver = null; private TimeChangeReceiver mTimeChangeReceiver = null; private BluetoothConnectReceiver mBlueToothConnectReceiver = null; + private KeyMissingReceiver mKeyMissingReceiver = null; private BluetoothPairingRequestReceiver mBlueToothPairingRequestReceiver = null; private AlarmClockReceiver mAlarmClockReceiver = null; private SilentModeReceiver mSilentModeReceiver = null; @@ -536,6 +538,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere ContextCompat.registerReceiver(this, deviceSettingsReceiver, deviceSettingsIntentFilter, ContextCompat.RECEIVER_EXPORTED); ContextCompat.registerReceiver(this, intentApiReceiver, intentApiReceiver.buildFilter(), ContextCompat.RECEIVER_EXPORTED); + + mKeyMissingReceiver = new KeyMissingReceiver(); + ContextCompat.registerReceiver(this, mKeyMissingReceiver, new IntentFilter(KeyMissingReceiver.ACTION_KEY_MISSING), ContextCompat.RECEIVER_EXPORTED); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java index 3cb983ecdf..a179cada00 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java @@ -375,6 +375,18 @@ public class BleNamesResolver { }; } + /// lookup description for numeric bond loss reasons (introduced in API 36.1) + public static String getBondLossReasonString(int reason){ + return switch (reason) { + case 0 -> "BOND_LOSS_REASON_UNKNOWN"; + case 1 -> "BOND_LOSS_REASON_BREDR_AUTH_FAILURE"; + case 2 -> "BOND_LOSS_REASON_BREDR_INCOMING_PAIRING"; + case 3 -> "BOND_LOSS_REASON_LE_ENCRYPT_FAILURE"; + case 4 -> "BOND_LOSS_REASON_LE_INCOMING_PAIRING"; + default -> "reason_" + reason; + }; + } + static public String getCharacteristicPropertyString(int property) { StringBuilder builder = new StringBuilder(); if (BluetoothGattCharacteristic.PROPERTY_BROADCAST == (BluetoothGattCharacteristic.PROPERTY_BROADCAST & property)) {