修复 setUp 和 clearServices 错误 (#39)

This commit is contained in:
iAMD
2024-01-17 17:58:22 +08:00
committed by GitHub
parent 6f0bc77ac9
commit 5b308f044b
14 changed files with 205 additions and 126 deletions

View File

@ -1,10 +1,15 @@
## 5.0.3
* `Android` Fix the wrong blutooth low energy state caused by multi permission requests at the same time.
* `Android` Fix the ConcurrentModificationException when `PeripheralManager#clearServices` is called.
## 5.0.2
* Fix the issue that [discoverGATT failed caused by CoW](https://github.com/yanshouwang/bluetooth_low_energy/issues/36).
* `iOS` Fix the issue that [discoverGATT failed](https://github.com/yanshouwang/bluetooth_low_energy/issues/36) caused by CoW.
## 5.0.1
* Fix the issue that [completion was called duplicately caused by CoW](https://github.com/yanshouwang/bluetooth_low_energy/issues/36).
* `iOS` Fix the issue that [completion was called duplicately](https://github.com/yanshouwang/bluetooth_low_energy/issues/36) caused by CoW.
## 5.0.0

View File

@ -23,15 +23,15 @@ packages:
path: ".."
relative: true
source: path
version: "5.0.2"
version: "5.0.3"
bluetooth_low_energy_android:
dependency: transitive
description:
name: bluetooth_low_energy_android
sha256: "502382b2bc6d0bf9e7aa635bafa28057cb8538d6f2dd9c2eceb6d0111bcee94a"
sha256: "703faeeecce61887af4fa34bce5e0f5c25019d585f2e05723948332512c4eedc"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "5.0.2"
bluetooth_low_energy_darwin:
dependency: transitive
description:

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy
description: A Flutter plugin for controlling the bluetooth low energy, supports central and peripheral apis.
version: 5.0.2
version: 5.0.3
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment:
@ -11,7 +11,7 @@ dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^5.0.0
bluetooth_low_energy_android: ^5.0.0
bluetooth_low_energy_android: ^5.0.2
bluetooth_low_energy_darwin: ^5.0.2
bluetooth_low_energy_windows: ^5.0.0
bluetooth_low_energy_linux: ^5.0.0

View File

@ -1,3 +1,12 @@
## 5.0.2
* Fix the ConcurrentModificationException when `PeripheralManager#clearServices` is called.
* Fix the `CentralManager#setUp` and `PeripheralManager#setUp` were blocked.
## 5.0.1
* Fix the wrong blutooth low energy state caused by multi permission requests at the same time.
## 5.0.0
* Now `CentralManager#writeCharacteristic` and `PeripheralManager#writeCharacteristic` will fragment the value automatically, the maximum write length is 512 bytes.

View File

@ -50,8 +50,10 @@ enum class MyBluetoothLowEnergyStateArgs(val raw: Int) {
UNKNOWN(0),
UNSUPPORTED(1),
UNAUTHORIZED(2),
POWEREDOFF(3),
POWEREDON(4);
OFF(3),
TURNINGON(4),
ON(5),
TURNINGOFF(6);
companion object {
fun ofRaw(raw: Int): MyBluetoothLowEnergyStateArgs? {
@ -327,7 +329,7 @@ private object MyCentralManagerHostApiCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface MyCentralManagerHostApi {
fun setUp()
fun setUp(callback: (Result<Unit>) -> Unit)
fun startDiscovery(callback: (Result<Unit>) -> Unit)
fun stopDiscovery()
fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit)
@ -353,14 +355,14 @@ interface MyCentralManagerHostApi {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.bluetooth_low_energy_android.MyCentralManagerHostApi.setUp", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.setUp()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
api.setUp() { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
@ -795,7 +797,7 @@ private object MyPeripheralManagerHostApiCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface MyPeripheralManagerHostApi {
fun setUp()
fun setUp(callback: (Result<Unit>) -> Unit)
fun addService(serviceArgs: MyGattServiceArgs, callback: (Result<Unit>) -> Unit)
fun removeService(hashCodeArgs: Long)
fun clearServices()
@ -816,14 +818,14 @@ interface MyPeripheralManagerHostApi {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.bluetooth_low_energy_android.MyPeripheralManagerHostApi.setUp", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
var wrapped: List<Any?>
try {
api.setUp()
wrapped = listOf<Any?>(null)
} catch (exception: Throwable) {
wrapped = wrapError(exception)
api.setUp() { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
} else {
reply.reply(wrapResult(null))
}
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)

View File

@ -131,8 +131,11 @@ fun MyGattServiceArgs.toService(): BluetoothGattService {
//region ToArgs
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.POWEREDON
else -> MyBluetoothLowEnergyStateArgs.POWEREDOFF
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNINGON
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNINGOFF
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
}
}

View File

@ -14,8 +14,6 @@ import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
import java.util.UUID
import java.util.concurrent.Executor
typealias MyBluetoothLowEnergyState = MyBluetoothLowEnergyStateArgs
abstract class MyBluetoothLowEnergyManager(context: Context) {
companion object {
val CLIENT_CHARACTERISTIC_CONFIG_UUID =
@ -40,33 +38,26 @@ abstract class MyBluetoothLowEnergyManager(context: Context) {
}
protected val executor get() = ContextCompat.getMainExecutor(mContext) as Executor
protected val hasFeature get() = mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
protected val manager
get() = ContextCompat.getSystemService(
mContext, BluetoothManager::class.java
) as BluetoothManager
protected val adapter get() = manager.adapter as BluetoothAdapter
protected fun initialize() {
val hasFeature =
mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
if (hasFeature) {
val authorized = permissions.all { permission ->
ActivityCompat.checkSelfPermission(
mContext, permission
) == PackageManager.PERMISSION_GRANTED
}
if (authorized) {
mOnAuthorizationStateChanged(true)
} else {
val activity = mBinding.activity
ActivityCompat.requestPermissions(activity, permissions, requestCode)
}
} else {
val state = MyBluetoothLowEnergyState.UNSUPPORTED
onStateChanged(state)
protected fun checkPermissions(): Boolean {
return permissions.all { permission ->
ActivityCompat.checkSelfPermission(
mContext, permission
) == PackageManager.PERMISSION_GRANTED
}
}
protected fun requestPermissions() {
val activity = mBinding.activity
ActivityCompat.requestPermissions(activity, permissions, requestCode)
}
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
mBinding = binding
@ -82,8 +73,9 @@ abstract class MyBluetoothLowEnergyManager(context: Context) {
if (this.requestCode != requestCode) {
return false
}
val authorized = results.all { r -> r == PackageManager.PERMISSION_GRANTED }
mOnAuthorizationStateChanged(authorized)
val granted =
permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
onPermissionsRequested(granted)
return true
}
@ -92,23 +84,11 @@ abstract class MyBluetoothLowEnergyManager(context: Context) {
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val extra = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val state = extra.toBluetoothLowEnergyStateArgs()
onStateChanged(state)
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
onAdapterStateChanged(state)
}
private fun mOnAuthorizationStateChanged(authorized: Boolean) {
val state = if (authorized) {
mRegisterReceiver()
if (adapter.state == BluetoothAdapter.STATE_ON) MyBluetoothLowEnergyState.POWEREDON
else MyBluetoothLowEnergyState.POWEREDOFF
} else {
MyBluetoothLowEnergyState.UNAUTHORIZED
}
onStateChanged(state)
}
private fun mRegisterReceiver() {
protected fun registerReceiver() {
if (mRegistered) {
return
}
@ -119,6 +99,8 @@ abstract class MyBluetoothLowEnergyManager(context: Context) {
abstract val permissions: Array<String>
abstract val requestCode: Int
abstract fun onStateChanged(state: MyBluetoothLowEnergyState)
abstract fun onPermissionsRequested(granted: Boolean)
abstract fun onAdapterStateChanged(state: Int)
}

View File

@ -39,6 +39,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
private val mCharacteristics: MutableMap<String, Map<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, Map<Long, BluetoothGattDescriptor>>
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
private var mStartDiscoveryCallback: ((Result<Unit>) -> Unit)?
private val mConnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
private val mDisconnectCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
@ -61,6 +62,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mSetUpCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks = mutableMapOf()
mDisconnectCallbacks = mutableMapOf()
@ -93,9 +95,25 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override val requestCode: Int
get() = REQUEST_CODE
override fun setUp() {
mClearState()
initialize()
override fun setUp(callback: (Result<Unit>) -> Unit) {
try {
mClearState()
val stateArgs = if (hasFeature) {
val granted = checkPermissions()
if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else {
requestPermissions()
mSetUpCallback = callback
return
}
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
@ -104,10 +122,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val settings =
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
mScanner.startScan(filters, settings, mScanCallback)
mStartDiscoveryCallback = callback
executor.execute {
onScanSucceed()
}
mStartDiscoveryCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
@ -319,9 +337,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun onStateChanged(state: MyBluetoothLowEnergyState) {
val stateNumberArgs = state.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
override fun onPermissionsRequested(granted: Boolean) {
val callback = mSetUpCallback ?: return
val stateArgs = if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
}
override fun onAdapterStateChanged(state: Int) {
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mOnStateChanged(stateArgs)
}
private fun onScanSucceed() {
@ -554,6 +582,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mCharacteristics.clear()
mDescriptors.clear()
mSetUpCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks.clear()
mDisconnectCallbacks.clear()
@ -566,6 +595,11 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mWriteDescriptorCallbacks.clear()
}
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
}
private fun mRetrieveCharacteristic(
addressArgs: String, hashCodeArgs: Long
): BluetoothGattCharacteristic? {

View File

@ -19,7 +19,7 @@ import io.flutter.plugin.common.BinaryMessenger
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostApi {
companion object {
const val REQUEST_CODE = 444
const val REQUEST_CODE = 445
}
private val advertiser get() = adapter.bluetoothLeAdvertiser
@ -89,9 +89,25 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
override val requestCode: Int
get() = REQUEST_CODE
override fun setUp() {
mClearState()
initialize()
override fun setUp(callback: (Result<Unit>) -> Unit) {
try {
mClearState()
val stateArgs = if (hasFeature) {
val granted = checkPermissions()
if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else {
requestPermissions()
mSetUpCallback = callback
return
}
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun addService(serviceArgs: MyGattServiceArgs, callback: (Result<Unit>) -> Unit) {
@ -143,28 +159,45 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
mAddServiceCallback = callback
} catch (e: Throwable) {
mClearService(serviceArgs)
callback(Result.failure(e))
}
}
override fun removeService(hashCodeArgs: Long) {
val service = mServices[hashCodeArgs] as BluetoothGattService
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs[hashCode] as MyGattServiceArgs
val service = mServices.remove(hashCodeArgs) as BluetoothGattService
val removed = mServer.removeService(service)
if (!removed) {
throw IllegalStateException()
}
mClearService(serviceArgs)
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs.remove(hashCode) as MyGattServiceArgs
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristic =
mCharacteristics.remove(characteristicHashCodeArgs) as BluetoothGattCharacteristic
val characteristicHashCode = characteristic.hashCode()
mCharacteristicsArgs.remove(characteristicHashCode)
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptor =
mDescriptors.remove(descriptorHashCodeArgs) as BluetoothGattDescriptor
val descriptorHashCode = descriptor.hashCode()
mDescriptorsArgs.remove(descriptorHashCode)
}
}
}
override fun clearServices() {
mServer.clearServices()
val servicesArgs = this.mServicesArgs.values
for (serviceArgs in servicesArgs) {
mClearService(serviceArgs)
}
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
}
override fun startAdvertising(
@ -235,14 +268,19 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun onStateChanged(state: MyBluetoothLowEnergyState) {
when (state) {
MyBluetoothLowEnergyStateArgs.POWEREDOFF -> mCloseGattServer()
MyBluetoothLowEnergyStateArgs.POWEREDON -> mOpenGattServer()
else -> {}
}
val stateNumberArgs = state.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
override fun onPermissionsRequested(granted: Boolean) {
val callback = mSetUpCallback ?: return
val stateArgs = if (granted) {
registerReceiver()
adapter.state.toBluetoothLowEnergyStateArgs()
} else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
mOnStateChanged(stateArgs)
callback(Result.success(Unit))
}
override fun onAdapterStateChanged(state: Int) {
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mOnStateChanged(stateArgs)
}
fun onServiceAdded(status: Int, service: BluetoothGattService) {
@ -253,9 +291,6 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs[hashCode] ?: return
mClearService(serviceArgs)
}
}
@ -403,7 +438,18 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
mNotifyCharacteristicValueChangedCallbacks.clear()
}
private fun mOpenGattServer() {
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
// Renew GATT server when bluetooth adapter state changed.
when (stateArgs) {
MyBluetoothLowEnergyStateArgs.OFF -> mCloseServer()
MyBluetoothLowEnergyStateArgs.ON -> mOpenServer()
else -> {}
}
}
private fun mOpenServer() {
if (mOpening) {
return
}
@ -411,32 +457,11 @@ class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
mOpening = true
}
private fun mCloseGattServer() {
private fun mCloseServer() {
if (!mOpening) {
return
}
mServer.close()
mOpening = false
}
private fun mClearService(serviceArgs: MyGattServiceArgs) {
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptor = mDescriptors.remove(descriptorHashCodeArgs) ?: continue
val descriptorHashCode = descriptor.hashCode()
mDescriptorsArgs.remove(descriptorHashCode)
}
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristic = mCharacteristics.remove(characteristicHashCodeArgs) ?: continue
val characteristicHashCode = characteristic.hashCode()
mCharacteristicsArgs.remove(characteristicHashCode)
}
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
val service = mServices.remove(serviceHashCodeArgs) ?: return
val serviceHashCode = service.hashCode()
mServicesArgs.remove(serviceHashCode)
}
}

View File

@ -15,7 +15,7 @@ packages:
path: ".."
relative: true
source: path
version: "5.0.0"
version: "5.0.1"
bluetooth_low_energy_platform_interface:
dependency: "direct main"
description:

View File

@ -14,7 +14,20 @@ export 'my_api.g.dart';
// ToObject
extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
BluetoothLowEnergyState toState() {
return BluetoothLowEnergyState.values[index];
switch (this) {
case MyBluetoothLowEnergyStateArgs.unknown:
return BluetoothLowEnergyState.unknown;
case MyBluetoothLowEnergyStateArgs.unsupported:
return BluetoothLowEnergyState.unsupported;
case MyBluetoothLowEnergyStateArgs.unauthorized:
return BluetoothLowEnergyState.unauthorized;
case MyBluetoothLowEnergyStateArgs.off:
case MyBluetoothLowEnergyStateArgs.turningOn:
return BluetoothLowEnergyState.poweredOff;
case MyBluetoothLowEnergyStateArgs.on:
case MyBluetoothLowEnergyStateArgs.turningOff:
return BluetoothLowEnergyState.poweredOn;
}
}
}

View File

@ -29,8 +29,10 @@ enum MyBluetoothLowEnergyStateArgs {
unknown,
unsupported,
unauthorized,
poweredOff,
poweredOn,
off,
turningOn,
on,
turningOff,
}
enum MyGattCharacteristicPropertyArgs {

View File

@ -15,8 +15,10 @@ enum MyBluetoothLowEnergyStateArgs {
unknown,
unsupported,
unauthorized,
poweredOff,
poweredOn,
off,
turningOn,
on,
turningOff,
}
enum MyGattCharacteristicPropertyArgs {
@ -124,6 +126,7 @@ class MyGattServiceArgs {
@HostApi()
abstract class MyCentralManagerHostApi {
@async
void setUp();
@async
void startDiscovery();
@ -182,6 +185,7 @@ abstract class MyCentralManagerFlutterApi {
@HostApi()
abstract class MyPeripheralManagerHostApi {
@async
void setUp();
@async
void addService(MyGattServiceArgs serviceArgs);

View File

@ -1,6 +1,6 @@
name: bluetooth_low_energy_android
description: Android implementation of the bluetooth_low_energy plugin.
version: 5.0.0
version: 5.0.2
homepage: https://github.com/yanshouwang/bluetooth_low_energy
environment: