fix: thread safety, GATT cleanup, onResume guard, relay2 docs

This commit is contained in:
eddy 2026-07-29 20:05:26 +02:00
commit a6e5df0052
3 changed files with 69 additions and 16 deletions

View file

@ -73,23 +73,23 @@ class BleRelayManager(
fun startScan() {
if (isScanning) return
if (!hasPermissions()) {
callbacks.onStatusChanged(BleStatus.PermissionsDenied)
handler.post { callbacks.onStatusChanged(BleStatus.PermissionsDenied) }
return
}
val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter == null || !adapter.isEnabled) {
callbacks.onStatusChanged(BleStatus.BluetoothDisabled)
handler.post { callbacks.onStatusChanged(BleStatus.BluetoothDisabled) }
return
}
loadState()
if (hmacKey == null || deviceUuid == null) {
callbacks.onStatusChanged(BleStatus.NoKey)
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
return
}
isScanning = true
scanTimedOut = false
scanTimeoutRetries = 0
callbacks.onStatusChanged(BleStatus.Scanning)
handler.post { callbacks.onStatusChanged(BleStatus.Scanning) }
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
adapter.bluetoothLeScanner.startScan(listOf(filter), settings, scanCallback)
@ -105,6 +105,7 @@ class BleRelayManager(
@SuppressLint("MissingPermission")
fun stopScan() {
handler.removeCallbacks(scanRunnable)
if (!isScanning) return
isScanning = false
scanTimeoutRunnable?.let { handler.removeCallbacks(it) }
@ -116,7 +117,7 @@ class BleRelayManager(
handler.postDelayed(scanRunnable, 2000L)
return
}
callbacks.onStatusChanged(BleStatus.DeviceNotFound)
handler.post { callbacks.onStatusChanged(BleStatus.DeviceNotFound) }
}
}
@ -140,6 +141,9 @@ class BleRelayManager(
fun destroy() {
handler.removeCallbacksAndMessages(null)
stopScan()
bluetoothGatt?.disconnect()
bluetoothGatt?.close()
}
@SuppressLint("MissingPermission")
@ -173,9 +177,14 @@ class BleRelayManager(
triedAddresses.clear()
}
fun isConnected(): Boolean {
return bluetoothGatt != null
}
@SuppressLint("MissingPermission")
private fun connectToDevice(device: android.bluetooth.BluetoothDevice) {
callbacks.onStatusChanged(BleStatus.Connecting)
handler.post { callbacks.onStatusChanged(BleStatus.Connecting) }
Log.d(TAG, "Connecting to ${device.address}")
bluetoothGatt = device.connectGatt(context, false, gattCallback)
}
@ -185,7 +194,7 @@ class BleRelayManager(
if (newState == BluetoothProfile.STATE_CONNECTED) {
if (hmacKey == null) {
Log.w(TAG, "GATT connected but no key -- disconnecting")
callbacks.onStatusChanged(BleStatus.NoKey)
handler.post { callbacks.onStatusChanged(BleStatus.NoKey) }
gatt.disconnect()
return
}
@ -194,7 +203,7 @@ class BleRelayManager(
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG, "GATT disconnected")
callbacks.onStatusChanged(BleStatus.Disconnected)
handler.post { callbacks.onStatusChanged(BleStatus.Disconnected) }
gatt.close()
bluetoothGatt = null
relayCharacteristic = null
@ -207,7 +216,7 @@ class BleRelayManager(
override fun onServicesDiscovered(gatt: android.bluetooth.BluetoothGatt, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "Service discovery failed (status=$status)")
callbacks.onStatusChanged(BleStatus.ServiceError)
handler.post { callbacks.onStatusChanged(BleStatus.ServiceError) }
return
}
Log.d(TAG, "Services discovered, requesting MTU...")
@ -226,11 +235,11 @@ class BleRelayManager(
gatt.readCharacteristic(uuidChar)
} else {
Log.e(TAG, "Service or characteristics not found")
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
handler.post { callbacks.onStatusChanged(BleStatus.ServiceNotFound) }
}
} else {
Log.e(TAG, "Service not found")
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
handler.post { callbacks.onStatusChanged(BleStatus.ServiceNotFound) }
}
}
@ -246,11 +255,11 @@ class BleRelayManager(
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
callbacks.onStatusChanged(BleStatus.Connected)
handler.post { callbacks.onStatusChanged(BleStatus.Connected) }
} else {
Log.w(TAG, "UUID mismatch -- disconnecting")
triedAddresses.add(gatt.device.address)
callbacks.onStatusChanged(BleStatus.WrongDevice)
handler.post { callbacks.onStatusChanged(BleStatus.WrongDevice) }
gatt.disconnect()
}
}
@ -260,7 +269,7 @@ class BleRelayManager(
override fun onCharacteristicWrite(gatt: android.bluetooth.BluetoothGatt, characteristic: android.bluetooth.BluetoothGattCharacteristic, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "Write failed (status=$status)")
callbacks.onStatusChanged(BleStatus.CommError)
handler.post { callbacks.onStatusChanged(BleStatus.CommError) }
}
}
}

View file

@ -117,8 +117,10 @@ class MainActivity : AppCompatActivity(), BleCallbacks {
requestPermissions()
return
}
manager.resetTriedAddresses()
handler.postDelayed({ manager.startScan() }, ON_RESUME_SCAN_DELAY_MS)
if (!manager.isConnected()) {
manager.resetTriedAddresses()
handler.postDelayed({ manager.startScan() }, ON_RESUME_SCAN_DELAY_MS)
}
}
override fun onPause() {