fix(ble): migrate AndroidBleTransport off deprecated BLE APIs

Replaces deprecated BluetoothGattCharacteristic.value, writeCharacteristic,
notifyCharacteristicChanged, writeDescriptor and connectGatt overloads with
their Tiramisu (API 33+) replacements, falling back to the legacy API with
@Suppress("DEPRECATION") when running on older devices. Adds the new
onCharacteristicChanged(gatt, characteristic, value) overload and caches the
last notified payload so GATT read requests no longer read from the
deprecated characteristic.value field.
This commit is contained in:
Claude
2026-04-15 02:16:15 +00:00
parent b10a7a5153
commit ccba463686
@@ -32,6 +32,7 @@ import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
@@ -40,6 +41,7 @@ import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.os.Build
import android.os.ParcelUuid
import com.vitorpamplona.quartz.nipBEBle.AndroidBleTransportContract
import com.vitorpamplona.quartz.nipBEBle.BleConfig
@@ -89,6 +91,11 @@ class AndroidBleTransport(
private var readCharacteristic: BluetoothGattCharacteristic? = null
// Caches the most recently notified payload so that GATT read requests on the
// read characteristic can return a meaningful value without relying on the
// deprecated BluetoothGattCharacteristic.value field.
private var lastNotifiedValue: ByteArray = ByteArray(0)
/**
* Sets the listener for transport events. Must be called before [startAdvertising] or [startScanning].
*/
@@ -206,7 +213,14 @@ class AndroidBleTransport(
}
peerMap[peer.deviceUuid] = peer
val gatt = device.connectGatt(context, false, gattClientCallback, BluetoothDevice.TRANSPORT_LE)
val gatt =
device.connectGatt(
context,
false,
gattClientCallback,
BluetoothDevice.TRANSPORT_LE,
BluetoothDevice.PHY_LE_1M_MASK,
)
gattConnections[peer.deviceUuid] = gatt
}
@@ -227,8 +241,18 @@ class AndroidBleTransport(
val service = gatt.getService(serviceUuid) ?: return false
val char = service.getCharacteristic(writeCharUuid) ?: return false
char.value = chunk
return gatt.writeCharacteristic(char)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeCharacteristic(
char,
chunk,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
) == BluetoothStatusCodes.SUCCESS
} else {
@Suppress("DEPRECATION")
char.value = chunk
@Suppress("DEPRECATION")
gatt.writeCharacteristic(char)
}
}
override fun notifyChunk(
@@ -239,8 +263,15 @@ class AndroidBleTransport(
val device = connectedDevices[peer.deviceUuid] ?: return false
val char = readCharacteristic ?: return false
char.value = chunk
return server.notifyCharacteristicChanged(device, char, false)
lastNotifiedValue = chunk
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
server.notifyCharacteristicChanged(device, char, false, chunk) == BluetoothStatusCodes.SUCCESS
} else {
@Suppress("DEPRECATION")
char.value = chunk
@Suppress("DEPRECATION")
server.notifyCharacteristicChanged(device, char, false)
}
}
override fun requestMtu(
@@ -341,7 +372,7 @@ class AndroidBleTransport(
requestId,
BluetoothGatt.GATT_SUCCESS,
0,
characteristic.value ?: ByteArray(0),
lastNotifiedValue,
)
}
}
@@ -398,13 +429,20 @@ class AndroidBleTransport(
val cccd = readChar.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
cccd?.let {
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(it)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeDescriptor(it, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
} else {
@Suppress("DEPRECATION")
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
@Suppress("DEPRECATION")
gatt.writeDescriptor(it)
}
}
listener?.onPeerConnected(peer)
}
@Deprecated("Used on Android 12 and lower. API 33+ uses the overload with the value parameter.")
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
@@ -412,12 +450,26 @@ class AndroidBleTransport(
if (characteristic.uuid == readCharUuid) {
val peerUuid = deviceUuidMap[gatt.device.address] ?: return
val peer = peerMap[peerUuid] ?: return
@Suppress("DEPRECATION")
val value = characteristic.value ?: return
listener?.onChunkReceived(peer, value)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
) {
if (characteristic.uuid == readCharUuid) {
val peerUuid = deviceUuidMap[gatt.device.address] ?: return
val peer = peerMap[peerUuid] ?: return
listener?.onChunkReceived(peer, value)
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,