feat: add BleRelayManager GATT callback (connect, UUID verify, notifications)

This commit is contained in:
eddy 2026-07-29 19:54:31 +02:00
commit a60f6cdc1c

View file

@ -11,6 +11,9 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile
import android.util.Log
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
@ -27,6 +30,7 @@ class BleRelayManager(
private val callbacks: BleCallbacks private val callbacks: BleCallbacks
) { ) {
companion object { companion object {
private const val TAG = "BleRelayManager"
private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b") private val SERVICE_UUID: UUID = UUID.fromString("4fafc201-1fb5-459e-8fcc-c5c9c331914b")
private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8") private val CHARACTERISTIC_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d8")
private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9") private val UUID_CHAR_UUID: UUID = UUID.fromString("beb5483e-36e1-4688-b7f5-ea07361f26d9")
@ -55,7 +59,11 @@ class BleRelayManager(
override fun onScanResult(callbackType: Int, result: ScanResult) { override fun onScanResult(callbackType: Int, result: ScanResult) {
val device = result.device ?: return val device = result.device ?: return
val address = device.address ?: return val address = device.address ?: return
if (triedAddresses.contains(address)) return Log.d(TAG, "Found device: ${device.name ?: ""} ($address)")
if (triedAddresses.contains(address)) {
Log.d(TAG, "Skipping already-tried device $address")
return
}
stopScan() stopScan()
connectToDevice(device) connectToDevice(device)
} }
@ -151,7 +159,89 @@ class BleRelayManager(
} }
private val gattCallback = object : android.bluetooth.BluetoothGattCallback() { private val gattCallback = object : android.bluetooth.BluetoothGattCallback() {
// implemented in next task @SuppressLint("MissingPermission")
override fun onConnectionStateChange(gatt: android.bluetooth.BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
if (hmacKey == null) {
Log.w(TAG, "GATT connected but no key -- disconnecting")
callbacks.onStatusChanged(BleStatus.NoKey)
gatt.disconnect()
return
}
Log.d(TAG, "GATT connected")
bluetoothGatt = gatt
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG, "GATT disconnected")
callbacks.onStatusChanged(BleStatus.Disconnected)
gatt.close()
bluetoothGatt = null
relayCharacteristic = null
seq = 1
handler.postDelayed(scanRunnable, RECONNECT_DELAY_MS)
}
}
@SuppressLint("MissingPermission")
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)
return
}
Log.d(TAG, "Services discovered, requesting MTU...")
gatt.requestMtu(256)
}
@SuppressLint("MissingPermission")
override fun onMtuChanged(gatt: android.bluetooth.BluetoothGatt, mtu: Int, status: Int) {
Log.d(TAG, "MTU changed to $mtu")
val service = gatt.getService(SERVICE_UUID)
if (service != null) {
relayCharacteristic = service.getCharacteristic(CHARACTERISTIC_UUID)
val uuidChar = service.getCharacteristic(UUID_CHAR_UUID)
if (relayCharacteristic != null && uuidChar != null) {
Log.d(TAG, "Reading device UUID characteristic...")
gatt.readCharacteristic(uuidChar)
} else {
Log.e(TAG, "Service or characteristics not found")
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
}
} else {
Log.e(TAG, "Service not found")
callbacks.onStatusChanged(BleStatus.ServiceNotFound)
}
}
@SuppressLint("MissingPermission")
override fun onCharacteristicRead(gatt: android.bluetooth.BluetoothGatt, characteristic: android.bluetooth.BluetoothGattCharacteristic, value: ByteArray, status: Int) {
if (characteristic.uuid == UUID_CHAR_UUID && status == BluetoothGatt.GATT_SUCCESS) {
val storedUuid = deviceUuid
if (storedUuid != null && value.contentEquals(storedUuid)) {
Log.d(TAG, "UUID verified, setting up relay")
relayCharacteristic?.let { rc ->
gatt.setCharacteristicNotification(rc, true)
val descriptor = rc.getDescriptor(CCCD_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
callbacks.onStatusChanged(BleStatus.Connected)
} else {
Log.w(TAG, "UUID mismatch -- disconnecting")
triedAddresses.add(gatt.device.address)
callbacks.onStatusChanged(BleStatus.WrongDevice)
gatt.disconnect()
}
}
}
@SuppressLint("MissingPermission")
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)
}
}
} }
private fun loadState() { private fun loadState() {
@ -161,6 +251,7 @@ class BleRelayManager(
val hexUuid = prefs.getString(PrefsHelper.PREF_UUID, null) val hexUuid = prefs.getString(PrefsHelper.PREF_UUID, null)
deviceUuid = hexUuid?.let { hexStringToBytes(it) } deviceUuid = hexUuid?.let { hexStringToBytes(it) }
seq = prefs.getInt(PrefsHelper.PREF_SEQ, 1).coerceAtMost(PrefsHelper.SEQ_MAX) seq = prefs.getInt(PrefsHelper.PREF_SEQ, 1).coerceAtMost(PrefsHelper.SEQ_MAX)
Log.d(TAG, "HMAC key loaded: ${hmacKey?.size ?: "none"} bytes, UUID: ${deviceUuid?.size ?: "none"} bytes, seq=$seq")
} }
private fun hexStringToBytes(hex: String): ByteArray? { private fun hexStringToBytes(hex: String): ByteArray? {