Improve Android bluetoothGatt management (#124)
* Improve Android bluetoothGatt management * Update Changelog * Apply suggestions from code review Co-authored-by: Foti Dim <foti@navideck.com> --------- Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
+2
-1
@@ -1,5 +1,6 @@
|
||||
## 0.14.0
|
||||
## 0.15.0
|
||||
* `getSystemDevices(withServices:)` now sets several generic services by default as filter
|
||||
* `getConnectionState` on Android will now return `BleConnectionState.disconnected` if device is connected to the system but not to the app
|
||||
|
||||
## 0.14.0
|
||||
* BREAKING CHANGE: `bleDevice.name` now filters out non-printable characters
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.navideck.universal_ble
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.bluetooth.BluetoothStatusCodes
|
||||
import android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED
|
||||
import android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED
|
||||
@@ -13,20 +11,16 @@ import android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED
|
||||
import android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR
|
||||
import android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES
|
||||
import android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.Build
|
||||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.keyIterator
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.UUID
|
||||
|
||||
private const val TAG = "UniversalBlePlugin"
|
||||
|
||||
val knownGatts = mutableListOf<BluetoothGatt>()
|
||||
private val knownGatts = mutableMapOf<String, BluetoothGatt>()
|
||||
val ccdCharacteristic: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
enum class BleConnectionState(val value: Long) {
|
||||
@@ -91,11 +85,29 @@ fun List<String>.toUUIDList(): List<UUID> {
|
||||
return this.map { UUID.fromString(it.validFullUUID()) }
|
||||
}
|
||||
|
||||
|
||||
fun String.toBluetoothGatt(): BluetoothGatt {
|
||||
return knownGatts.find { it.device.address == this }
|
||||
return this.findGatt()
|
||||
?: throw FlutterError("IllegalArgument", "Unknown deviceId: $this", null)
|
||||
}
|
||||
|
||||
fun String.isKnownGatt(): Boolean {
|
||||
return this.findGatt() != null
|
||||
}
|
||||
|
||||
fun String.findGatt(): BluetoothGatt? {
|
||||
return knownGatts[this]
|
||||
}
|
||||
|
||||
fun BluetoothGatt.saveCacheIfNeeded() {
|
||||
knownGatts[this.device.address] = this
|
||||
}
|
||||
|
||||
fun BluetoothGatt.removeCache() {
|
||||
knownGatts.remove(this.device.address)
|
||||
}
|
||||
|
||||
|
||||
fun Int.toAvailabilityState(): Long {
|
||||
return when (this) {
|
||||
BluetoothAdapter.STATE_OFF -> AvailabilityState.PoweredOff.value
|
||||
|
||||
@@ -48,7 +48,6 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
private lateinit var bluetoothManager: BluetoothManager
|
||||
private lateinit var safeScanner: SafeScanner
|
||||
private val cachedServicesMap = mutableMapOf<String, List<String>>()
|
||||
private val devicesStateMap = mutableMapOf<String, Int>()
|
||||
private val universalBleFilterUtil = UniversalBleFilterUtil()
|
||||
|
||||
// Flutter Futures
|
||||
@@ -182,11 +181,10 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
|
||||
override fun connect(deviceId: String) {
|
||||
val currentState = devicesStateMap[deviceId]
|
||||
|
||||
// If already connected, send connected message,
|
||||
// if connecting, do nothing
|
||||
knownGatts.find { it.device.address == deviceId }?.let {
|
||||
deviceId.findGatt()?.let {
|
||||
val currentState = bluetoothManager.getConnectionState(it.device, BluetoothProfile.GATT)
|
||||
if (currentState == BluetoothGatt.STATE_CONNECTED) {
|
||||
Log.e(TAG, "$deviceId Already connected")
|
||||
mainThreadHandler?.post {
|
||||
@@ -210,7 +208,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
} else {
|
||||
remoteDevice.connectGatt(context, false, this)
|
||||
}
|
||||
knownGatts.add(gatt)
|
||||
gatt.saveCacheIfNeeded()
|
||||
}
|
||||
|
||||
override fun disconnect(deviceId: String) {
|
||||
@@ -218,10 +216,18 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
|
||||
override fun getConnectionState(deviceId: String): Long {
|
||||
return bluetoothManager.getConnectionState(
|
||||
val connectionState = bluetoothManager.getConnectionState(
|
||||
bluetoothManager.adapter.getRemoteDevice(deviceId),
|
||||
BluetoothProfile.GATT
|
||||
).toBleConnectionState().value
|
||||
)
|
||||
|
||||
return if (deviceId.isKnownGatt() || connectionState == BluetoothGatt.STATE_DISCONNECTED || connectionState == BluetoothGatt.STATE_DISCONNECTING) {
|
||||
connectionState.toBleConnectionState().value
|
||||
} else {
|
||||
// Might be connected with device, but not with app
|
||||
Log.e(TAG, "Device might be connected but not known to this app")
|
||||
BleConnectionState.Disconnected.value
|
||||
}
|
||||
}
|
||||
|
||||
override fun discoverServices(
|
||||
@@ -229,19 +235,14 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
callback: (Result<List<UniversalBleService>>) -> Unit,
|
||||
) {
|
||||
try {
|
||||
if (!deviceId.toBluetoothGatt().discoverServices()) {
|
||||
val gatt = deviceId.toBluetoothGatt()
|
||||
if (gatt.discoverServices()) {
|
||||
discoverServicesFutureList.add(DiscoverServicesFuture(deviceId, callback))
|
||||
} else {
|
||||
callback(
|
||||
Result.failure(
|
||||
FlutterError(
|
||||
"Failed",
|
||||
"Failed to discover services",
|
||||
null
|
||||
)
|
||||
)
|
||||
Result.failure(FlutterError("Failed", "Failed to discover services", null))
|
||||
)
|
||||
return
|
||||
}
|
||||
discoverServicesFutureList.add(DiscoverServicesFuture(deviceId, callback))
|
||||
} catch (e: FlutterError) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
@@ -684,7 +685,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
|
||||
// If its a known gatt, just discover services
|
||||
knownGatts.find { it.device.address == device.address }?.let { gatt ->
|
||||
device.address.findGatt()?.let { gatt ->
|
||||
gatt.services?.let { services ->
|
||||
updateCallback(services.map { service -> service.uuid.toString() })
|
||||
return
|
||||
@@ -710,7 +711,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
|
||||
if (gatt?.discoverServices() != true) {
|
||||
updateCallback(null)
|
||||
if (!knownGatts.any { it.device.address == device.address }) {
|
||||
if (!device.address.isKnownGatt()) {
|
||||
gatt?.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -727,7 +728,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
updateCallback(uuids)
|
||||
}
|
||||
if (!knownGatts.any { it.device.address == device.address }) {
|
||||
if (!device.address.isKnownGatt()) {
|
||||
gatt?.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -741,7 +742,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
|
||||
private fun cleanConnection(gatt: BluetoothGatt) {
|
||||
knownGatts.remove(gatt)
|
||||
gatt.removeCache()
|
||||
gatt.disconnect()
|
||||
|
||||
readResultFutureList.removeAll {
|
||||
@@ -892,7 +893,6 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
|
||||
|
||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||
devicesStateMap[gatt.device.address] = newState
|
||||
Log.d(
|
||||
TAG,
|
||||
"onConnectionStateChange-> Status: $status ${status.parseHciErrorCode()}, NewState: $newState"
|
||||
|
||||
Reference in New Issue
Block a user