* 调整接口

* 临时提交

* 重构 Android 平台代码

* 临时提交

* 临时提交

* Android 6.0.0-dev.0

* 临时提交

* 实现 Windows 接口

* windows-6.0.0-dev.0

* Darwin 6.0.0-dev.0

* 临时提交

* 1

* 临时提交

* 调整接口

* windows-6.0.0-dev.1

* 临时提交

* interface-6.0.0-dev.7

* interface-6.0.0-dev.8

* 临时提交

* windows-6.0.0-dev.2

* 删除多余脚本

* interface-6.0.0-dev.9

* 临时提交

* 临时提交

* interface-6.0.0-dev.10

* android-6.0.0-dev.1

* windows-6.0.0-dev.3

* 临时提交

* interface-6.0.0-dev.11

* windows-6.0.0-dev.4

* 更新 pubspec.lock

* 1

* interface-6.0.0-dev.12

* interface-6.0.0-dev.13

* interface-6.0.0-dev.14

* 临时提交

* interface-6.0.0-dev.15

* 临时提交

* interface-6.0.0-dev.16

* android-6.0.0-dev.2

* 临时提交

* windows-6.0.0-dev.5

* 临时提交

* 临时提交

* windows-6.0.0-dev.6

* 优化注释和代码样式

* 优化代码

* 临时提交

* 实现 Dart 接口

* darwin-6.0.0-dev.0

* linux-6.0.0-dev.0

* 修复已知问题

* 修复问题

* 6.0.0-dev.0

* 修改包名

* 更新版本

* 移除原生部分

* 临时提交

* 修复问题

* 更新 pigeon 19.0.0

* 更新 README,添加迁移文档

* linux-6.0.0-dev.1

* 解析扫描回复和扩展广播

* 修复 googletest 版本警告问题

* Use centralArgs instead of addressArgs

* interface-6.0.0-dev.18

* android-6.0.0-dev.4

* linux-6.0.0-dev.2

* windows-6.0.0-dev.8

* darwin-6.0.0-dev.2

* 6.0.0-dev.1

* Update LICENSE

* clang-format

* Combine ADV_IND and SCAN_RES

* TEMP commit: update exampe

* Adjust advertisement combine logic

* Implement `MyPeripheralMananger` on Windows

* Added NuGet auto download and scan for names on peripheral (#67)

* fetch nuget using other technique

* move FetchContent to right location in CMakeLists.txt

* also added hash for googletest

---------

Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch>

* Fix errors.

* Check BluetoothAdapter role supported state and implement PeripheralManager on Flutter side.

* Sort code

* Fix known errors

* interface-6.0.0-dev.19

* windows-6.0.0-dev.9

* Optimize example

* android-6.0.0-dev.5

* Optimize the Adverrtisement BottomSheet.

* linux-6.0.0-dev.3

* Update dependency

* Fix example errors.

* Temp commit.

* darwin-6.0.0-dev.3

* 6.0.0-dev.2

* Update README.md

* 6.0.0

* darwin-6.0.0-dev.4

* android-6.0.0-dev.6

* 6.0.0-dev.3

* Update docs.

* interface-6.0.0

* android-6.0.0

* darwin-6.0.0

* linux-6.0.0

* windows-6.0.0

* 6.0.0

* Update dependency

---------

Co-authored-by: Kevin De Keyser <dekeyser.kevin97@gmail.com>
Co-authored-by: Kevin De Keyser <kevin@dekeyser.ch>
This commit is contained in:
渐渐被你吸引
2024-06-04 00:44:39 +08:00
committed by GitHub
parent 71de531ceb
commit 108b6a804f
380 changed files with 23782 additions and 14127 deletions

View File

@ -26,5 +26,4 @@ migrate_working_dir/
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
channel: "stable"
project_type: plugin
@ -13,11 +13,11 @@ project_type: plugin
migration:
platforms:
- platform: root
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
- platform: android
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
# User provided section

View File

@ -1,3 +1,60 @@
## 6.0.0
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
* Add `CentralManager#requestMTU` method.
* Add `CentralManager#retrieveConnectedPeripherals` method.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Remove `BluetoothLowEnergyManager#setUp` method.
* Add `CentralManager#mtuChanged` event.
* Add `PeripheralManager#mtuChanged` event.
* Add `BluetoothLowEnergyManager.authorize()` method.
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
* Rewrite example with MVVM.
* Fix known issues.
## 6.0.0-dev.6
* Fix known issues.
## 6.0.0-dev.5
* Rewrite example with MVVM.
* Fix known issues.
## 6.0.0-dev.4
* Implement new APIs.
## 6.0.0-dev.3
* Move organizaioin.
## 6.0.0-dev.2
* Implement new APIs.
## 6.0.0-dev.1
* Add `CentralManager#mtuChanged` event.
* Add `PeripheralManager#mtuChanged` event.
* Add `BluetoothLowEnergyManager.authorize()` method.
* Add `BluetoothLowEnergyManager.showAppSettings()` method.
* Add modifiers to all classes.
* Use new capitalization rules.
* Make it possible to change the `logLevel` before `initialize()`.
## 6.0.0-dev.0
* Add `serviceUUIDs` argument to `CentralManager#startDiscovery` method.
* Add `CentralManager#requestMTU` method.
* Add `CentralManager#retrieveConnectedPeripherals` method.
* Move `BluetoothLowEnergyManager#getState` to `BluetoothLowEnergyManager#state`.
* Move `CentralManger.instance` to factory constructor.
* Move `PeripheralManager.instance` to factory constructor.
* Remove `BluetoothLowEnergyManager#setUp` method.
## 5.0.5
* Fix the issue that [Advertisement resolve failed with `NullPointerException`](https://github.com/yanshouwang/bluetooth_low_energy/issues/59)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 yanshouwang
Copyright (c) 2024 hebei.dev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,16 +1,16 @@
group 'dev.yanshouwang.bluetooth_low_energy_android'
version '1.0-SNAPSHOT'
group = "dev.hebei.bluetooth_low_energy_android"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = "1.7.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath("com.android.tools.build:gradle:7.3.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
@ -21,37 +21,37 @@ allprojects {
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
if (project.android.hasProperty("namespace")) {
namespace 'dev.yanshouwang.bluetooth_low_energy_android'
namespace = "dev.hebei.bluetooth_low_energy_android"
}
compileSdkVersion 33
compileSdk = 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = "1.8"
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
test.java.srcDirs += 'src/test/kotlin'
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdkVersion 21
minSdk = 21
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {

View File

@ -1,15 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.yanshouwang.bluetooth_low_energy_android">
package="dev.hebei.bluetooth_low_energy_android">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>

View File

@ -1,11 +1,11 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
/** BluetoothLowEnergyAndroid */
class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
/** BluetoothLowEnergyAndroidPlugin */
class BluetoothLowEnergyAndroidPlugin : FlutterPlugin, ActivityAware {
private lateinit var mCentralManager: MyCentralManager
private lateinit var mPeripheralManager: MyPeripheralManager
@ -14,14 +14,14 @@ class BluetoothLowEnergyAndroid : FlutterPlugin, ActivityAware {
val binaryMessenger = binding.binaryMessenger
mCentralManager = MyCentralManager(context, binaryMessenger)
mPeripheralManager = MyPeripheralManager(context, binaryMessenger)
MyCentralManagerHostApi.setUp(binaryMessenger, mCentralManager)
MyPeripheralManagerHostApi.setUp(binaryMessenger, mPeripheralManager)
MyCentralManagerHostAPI.setUp(binaryMessenger, mCentralManager)
MyPeripheralManagerHostAPI.setUp(binaryMessenger, mPeripheralManager)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
val binaryMessenger = binding.binaryMessenger
MyCentralManagerHostApi.setUp(binaryMessenger, null)
MyPeripheralManagerHostApi.setUp(binaryMessenger, null)
MyCentralManagerHostAPI.setUp(binaryMessenger, null)
MyPeripheralManagerHostAPI.setUp(binaryMessenger, null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {

View File

@ -0,0 +1,258 @@
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.ScanResult
import android.os.Build
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
//region ToObject
fun MyGATTCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGATTCharacteristicWriteTypeArgs.WITH_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGATTCharacteristicWriteTypeArgs.WITHOUT_RESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}
fun MyGATTStatusArgs.toStatus(): Int {
return when (this) {
MyGATTStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
MyGATTStatusArgs.READ_NOT_PERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGATTStatusArgs.WRITE_NOT_PERMITTED -> BluetoothGatt.GATT_WRITE_NOT_PERMITTED
MyGATTStatusArgs.INSUFFICIENT_AUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
MyGATTStatusArgs.REQUEST_NOT_SUPPORTED -> BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED
MyGATTStatusArgs.INSUFFICIENT_ENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
MyGATTStatusArgs.INVALID_OFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
MyGATTStatusArgs.INSUFFICIENT_AUTHORIZATION -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION
else BluetoothGatt.GATT_FAILURE
MyGATTStatusArgs.INVALID_ATTRIBUTE_LENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
MyGATTStatusArgs.CONNECTION_CONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
MyGATTStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
}
}
fun MyAdvertisementArgs.toAdvertiseData(): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
advertiseDataBuilder.addServiceUuid(serviceUUID)
}
for (entry in serviceDataArgs) {
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
val serviceData = entry.value as ByteArray
advertiseDataBuilder.addServiceData(serviceDataUUID, serviceData)
}
for (args in manufacturerSpecificDataArgs) {
val itemArgs = args as MyManufacturerSpecificDataArgs
val manufacturerId = itemArgs.idArgs.toInt()
val manufacturerSpecificData = itemArgs.dataArgs
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
}
return advertiseDataBuilder.build()
}
fun MyAdvertisementArgs.toScanResponse(): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
val includeDeviceName = nameArgs != null
advertiseDataBuilder.setIncludeDeviceName(includeDeviceName)
return advertiseDataBuilder.build()
}
fun MyMutableGATTDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
val uuid = UUID.fromString(uuidArgs)
val permissions = BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
return BluetoothGattDescriptor(uuid, permissions)
}
fun MyMutableGATTCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
val uuid = UUID.fromString(uuidArgs)
val properties = getProperties()
val permissions = getPermissions()
return BluetoothGattCharacteristic(uuid, properties, permissions)
}
fun MyMutableGATTCharacteristicArgs.getProperties(): Int {
val propertiesArgs = propertyNumbersArgs.requireNoNulls().map { args ->
val raw = args.toInt()
MyGATTCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE)
val notify = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.NOTIFY)
val indicate = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.INDICATE)
var properties = 0
if (read) properties = properties or BluetoothGattCharacteristic.PROPERTY_READ
if (write) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE
if (writeWithoutResponse) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
if (notify) properties = properties or BluetoothGattCharacteristic.PROPERTY_NOTIFY
if (indicate) properties = properties or BluetoothGattCharacteristic.PROPERTY_INDICATE
return properties
}
fun MyMutableGATTCharacteristicArgs.getPermissions(): Int {
val propertiesArgs = propertyNumbersArgs.requireNoNulls().map { args ->
val raw = args.toInt()
MyGATTCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse = propertiesArgs.contains(MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE)
var permissions = 0
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
if (write || writeWithoutResponse) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
return permissions
}
fun MyMutableGATTServiceArgs.toService(): BluetoothGattService {
val uuid = UUID.fromString(uuidArgs)
val serviceType = if (isPrimaryArgs) BluetoothGattService.SERVICE_TYPE_PRIMARY
else BluetoothGattService.SERVICE_TYPE_SECONDARY
return BluetoothGattService(uuid, serviceType)
} //endregion
//region ToArgs
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
BluetoothAdapter.STATE_OFF -> MyBluetoothLowEnergyStateArgs.OFF
BluetoothAdapter.STATE_TURNING_ON -> MyBluetoothLowEnergyStateArgs.TURNING_ON
BluetoothAdapter.STATE_ON -> MyBluetoothLowEnergyStateArgs.ON
BluetoothAdapter.STATE_TURNING_OFF -> MyBluetoothLowEnergyStateArgs.TURNING_OFF
else -> MyBluetoothLowEnergyStateArgs.UNKNOWN
}
}
fun Int.toConnectionStateArgs(): MyConnectionStateArgs {
return when (this) {
BluetoothProfile.STATE_DISCONNECTED -> MyConnectionStateArgs.DISCONNECTED
BluetoothProfile.STATE_CONNECTING -> MyConnectionStateArgs.CONNECTING
BluetoothProfile.STATE_CONNECTED -> MyConnectionStateArgs.CONNECTED
BluetoothProfile.STATE_DISCONNECTING -> MyConnectionStateArgs.DISCONNECTING
else -> throw IllegalArgumentException()
}
}
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): List<MyManufacturerSpecificDataArgs> {
var index = 0
val size = size()
val itemsArgs = mutableListOf<MyManufacturerSpecificDataArgs>()
while (index < size) {
val idArgs = keyAt(index).toLong()
val dataArgs = valueAt(index)
val itemArgs = MyManufacturerSpecificDataArgs(idArgs, dataArgs)
itemsArgs.add(itemArgs)
index++
}
return itemsArgs
}
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
val record = scanRecord
return if (record == null) {
val nameArgs = null
val serviceUUIDsArgs = emptyList<String?>()
val serviceDataArgs = emptyMap<String?, ByteArray>()
val manufacturerSpecificDataArgs = emptyList<MyManufacturerSpecificDataArgs>()
MyAdvertisementArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
} else {
val nameArgs = record.deviceName
val serviceUUIDsArgs = record.serviceUuids?.map { uuid -> uuid.toString() } ?: emptyList()
val pairs = record.serviceData?.map { (uuid, value) ->
val key = uuid.toString()
return@map Pair(key, value)
}?.toTypedArray() ?: emptyArray()
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
val manufacturerSpecificDataArgs = record.manufacturerSpecificData?.toManufacturerSpecificDataArgs() ?: emptyList()
MyAdvertisementArgs(nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs)
}
}
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
val addressArgs = address
return MyCentralArgs(addressArgs)
}
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
val addressArgs = address
return MyPeripheralArgs(addressArgs)
}
fun BluetoothGattDescriptor.toArgs(): MyGATTDescriptorArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGATTDescriptorArgs(hashCodeArgs, uuidArgs)
}
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
val numbersArgs = mutableListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
val number = MyGATTCharacteristicPropertyArgs.READ.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
val number = MyGATTCharacteristicPropertyArgs.WRITE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
val number = MyGATTCharacteristicPropertyArgs.WRITE_WITHOUT_RESPONSE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
val number = MyGATTCharacteristicPropertyArgs.NOTIFY.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
val number = MyGATTCharacteristicPropertyArgs.INDICATE.raw.toLong()
numbersArgs.add(number)
}
return numbersArgs
}
fun BluetoothGattCharacteristic.toArgs(): MyGATTCharacteristicArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val propertyNumbersArgs = getPropertyNumbersArgs()
val descriptorsArgs = descriptors.map { it.toArgs() }
return MyGATTCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
}
fun BluetoothGattService.toArgs(): MyGATTServiceArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = uuid.toString()
val isPrimaryArgs = type == BluetoothGattService.SERVICE_TYPE_PRIMARY
val includedServicesArgs = includedServices.map { it.toArgs() }
val characteristicsArgs = characteristics.map { it.toArgs() }
return MyGATTServiceArgs(hashCodeArgs, uuidArgs, isPrimaryArgs, includedServicesArgs, characteristicsArgs)
} //endregion
val Any.hashCode get() = this.hashCode()
val Int.args get() = this.toLong()
//val Any.TAG get() = this::class.java.simpleName as String
//
//val ScanRecord.rawValues: Map<Byte, ByteArray>
// get() {
// val rawValues = mutableMapOf<Byte, ByteArray>()
// var index = 0
// val size = bytes.size
// while (index < size) {
// val length = bytes[index++].toInt() and 0xff
// if (length == 0) {
// break
// }
// val end = index + length
// val type = bytes[index++]
// val value = bytes.slice(index until end).toByteArray()
// rawValues[type] = value
// index = end
// }
// return rawValues.toMap()
// }

View File

@ -0,0 +1,16 @@
package dev.hebei.bluetooth_low_energy_android
import android.content.Intent
import io.flutter.plugin.common.PluginRegistry
class MyActivityResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.ActivityResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
return mManager.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
@ -7,8 +7,7 @@ import android.bluetooth.BluetoothGattDescriptor
import android.os.Build
import java.util.concurrent.Executor
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
BluetoothGattCallback() {
class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) : BluetoothGattCallback() {
private val mManager: MyCentralManager
private val mExecutor: Executor
@ -45,12 +44,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
value: ByteArray,
status: Int
) {
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
super.onCharacteristicRead(gatt, characteristic, value, status)
mExecutor.execute {
mManager.onCharacteristicRead(gatt, characteristic, status, value)
@ -58,9 +52,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicRead(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicRead(gatt, characteristic, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -71,18 +63,14 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
mExecutor.execute {
mManager.onCharacteristicWrite(gatt, characteristic, status)
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
super.onCharacteristicChanged(gatt, characteristic, value)
mExecutor.execute {
mManager.onCharacteristicChanged(gatt, characteristic, value)
@ -90,9 +78,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
) {
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicChanged(gatt, characteristic)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -103,9 +89,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
super.onDescriptorRead(gatt, descriptor, status, value)
mExecutor.execute {
mManager.onDescriptorRead(gatt, descriptor, status, value)
@ -113,9 +97,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
// TODO: remove this override when minSdkVersion >= 33
override fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorRead(gatt, descriptor, status)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return
@ -126,9 +108,7 @@ class MyBluetoothGattCallback(manager: MyCentralManager, executor: Executor) :
}
}
override fun onDescriptorWrite(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
) {
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
mExecutor.execute {
mManager.onDescriptorWrite(gatt, descriptor, status)

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
@ -7,14 +7,13 @@ import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import java.util.concurrent.Executor
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Executor) :
BluetoothGattServerCallback() {
class MyBluetoothGattServerCallback(manager: MyPeripheralManager, mExecutor: Executor) : BluetoothGattServerCallback() {
private val mManager: MyPeripheralManager
private val mExecutor: Executor
init {
mManager = manager
mExecutor = executor
this.mExecutor = mExecutor
}
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
@ -38,41 +37,17 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
}
}
override fun onCharacteristicReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic
) {
override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic)
mExecutor.execute {
mManager.onCharacteristicReadRequest(device, requestId, offset, characteristic)
}
}
override fun onCharacteristicWriteRequest(
device: BluetoothDevice,
requestId: Int,
characteristic: BluetoothGattCharacteristic,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
super.onCharacteristicWriteRequest(
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
)
override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
mExecutor.execute {
mManager.onCharacteristicWriteRequest(
device, requestId, characteristic, preparedWrite, responseNeeded, offset, value
)
}
}
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
super.onExecuteWrite(device, requestId, execute)
mExecutor.execute {
mManager.onExecuteWrite(device, requestId, execute)
mManager.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value)
}
}
@ -83,31 +58,24 @@ class MyBluetoothGattServerCallback(manager: MyPeripheralManager, executor: Exec
}
}
override fun onDescriptorReadRequest(
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
) {
override fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor)
mExecutor.execute {
mManager.onDescriptorReadRequest(device, requestId, offset, descriptor)
}
}
override fun onDescriptorWriteRequest(
device: BluetoothDevice,
requestId: Int,
descriptor: BluetoothGattDescriptor,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
super.onDescriptorWriteRequest(
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
)
override fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
mExecutor.execute {
mManager.onDescriptorWriteRequest(
device, requestId, descriptor, preparedWrite, responseNeeded, offset, value
)
mManager.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value)
}
}
override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
super.onExecuteWrite(device, requestId, execute)
mExecutor.execute {
mManager.onExecuteWrite(device, requestId, execute)
}
}
}

View File

@ -0,0 +1,46 @@
package dev.hebei.bluetooth_low_energy_android
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.PluginRegistry
abstract class MyBluetoothLowEnergyManager(val context: Context) {
companion object {
const val AUTHORIZE_CODE = 0x00
const val SHOW_APP_SETTINGS_CODE = 0x01
}
private val mBroadcastReceiver: BroadcastReceiver by lazy { MyBroadcastReceiver(this) }
private val mRequestPermissionsResultListener: PluginRegistry.RequestPermissionsResultListener by lazy { MyRequestPermissionResultListener(this) }
private val mActivityResultListener: PluginRegistry.ActivityResultListener by lazy { MyActivityResultListener(this) }
private lateinit var mBinding: ActivityPluginBinding
init {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(mBroadcastReceiver, filter)
}
val activity: Activity get() = mBinding.activity
fun onAttachedToActivity(binding: ActivityPluginBinding) {
binding.addRequestPermissionsResultListener(mRequestPermissionsResultListener)
binding.addActivityResultListener(mActivityResultListener)
mBinding = binding
}
fun onDetachedFromActivity() {
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
mBinding.removeActivityResultListener(mActivityResultListener)
}
abstract fun onReceive(context: Context, intent: Intent)
abstract fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean
abstract fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.content.BroadcastReceiver
import android.content.Context

View File

@ -1,10 +1,13 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.BluetoothLeScanner
@ -13,47 +16,46 @@ import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.ParcelUuid
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import io.flutter.plugin.common.BinaryMessenger
import java.util.concurrent.Executor
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
MyBluetoothLowEnergyManager(context), MyCentralManagerHostApi {
companion object {
const val REQUEST_CODE = 443
}
class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyCentralManagerHostAPI {
private val mAPI: MyCentralManagerFlutterAPI
private val mContext: Context
private val mApi: MyCentralManagerFlutterApi
private val mScanCallback: ScanCallback by lazy {
MyScanCallback(this)
}
private val mBluetoothGattCallback: BluetoothGattCallback by lazy {
MyBluetoothGattCallback(this, executor)
}
private val mScanCallback: ScanCallback by lazy { MyScanCallback(this) }
private val mBluetoothGattCallback: BluetoothGattCallback by lazy { MyBluetoothGattCallback(this, executor) }
private var mDiscovering: Boolean
private val mDevices: MutableMap<String, BluetoothDevice>
private val mGATTs: MutableMap<String, BluetoothGatt>
private val mCharacteristics: MutableMap<String, Map<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, Map<Long, BluetoothGattDescriptor>>
private val mCharacteristics: MutableMap<String, MutableMap<Long, BluetoothGattCharacteristic>>
private val mDescriptors: MutableMap<String, MutableMap<Long, BluetoothGattDescriptor>>
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
private var mShowAppSettingsCallback: ((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>
private val mRequestMtuCallbacks: MutableMap<String, (Result<Long>) -> Unit>
private val mReadRssiCallbacks: MutableMap<String, (Result<Long>) -> Unit>
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGattServiceArgs>>) -> Unit>
private val mDiscoverServicesCallbacks: MutableMap<String, (Result<List<MyGATTServiceArgs>>) -> Unit>
private val mReadCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteCharacteristicCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
private val mReadDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<ByteArray>) -> Unit>>
private val mWriteDescriptorCallbacks: MutableMap<String, MutableMap<Long, (Result<Unit>) -> Unit>>
init {
mContext = context
mApi = MyCentralManagerFlutterApi(binaryMessenger)
mAPI = MyCentralManagerFlutterAPI(binaryMessenger)
mDiscovering = false
@ -62,7 +64,8 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mSetUpCallback = null
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks = mutableMapOf()
mDisconnectCallbacks = mutableMapOf()
@ -75,56 +78,91 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
mWriteDescriptorCallbacks = mutableMapOf()
}
private val mScanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
override val permissions: Array<String>
private val permissions: Array<String>
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_CONNECT
)
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
)
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
private val adapter get() = manager.adapter as BluetoothAdapter
private val scanner: BluetoothLeScanner get() = adapter.bluetoothLeScanner
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
override fun initialize(): MyCentralManagerArgs {
if (mDiscovering) {
stopDiscovery()
}
override val requestCode: Int
get() = REQUEST_CODE
for (gatt in mGATTs.values) {
gatt.disconnect()
}
override fun setUp(callback: (Result<Unit>) -> Unit) {
mDevices.clear()
mGATTs.clear()
mCharacteristics.clear()
mDescriptors.clear()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks.clear()
mDisconnectCallbacks.clear()
mRequestMtuCallbacks.clear()
mReadRssiCallbacks.clear()
mDiscoverServicesCallbacks.clear()
mReadCharacteristicCallbacks.clear()
mWriteCharacteristicCallbacks.clear()
mReadDescriptorCallbacks.clear()
mWriteDescriptorCallbacks.clear()
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
return MyCentralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
}
override fun getState(): MyBluetoothLowEnergyStateArgs {
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
return if (supported) {
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
}
override fun authorize(callback: (Result<Boolean>) -> 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))
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
mAuthorizeCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun startDiscovery(callback: (Result<Unit>) -> Unit) {
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
try {
val filters = emptyList<ScanFilter>()
val settings =
ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
mScanner.startScan(filters, settings, mScanCallback)
executor.execute {
onScanSucceed()
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
val options = ActivityOptionsCompat.makeBasic().toBundle()
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
mShowAppSettingsCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun startDiscovery(serviceUUIDsArgs: List<String>, callback: (Result<Unit>) -> Unit) {
try {
val filters = mutableListOf<ScanFilter>()
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
val filter = ScanFilter.Builder().setServiceUuid(serviceUUID).build()
filters.add(filter)
}
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
scanner.startScan(filters, settings, mScanCallback)
executor.execute { onScanSucceeded() }
mStartDiscoveryCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
@ -132,20 +170,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
override fun stopDiscovery() {
mScanner.stopScan(mScanCallback)
scanner.stopScan(mScanCallback)
mDiscovering = false
}
override fun connect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try {
val device = mDevices[addressArgs] as BluetoothDevice
val autoConnect = false
// Add to bluetoothGATTs cache.
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val autoConnect = false // Add to bluetoothGATTs cache.
mGATTs[addressArgs] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val transport = BluetoothDevice.TRANSPORT_LE
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback, transport)
device.connectGatt(context, autoConnect, mBluetoothGattCallback, transport)
} else {
device.connectGatt(mContext, autoConnect, mBluetoothGattCallback)
device.connectGatt(context, autoConnect, mBluetoothGattCallback)
}
mConnectCallbacks[addressArgs] = callback
} catch (e: Throwable) {
@ -155,7 +192,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun disconnect(addressArgs: String, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
gatt.disconnect()
mDisconnectCallbacks[addressArgs] = callback
} catch (e: Throwable) {
@ -163,9 +200,21 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun retrieveConnectedPeripherals(): List<MyPeripheralArgs> {
// The `BluetoothProfile.GATT` and `BluetoothProfile.GATT_SERVER` return same devices.
val devices = manager.getConnectedDevices(BluetoothProfile.GATT)
val peripheralsArgs = devices.map { device ->
val peripheralArgs = device.toPeripheralArgs()
val addressArgs = peripheralArgs.addressArgs
mDevices[addressArgs] = device
return@map peripheralArgs
}
return peripheralsArgs
}
override fun requestMTU(addressArgs: String, mtuArgs: Long, callback: (Result<Long>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val mtu = mtuArgs.toInt()
val requesting = gatt.requestMtu(mtu)
if (!requesting) {
@ -179,7 +228,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
override fun readRSSI(addressArgs: String, callback: (Result<Long>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val reading = gatt.readRemoteRssi()
if (!reading) {
throw IllegalStateException()
@ -190,11 +239,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun discoverServices(
addressArgs: String, callback: (Result<List<MyGattServiceArgs>>) -> Unit
) {
override fun discoverGATT(addressArgs: String, callback: (Result<List<MyGATTServiceArgs>>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val discovering = gatt.discoverServices()
if (!discovering) {
throw IllegalStateException()
@ -205,13 +252,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun readCharacteristic(
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
override fun readCharacteristic(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
val reading = gatt.readCharacteristic(characteristic)
if (!reading) {
throw IllegalStateException()
@ -223,26 +267,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun writeCharacteristic(
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
typeNumberArgs: Long,
callback: (Result<Unit>) -> Unit
) {
override fun writeCharacteristic(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, typeArgs: MyGATTCharacteristicWriteTypeArgs, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val typeRawArgs = typeNumberArgs.toInt()
val typeArgs = MyGattCharacteristicWriteTypeArgs.ofRaw(typeRawArgs)
?: throw IllegalArgumentException()
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
val type = typeArgs.toType()
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeCharacteristic(characteristic, valueArgs, type)
code == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
} else { // TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
characteristic.writeType = type
gatt.writeCharacteristic(characteristic)
@ -257,47 +290,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun setCharacteristicNotifyState(
addressArgs: String,
hashCodeArgs: Long,
stateNumberArgs: Long,
callback: (Result<Unit>) -> Unit
) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val characteristic =
mRetrieveCharacteristic(addressArgs, hashCodeArgs) as BluetoothGattCharacteristic
val stateRawArgs = stateNumberArgs.toInt()
val stateArgs = MyGattCharacteristicNotifyStateArgs.ofRaw(stateRawArgs)
?: throw IllegalArgumentException()
val enable = stateArgs != MyGattCharacteristicNotifyStateArgs.NONE
val notifying = gatt.setCharacteristicNotification(characteristic, enable)
if (!notifying) {
throw IllegalStateException()
}
// TODO: Seems the docs is not correct, this operation is necessary for all characteristics.
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
// This is specific to Heart Rate Measurement.
//if (characteristic.uuid == UUID_HEART_RATE_MEASUREMENT) {
val cccDescriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID)
val cccHashCodeArgs = cccDescriptor.hashCode().toLong()
val valueArgs = stateArgs.toValue()
writeDescriptor(addressArgs, cccHashCodeArgs, valueArgs, callback)
//} else {
// callback(Result.success(Unit))
//}
} catch (e: Throwable) {
callback(Result.failure(e))
override fun setCharacteristicNotification(addressArgs: String, hashCodeArgs: Long, enableArgs: Boolean) {
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val characteristic = retrieveCharacteristic(addressArgs, hashCodeArgs)
val notifying = gatt.setCharacteristicNotification(characteristic, enableArgs)
if (!notifying) {
throw IllegalStateException()
}
}
override fun readDescriptor(
addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit
) {
override fun readDescriptor(addressArgs: String, hashCodeArgs: Long, callback: (Result<ByteArray>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val descriptor =
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
val reading = gatt.readDescriptor(descriptor)
if (!reading) {
throw IllegalStateException()
@ -309,21 +314,14 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
override fun writeDescriptor(
addressArgs: String,
hashCodeArgs: Long,
valueArgs: ByteArray,
callback: (Result<Unit>) -> Unit
) {
override fun writeDescriptor(addressArgs: String, hashCodeArgs: Long, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val gatt = mGATTs[addressArgs] as BluetoothGatt
val descriptor =
mRetrieveDescriptor(addressArgs, hashCodeArgs) as BluetoothGattDescriptor
val gatt = mGATTs[addressArgs] ?: throw IllegalArgumentException()
val descriptor = retrieveDescriptor(addressArgs, hashCodeArgs)
val writing = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val code = gatt.writeDescriptor(descriptor, valueArgs)
code == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
} else { // TODO: remove this when minSdkVersion >= 33
descriptor.value = valueArgs
gatt.writeDescriptor(descriptor)
}
@ -337,22 +335,37 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
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) {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mOnStateChanged(stateArgs)
mAPI.onStateChanged(stateArgs) {}
}
private fun onScanSucceed() {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
if (requestCode != AUTHORIZE_CODE) {
return false
}
val callback = mAuthorizeCallback ?: return false
mAuthorizeCallback = null
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
callback(Result.success(authorized))
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != SHOW_APP_SETTINGS_CODE) {
return false
}
val callback = mShowAppSettingsCallback ?: return false
mShowAppSettingsCallback = null
callback(Result.success(Unit))
return true
}
private fun onScanSucceeded() {
mDiscovering = true
val callback = mStartDiscoveryCallback ?: return
mStartDiscoveryCallback = null
@ -370,16 +383,15 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val device = result.device
val peripheralArgs = device.toPeripheralArgs()
val addressArgs = peripheralArgs.addressArgs
val rssiArgs = result.rssi.toLong()
val rssiArgs = result.rssi.args
val advertisementArgs = result.toAdvertisementArgs()
mDevices[addressArgs] = device
mApi.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
mAPI.onDiscovered(peripheralArgs, rssiArgs, advertisementArgs) {}
}
fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val device = gatt.device
val addressArgs = device.address
// check connection state.
val addressArgs = device.address // check connection state.
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
gatt.close()
mGATTs.remove(addressArgs)
@ -427,9 +439,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
}
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
mApi.onConnectionStateChanged(addressArgs, stateArgs) {}
// check connect & disconnect callbacks.
// check connect callback.
val connectCallback = mConnectCallbacks.remove(addressArgs)
if (connectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -439,6 +449,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
connectCallback(Result.failure(error))
}
}
// check disconnect callback.
val disconnectCallback = mDisconnectCallbacks.remove(addressArgs)
if (disconnectCallback != null) {
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -448,14 +459,19 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
disconnectCallback(Result.failure(error))
}
}
// invoke connection state changed event.
val peripheralArgs = device.toPeripheralArgs()
val stateArgs = newState.toConnectionStateArgs()
mAPI.onConnectionStateChanged(peripheralArgs, stateArgs) {}
}
fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
val device = gatt.device
val addressArgs = device.address
val result = if (status == BluetoothGatt.GATT_SUCCESS) {
val mtuArgs = mtu.toLong()
mApi.onMtuChanged(addressArgs, mtuArgs) {}
val peripheralArgs = device.toPeripheralArgs()
val mtuArgs = mtu.args
mAPI.onMTUChanged(peripheralArgs, mtuArgs) {}
Result.success(mtuArgs)
} else {
val error = IllegalStateException("Read RSSI failed with status: $status")
@ -470,7 +486,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val addressArgs = device.address
val callback = mReadRssiCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val rssiArgs = rssi.toLong()
val rssiArgs = rssi.args
callback(Result.success(rssiArgs))
} else {
val error = IllegalStateException("Read RSSI failed with status: $status")
@ -484,10 +500,9 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
val callback = mDiscoverServicesCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
val services = gatt.services
val characteristics = services.flatMap { it.characteristics }
val descriptors = characteristics.flatMap { it.descriptors }
mCharacteristics[addressArgs] = characteristics.associateBy { it.hashCode().toLong() }
mDescriptors[addressArgs] = descriptors.associateBy { it.hashCode().toLong() }
for (service in services) {
addService(addressArgs, service)
}
val servicesArgs = services.map { it.toArgs() }
callback(Result.success(servicesArgs))
} else {
@ -496,15 +511,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int,
value: ByteArray
) {
fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
val hashCodeArgs = characteristic.hashCode.args
val callbacks = mReadCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -515,12 +525,10 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicWrite(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
) {
fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
val hashCodeArgs = characteristic.hashCode.args
val callbacks = mWriteCharacteristicCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -531,21 +539,17 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
fun onCharacteristicChanged(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = characteristic.hashCode().toLong()
mApi.onCharacteristicNotified(addressArgs, hashCodeArgs, value) {}
val peripheralArgs = device.toPeripheralArgs()
val characteristicArgs = characteristic.toArgs()
mAPI.onCharacteristicNotified(peripheralArgs, characteristicArgs, value) {}
}
fun onDescriptorRead(
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray
) {
fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong()
val hashCodeArgs = descriptor.hashCode.args
val callbacks = mReadDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -559,7 +563,7 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
val device = gatt.device
val addressArgs = device.address
val hashCodeArgs = descriptor.hashCode().toLong()
val hashCodeArgs = descriptor.hashCode.args
val callbacks = mWriteDescriptorCallbacks[addressArgs] ?: return
val callback = callbacks.remove(hashCodeArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
@ -570,47 +574,28 @@ class MyCentralManager(context: Context, binaryMessenger: BinaryMessenger) :
}
}
private fun mClearState() {
if (mDiscovering) {
stopDiscovery()
private fun addService(addressArgs: String, service: BluetoothGattService) {
val includedServices = service.includedServices
for (includedService in includedServices) {
addService(addressArgs, includedService)
}
for (gatt in mGATTs.values) {
gatt.disconnect()
for (characteristic in service.characteristics) {
for (descriptor in characteristic.descriptors) {
val descriptors = mDescriptors.getOrPut(addressArgs) { mutableMapOf() }
descriptors[descriptor.hashCode.args] = descriptor
}
val characteristics = mCharacteristics.getOrPut(addressArgs) { mutableMapOf() }
characteristics[characteristic.hashCode.args] = characteristic
}
mDevices.clear()
mGATTs.clear()
mCharacteristics.clear()
mDescriptors.clear()
mSetUpCallback = null
mStartDiscoveryCallback = null
mConnectCallbacks.clear()
mDisconnectCallbacks.clear()
mRequestMtuCallbacks.clear()
mReadRssiCallbacks.clear()
mDiscoverServicesCallbacks.clear()
mReadCharacteristicCallbacks.clear()
mWriteCharacteristicCallbacks.clear()
mReadDescriptorCallbacks.clear()
mWriteDescriptorCallbacks.clear()
}
private fun mOnStateChanged(stateArgs: MyBluetoothLowEnergyStateArgs) {
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onStateChanged(stateNumberArgs) {}
private fun retrieveCharacteristic(addressArgs: String, hashCodeArgs: Long): BluetoothGattCharacteristic {
val characteristics = mCharacteristics[addressArgs] ?: throw IllegalArgumentException()
return characteristics[hashCodeArgs] ?: throw IllegalArgumentException()
}
private fun mRetrieveCharacteristic(
addressArgs: String, hashCodeArgs: Long
): BluetoothGattCharacteristic? {
val characteristics = mCharacteristics[addressArgs] ?: return null
return characteristics[hashCodeArgs]
}
private fun mRetrieveDescriptor(
addressArgs: String, hashCodeArgs: Long
): BluetoothGattDescriptor? {
val descriptors = mDescriptors[addressArgs] ?: return null
return descriptors[hashCodeArgs]
private fun retrieveDescriptor(addressArgs: String, hashCodeArgs: Long): BluetoothGattDescriptor {
val descriptors = mDescriptors[addressArgs] ?: throw IllegalArgumentException()
return descriptors[hashCodeArgs] ?: throw IllegalArgumentException()
}
}

View File

@ -0,0 +1,446 @@
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import io.flutter.plugin.common.BinaryMessenger
import java.util.concurrent.Executor
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) : MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostAPI {
private val mAPI: MyPeripheralManagerFlutterAPI
private val mBluetoothGattServerCallback: BluetoothGattServerCallback by lazy { MyBluetoothGattServerCallback(this, executor) }
private val mAdvertiseCallback: AdvertiseCallback by lazy { MyAdvertiseCallback(this) }
private var mServer: BluetoothGattServer?
private var mAdvertising: Boolean
private val mServicesArgs: MutableMap<Int, MyMutableGATTServiceArgs>
private val mCharacteristicsArgs: MutableMap<Int, MyMutableGATTCharacteristicArgs>
private val mDescriptorsArgs: MutableMap<Int, MyMutableGATTDescriptorArgs>
private val mDevices: MutableMap<String, BluetoothDevice>
private val mServices: MutableMap<Long, BluetoothGattService>
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
private var mAuthorizeCallback: ((Result<Boolean>) -> Unit)?
private var mShowAppSettingsCallback: ((Result<Unit>) -> Unit)?
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
init {
mAPI = MyPeripheralManagerFlutterAPI(binaryMessenger)
mServer = null
mAdvertising = false
mServicesArgs = mutableMapOf()
mCharacteristicsArgs = mutableMapOf()
mDescriptorsArgs = mutableMapOf()
mDevices = mutableMapOf()
mServices = mutableMapOf()
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
}
private val permissions: Array<String>
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT)
} else {
arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
}
private val manager get() = ContextCompat.getSystemService(context, BluetoothManager::class.java) as BluetoothManager
private val adapter get() = manager.adapter as BluetoothAdapter
private val advertiser get() = adapter.bluetoothLeAdvertiser
private val server get() = mServer ?: throw IllegalStateException()
private val executor get() = ContextCompat.getMainExecutor(context) as Executor
override fun initialize(): MyPeripheralManagerArgs {
if (mAdvertising) {
stopAdvertising()
}
mServer?.close()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
mDevices.clear()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mAuthorizeCallback = null
mShowAppSettingsCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks.clear()
val enableNotificationValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val enableIndicationValue = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
val disableNotificationValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
return MyPeripheralManagerArgs(enableNotificationValue, enableIndicationValue, disableNotificationValue)
}
override fun getState(): MyBluetoothLowEnergyStateArgs {
val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
return if (supported) {
val authorized = permissions.all { permission -> ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED }
return if (authorized) adapter.state.toBluetoothLowEnergyStateArgs()
else MyBluetoothLowEnergyStateArgs.UNAUTHORIZED
} else MyBluetoothLowEnergyStateArgs.UNSUPPORTED
}
override fun authorize(callback: (Result<Boolean>) -> Unit) {
try {
ActivityCompat.requestPermissions(activity, permissions, AUTHORIZE_CODE)
mAuthorizeCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun showAppSettings(callback: (Result<Unit>) -> Unit) {
try {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
val options = ActivityOptionsCompat.makeBasic().toBundle()
ActivityCompat.startActivityForResult(activity, intent, SHOW_APP_SETTINGS_CODE, options)
mShowAppSettingsCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun openGATTServer() {
mServer = manager.openGattServer(context, mBluetoothGattServerCallback)
}
override fun closeGATTServer() {
server.close()
}
override fun addService(serviceArgs: MyMutableGATTServiceArgs, callback: (Result<Unit>) -> Unit) {
try {
val service = addServiceArgs(serviceArgs)
val adding = server.addService(service)
if (!adding) {
throw IllegalStateException()
}
mAddServiceCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun removeService(hashCodeArgs: Long) {
val service = mServices[hashCodeArgs] ?: throw IllegalArgumentException()
val removed = server.removeService(service)
if (!removed) {
throw IllegalStateException()
}
val hashCode = service.hashCode()
val serviceArgs = mServicesArgs[hashCode] ?: throw IllegalArgumentException()
removeServiceArgs(serviceArgs)
}
override fun removeAllServices() {
server.clearServices()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
}
override fun startAdvertising(advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit) {
try {
val settings = AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).build()
// TODO: There is an issue that Android will use the cached name before setName takes effect.
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
val nameArgs = advertisementArgs.nameArgs
if (nameArgs != null) {
adapter.name = nameArgs
}
val advertiseData = advertisementArgs.toAdvertiseData()
val scanResponse = advertisementArgs.toScanResponse()
advertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback)
mStartAdvertisingCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopAdvertising() {
advertiser.stopAdvertising(mAdvertiseCallback)
mAdvertising = false
}
override fun sendResponse(addressArgs: String, idArgs: Long, statusArgs: MyGATTStatusArgs, offsetArgs: Long, valueArgs: ByteArray?) {
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val requestId = idArgs.toInt()
val status = statusArgs.toStatus()
val offset = offsetArgs.toInt()
val sent = server.sendResponse(device, requestId, status, offset, valueArgs)
if (!sent) {
throw IllegalStateException("Send response failed.")
}
}
override fun notifyCharacteristicChanged(addressArgs: String, hashCodeArgs: Long, confirmArgs: Boolean, valueArgs: ByteArray, callback: (Result<Unit>) -> Unit) {
try {
val device = mDevices[addressArgs] ?: throw IllegalArgumentException()
val characteristic = mCharacteristics[hashCodeArgs] ?: throw IllegalArgumentException()
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val statusCode = server.notifyCharacteristicChanged(device, characteristic, confirmArgs, valueArgs)
statusCode == BluetoothStatusCodes.SUCCESS
} else { // TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
server.notifyCharacteristicChanged(device, characteristic, confirmArgs)
}
if (!notifying) {
throw IllegalStateException()
}
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
val stateArgs = state.toBluetoothLowEnergyStateArgs()
mAPI.onStateChanged(stateArgs) {}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
if (requestCode != AUTHORIZE_CODE) {
return false
}
val callback = mAuthorizeCallback ?: return false
mAuthorizeCallback = null
val authorized = permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
callback(Result.success(authorized))
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode != SHOW_APP_SETTINGS_CODE) {
return false
}
val callback = mShowAppSettingsCallback ?: return false
mShowAppSettingsCallback = null
callback(Result.success(Unit))
return true
}
fun onServiceAdded(status: Int, service: BluetoothGattService) {
val callback = mAddServiceCallback ?: return
mAddServiceCallback = null
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
mAdvertising = true
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
callback(Result.success(Unit))
}
fun onStartFailure(errorCode: Int) {
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
val centralArgs = device.toCentralArgs()
val addressArgs = centralArgs.addressArgs
val statusArgs = status.args
val stateArgs = newState.toConnectionStateArgs()
mDevices[addressArgs] = device
mAPI.onConnectionStateChanged(centralArgs, statusArgs, stateArgs) {}
}
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
val centralArgs = device.toCentralArgs()
val mtuArgs = mtu.args
mAPI.onMTUChanged(centralArgs, mtuArgs) {}
}
fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val offsetArgs = offset.args
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode]
if (characteristicArgs == null) {
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = characteristicArgs.hashCodeArgs
mAPI.onCharacteristicReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
}
}
fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode]
if (characteristicArgs == null) {
if (!responseNeeded) {
return
}
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = characteristicArgs.hashCodeArgs
val offsetArgs = offset.args
mAPI.onCharacteristicWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
}
}
fun onNotificationSent(device: BluetoothDevice, status: Int) {
val addressArgs = device.address
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Notify characteristic value changed failed with status: $status")
callback(Result.failure(error))
}
}
fun onDescriptorReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val offsetArgs = offset.args
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode]
if (descriptorArgs == null) {
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = descriptorArgs.hashCodeArgs
mAPI.onDescriptorReadRequest(centralArgs, idArgs, offsetArgs, hashCodeArgs) {}
}
}
fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode]
if (descriptorArgs == null) {
if (!responseNeeded) {
return
}
val status = BluetoothGatt.GATT_FAILURE
server.sendResponse(device, requestId, status, offset, null)
} else {
val hashCodeArgs = descriptorArgs.hashCodeArgs
val offsetArgs = offset.args
mAPI.onDescriptorWriteRequest(centralArgs, idArgs, hashCodeArgs, preparedWrite, responseNeeded, offsetArgs, value) {}
}
}
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
val centralArgs = device.toCentralArgs()
val idArgs = requestId.args
mAPI.onExecuteWrite(centralArgs, idArgs, execute) {}
}
private fun addServiceArgs(serviceArgs: MyMutableGATTServiceArgs): BluetoothGattService {
val service = serviceArgs.toService()
this.mServicesArgs[service.hashCode] = serviceArgs
this.mServices[serviceArgs.hashCodeArgs] = service
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
for (includedServiceArgs in includedServicesArgs) {
val includedService = addServiceArgs(includedServiceArgs)
val adding = service.addService(includedService)
if (!adding) {
throw IllegalStateException()
}
}
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
for (characteristicArgs in characteristicsArgs) {
val characteristic = characteristicArgs.toCharacteristic()
this.mCharacteristicsArgs[characteristic.hashCode] = characteristicArgs
this.mCharacteristics[characteristicArgs.hashCodeArgs] = characteristic
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
for (descriptorArgs in descriptorsArgs) {
val descriptor = descriptorArgs.toDescriptor()
this.mDescriptorsArgs[descriptor.hashCode] = descriptorArgs
this.mDescriptors[descriptorArgs.hashCodeArgs] = descriptor
val descriptorAdded = characteristic.addDescriptor(descriptor)
if (!descriptorAdded) {
throw IllegalStateException()
}
}
val characteristicAdded = service.addCharacteristic(characteristic)
if (!characteristicAdded) {
throw IllegalStateException()
}
}
return service
}
private fun removeServiceArgs(serviceArgs: MyMutableGATTServiceArgs) {
val includedServicesArgs = serviceArgs.includedServicesArgs.requireNoNulls()
for (includedServiceArgs in includedServicesArgs) {
removeServiceArgs(includedServiceArgs)
}
val characteristicsArgs = serviceArgs.characteristicsArgs.requireNoNulls()
for (characteristicArgs in characteristicsArgs) {
val descriptorsArgs = characteristicArgs.descriptorsArgs.requireNoNulls()
for (descriptorArgs in descriptorsArgs) {
val descriptor = mDescriptors.remove(descriptorArgs.hashCodeArgs) ?: throw IllegalArgumentException()
this.mDescriptorsArgs.remove(descriptor.hashCode)
}
val characteristic = mCharacteristics.remove(characteristicArgs.hashCodeArgs) ?: throw IllegalArgumentException()
this.mCharacteristicsArgs.remove(characteristic.hashCode)
}
val service = mServices.remove(serviceArgs.hashCodeArgs) ?: throw IllegalArgumentException()
mServicesArgs.remove(service.hashCode)
}
}

View File

@ -0,0 +1,15 @@
package dev.hebei.bluetooth_low_energy_android
import io.flutter.plugin.common.PluginRegistry
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) : PluginRegistry.RequestPermissionsResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, results: IntArray): Boolean {
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
}
}

View File

@ -1,4 +1,4 @@
package dev.yanshouwang.bluetooth_low_energy_android
package dev.hebei.bluetooth_low_energy_android
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult

View File

@ -1,270 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import android.util.SparseArray
import java.util.UUID
//region ToObj
fun MyGattCharacteristicWriteTypeArgs.toType(): Int {
return when (this) {
MyGattCharacteristicWriteTypeArgs.WITHRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
MyGattCharacteristicWriteTypeArgs.WITHOUTRESPONSE -> BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
}
}
fun MyGattCharacteristicNotifyStateArgs.toValue(): ByteArray {
return when (this) {
MyGattCharacteristicNotifyStateArgs.NONE -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
MyGattCharacteristicNotifyStateArgs.NOTIFY -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
MyGattCharacteristicNotifyStateArgs.INDICATE -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
}
}
fun MyGattStatusArgs.toStatus(): Int {
return when (this) {
MyGattStatusArgs.SUCCESS -> BluetoothGatt.GATT_SUCCESS
MyGattStatusArgs.READNOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.WRITENOTPERMITTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.REQUESTNOTSUPPORTED -> BluetoothGatt.GATT_READ_NOT_PERMITTED
MyGattStatusArgs.INVALIDOFFSET -> BluetoothGatt.GATT_INVALID_OFFSET
MyGattStatusArgs.INSUFFICIENTAUTHENTICATION -> BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION
MyGattStatusArgs.INSUFFICIENTENCRYPTION -> BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION
MyGattStatusArgs.INVALIDATTRIBUTELENGTH -> BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH
MyGattStatusArgs.CONNECTIONCONGESTED -> BluetoothGatt.GATT_CONNECTION_CONGESTED
MyGattStatusArgs.FAILURE -> BluetoothGatt.GATT_FAILURE
}
}
fun MyAdvertisementArgs.toAdvertiseData(adapter: BluetoothAdapter): AdvertiseData {
val advertiseDataBuilder = AdvertiseData.Builder()
if (nameArgs == null) {
advertiseDataBuilder.setIncludeDeviceName(false)
} else {
// TODO: There is an issue that Android will use the cached name before setName takes effect.
// see https://stackoverflow.com/questions/8377558/change-the-android-bluetooth-device-name
adapter.name = nameArgs
advertiseDataBuilder.setIncludeDeviceName(true)
}
for (serviceUuidArgs in serviceUUIDsArgs) {
val serviceUUID = ParcelUuid.fromString(serviceUuidArgs)
advertiseDataBuilder.addServiceUuid(serviceUUID)
}
for (entry in serviceDataArgs) {
val serviceDataUUID = ParcelUuid.fromString(entry.key as String)
val serviceData = entry.value as ByteArray
advertiseDataBuilder.addServiceData(serviceDataUUID, serviceData)
}
if (manufacturerSpecificDataArgs != null) {
val manufacturerId = manufacturerSpecificDataArgs.idArgs.toInt()
val manufacturerSpecificData = manufacturerSpecificDataArgs.dataArgs
advertiseDataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData)
}
return advertiseDataBuilder.build()
}
fun MyGattDescriptorArgs.toDescriptor(): BluetoothGattDescriptor {
val uuid = UUID.fromString(uuidArgs)
val permissions =
BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE
return BluetoothGattDescriptor(uuid, permissions)
}
fun MyGattCharacteristicArgs.toCharacteristic(): BluetoothGattCharacteristic {
val uuid = UUID.fromString(uuidArgs)
val properties = getProperties()
val permissions = getPermissions()
return BluetoothGattCharacteristic(uuid, properties, permissions)
}
fun MyGattCharacteristicArgs.getProperties(): Int {
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
val raw = args.toInt()
MyGattCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse =
propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
val notify = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.NOTIFY)
val indicate = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.INDICATE)
var properties = 0
if (read) properties = properties or BluetoothGattCharacteristic.PROPERTY_READ
if (write) properties = properties or BluetoothGattCharacteristic.PROPERTY_WRITE
if (writeWithoutResponse) properties =
properties or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
if (notify) properties = properties or BluetoothGattCharacteristic.PROPERTY_NOTIFY
if (indicate) properties = properties or BluetoothGattCharacteristic.PROPERTY_INDICATE
return properties
}
fun MyGattCharacteristicArgs.getPermissions(): Int {
val propertiesArgs = propertyNumbersArgs.filterNotNull().map { args ->
val raw = args.toInt()
MyGattCharacteristicPropertyArgs.ofRaw(raw)
}
val read = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.READ)
val write = propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITE)
val writeWithoutResponse =
propertiesArgs.contains(MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE)
var permissions = 0
if (read) permissions = permissions or BluetoothGattCharacteristic.PERMISSION_READ
if (write || writeWithoutResponse) permissions =
permissions or BluetoothGattCharacteristic.PERMISSION_WRITE
return permissions
}
fun MyGattServiceArgs.toService(): BluetoothGattService {
val uuid = UUID.fromString(uuidArgs)
val serviceType = BluetoothGattService.SERVICE_TYPE_PRIMARY
return BluetoothGattService(uuid, serviceType)
}
//endregion
//region ToArgs
fun Int.toBluetoothLowEnergyStateArgs(): MyBluetoothLowEnergyStateArgs {
return when (this) {
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
}
}
fun SparseArray<ByteArray>.toManufacturerSpecificDataArgs(): MyManufacturerSpecificDataArgs? {
var index = 0
val size = size()
val itemsArgs = mutableListOf<MyManufacturerSpecificDataArgs>()
while (index < size) {
val idArgs = keyAt(index).toLong()
val dataArgs = valueAt(index)
val itemArgs = MyManufacturerSpecificDataArgs(idArgs, dataArgs)
itemsArgs.add(itemArgs)
index++
}
return itemsArgs.lastOrNull()
}
fun ScanResult.toAdvertisementArgs(): MyAdvertisementArgs {
val record = scanRecord
return if (record == null) {
val nameArgs = null
val serviceUUIDsArgs = emptyList<String?>()
val serviceDataArgs = emptyMap<String?, ByteArray>()
val manufacturerSpecificDataArgs = null
MyAdvertisementArgs(
nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs
)
} else {
val nameArgs = record.deviceName
val serviceUUIDsArgs = record.serviceUuids?.map { uuid -> uuid.toString() } ?: emptyList()
val pairs = record.serviceData?.map { (uuid, value) ->
val key = uuid.toString()
return@map Pair(key, value)
}?.toTypedArray() ?: emptyArray()
val serviceDataArgs = mapOf<String?, ByteArray?>(*pairs)
val manufacturerSpecificDataArgs =
record.manufacturerSpecificData?.toManufacturerSpecificDataArgs()
MyAdvertisementArgs(
nameArgs, serviceUUIDsArgs, serviceDataArgs, manufacturerSpecificDataArgs
)
}
}
fun BluetoothDevice.toCentralArgs(): MyCentralArgs {
val addressArgs = address
return MyCentralArgs(addressArgs)
}
fun BluetoothDevice.toPeripheralArgs(): MyPeripheralArgs {
val addressArgs = address
return MyPeripheralArgs(addressArgs)
}
fun BluetoothGattService.toArgs(): MyGattServiceArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val characteristicsArgs = characteristics.map { it.toArgs() }
return MyGattServiceArgs(hashCodeArgs, uuidArgs, characteristicsArgs)
}
fun BluetoothGattCharacteristic.toArgs(): MyGattCharacteristicArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
val propertyNumbersArgs = getPropertyNumbersArgs()
val descriptorsArgs = descriptors.map { it.toArgs() }
return MyGattCharacteristicArgs(hashCodeArgs, uuidArgs, propertyNumbersArgs, descriptorsArgs)
}
fun BluetoothGattCharacteristic.getPropertyNumbersArgs(): List<Long> {
val numbersArgs = mutableListOf<Long>()
if (properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
val number = MyGattCharacteristicPropertyArgs.READ.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {
val number = MyGattCharacteristicPropertyArgs.WRITEWITHOUTRESPONSE.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {
val number = MyGattCharacteristicPropertyArgs.NOTIFY.raw.toLong()
numbersArgs.add(number)
}
if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {
val number = MyGattCharacteristicPropertyArgs.INDICATE.raw.toLong()
numbersArgs.add(number)
}
return numbersArgs
}
fun BluetoothGattDescriptor.toArgs(): MyGattDescriptorArgs {
val hashCodeArgs = hashCode().toLong()
val uuidArgs = this.uuid.toString()
return MyGattDescriptorArgs(hashCodeArgs, uuidArgs, null)
}
fun ByteArray.toNotifyStateArgs(): MyGattCharacteristicNotifyStateArgs {
return if (this contentEquals BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.NOTIFY
} else if (this contentEquals BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.INDICATE
} else if (this contentEquals BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
MyGattCharacteristicNotifyStateArgs.NONE
} else {
throw IllegalArgumentException()
}
}
//endregion
val Any.TAG get() = this::class.java.simpleName as String
//val ScanRecord.rawValues: Map<Byte, ByteArray>
// get() {
// val rawValues = mutableMapOf<Byte, ByteArray>()
// var index = 0
// val size = bytes.size
// while (index < size) {
// val length = bytes[index++].toInt() and 0xff
// if (length == 0) {
// break
// }
// val end = index + length
// val type = bytes[index++]
// val value = bytes.slice(index until end).toByteArray()
// rawValues[type] = value
// index = end
// }
// return rawValues.toMap()
// }

View File

@ -1,106 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
import java.util.UUID
import java.util.concurrent.Executor
abstract class MyBluetoothLowEnergyManager(context: Context) {
companion object {
val CLIENT_CHARACTERISTIC_CONFIG_UUID =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") as UUID
}
private val mContext: Context
private val mRequestPermissionsResultListener: RequestPermissionsResultListener by lazy {
MyRequestPermissionResultListener(this)
}
private val mBroadcastReceiver: BroadcastReceiver by lazy {
MyBroadcastReceiver(this)
}
private var mRegistered: Boolean
private lateinit var mBinding: ActivityPluginBinding
init {
mContext = context
mRegistered = false
}
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 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
}
fun onDetachedFromActivity() {
mBinding.removeRequestPermissionsResultListener(mRequestPermissionsResultListener)
}
fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, results: IntArray
): Boolean {
if (this.requestCode != requestCode) {
return false
}
val granted =
permissions.contentEquals(this.permissions) && results.all { r -> r == PackageManager.PERMISSION_GRANTED }
onPermissionsRequested(granted)
return true
}
fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action != BluetoothAdapter.ACTION_STATE_CHANGED) {
return
}
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
onAdapterStateChanged(state)
}
protected fun registerReceiver() {
if (mRegistered) {
return
}
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
mContext.registerReceiver(mBroadcastReceiver, filter)
mRegistered = true
}
abstract val permissions: Array<String>
abstract val requestCode: Int
abstract fun onPermissionsRequested(granted: Boolean)
abstract fun onAdapterStateChanged(state: Int)
}

View File

@ -1,461 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattServer
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.os.Build
import io.flutter.plugin.common.BinaryMessenger
class MyPeripheralManager(context: Context, binaryMessenger: BinaryMessenger) :
MyBluetoothLowEnergyManager(context), MyPeripheralManagerHostApi {
companion object {
const val REQUEST_CODE = 445
}
private val advertiser get() = adapter.bluetoothLeAdvertiser
private val mContext: Context
private val mApi: MyPeripheralManagerFlutterApi
private val bluetoothGattServerCallback: BluetoothGattServerCallback by lazy {
MyBluetoothGattServerCallback(this, executor)
}
private val advertiseCallback: AdvertiseCallback by lazy {
MyAdvertiseCallback(this)
}
private lateinit var mServer: BluetoothGattServer
private var mOpening = false
private var mAdvertising = false
private val mServicesArgs: MutableMap<Int, MyGattServiceArgs>
private val mCharacteristicsArgs: MutableMap<Int, MyGattCharacteristicArgs>
private val mDescriptorsArgs: MutableMap<Int, MyGattDescriptorArgs>
private val mDevices: MutableMap<String, BluetoothDevice>
private val mServices: MutableMap<Long, BluetoothGattService>
private val mCharacteristics: MutableMap<Long, BluetoothGattCharacteristic>
private val mDescriptors: MutableMap<Long, BluetoothGattDescriptor>
private var mSetUpCallback: ((Result<Unit>) -> Unit)?
private var mAddServiceCallback: ((Result<Unit>) -> Unit)?
private var mStartAdvertisingCallback: ((Result<Unit>) -> Unit)?
private val mNotifyCharacteristicValueChangedCallbacks: MutableMap<String, (Result<Unit>) -> Unit>
init {
mContext = context
mApi = MyPeripheralManagerFlutterApi(binaryMessenger)
mServicesArgs = mutableMapOf()
mCharacteristicsArgs = mutableMapOf()
mDescriptorsArgs = mutableMapOf()
mDevices = mutableMapOf()
mServices = mutableMapOf()
mCharacteristics = mutableMapOf()
mDescriptors = mutableMapOf()
mSetUpCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks = mutableMapOf()
}
override val permissions: Array<String>
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.BLUETOOTH_ADVERTISE,
android.Manifest.permission.BLUETOOTH_CONNECT
)
} else {
arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION
)
}
override val requestCode: Int
get() = REQUEST_CODE
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) {
try {
val service = serviceArgs.toService()
val characteristicsArgs = serviceArgs.characteristicsArgs.filterNotNull()
for (characteristicArgs in characteristicsArgs) {
val characteristic = characteristicArgs.toCharacteristic()
val descriptorsArgs = characteristicArgs.descriptorsArgs.filterNotNull()
for (descriptorArgs in descriptorsArgs) {
val descriptor = descriptorArgs.toDescriptor()
val descriptorHashCodeArgs = descriptorArgs.hashCodeArgs
val descriptorHashCode = descriptor.hashCode()
this.mDescriptorsArgs[descriptorHashCode] = descriptorArgs
this.mDescriptors[descriptorHashCodeArgs] = descriptor
val descriptorAdded = characteristic.addDescriptor(descriptor)
if (!descriptorAdded) {
throw IllegalStateException()
}
}
val characteristicHashCodeArgs = characteristicArgs.hashCodeArgs
val characteristicHashCode = characteristic.hashCode()
this.mCharacteristicsArgs[characteristicHashCode] = characteristicArgs
this.mCharacteristics[characteristicHashCodeArgs] = characteristic
val characteristicAdded = service.addCharacteristic(characteristic)
if (!characteristicAdded) {
throw IllegalStateException()
}
}
val serviceHashCodeArgs = serviceArgs.hashCodeArgs
val serviceHashCode = service.hashCode()
this.mServicesArgs[serviceHashCode] = serviceArgs
this.mServices[serviceHashCodeArgs] = service
val adding = mServer.addService(service)
if (!adding) {
throw IllegalStateException()
}
mAddServiceCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun removeService(hashCodeArgs: Long) {
val service = mServices.remove(hashCodeArgs) as BluetoothGattService
val removed = mServer.removeService(service)
if (!removed) {
throw IllegalStateException()
}
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()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
}
override fun startAdvertising(
advertisementArgs: MyAdvertisementArgs, callback: (Result<Unit>) -> Unit
) {
try {
val settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true)
.build()
val advertiseData = advertisementArgs.toAdvertiseData(adapter)
advertiser.startAdvertising(settings, advertiseData, advertiseCallback)
mStartAdvertisingCallback = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
override fun stopAdvertising() {
advertiser.stopAdvertising(advertiseCallback)
mAdvertising = false
}
override fun sendResponse(
addressArgs: String,
idArgs: Long,
statusNumberArgs: Long,
offsetArgs: Long,
valueArgs: ByteArray?
) {
val device = mDevices[addressArgs] as BluetoothDevice
val requestId = idArgs.toInt()
val statusRawArgs = statusNumberArgs.toInt()
val statusArgs = MyGattStatusArgs.ofRaw(statusRawArgs) ?: throw IllegalArgumentException()
val status = statusArgs.toStatus()
val offset = offsetArgs.toInt()
val sent = mServer.sendResponse(device, requestId, status, offset, valueArgs)
if (!sent) {
throw IllegalStateException("Send response failed.")
}
}
override fun notifyCharacteristicChanged(
hashCodeArgs: Long,
valueArgs: ByteArray,
confirmArgs: Boolean,
addressArgs: String,
callback: (Result<Unit>) -> Unit
) {
try {
val device = mDevices[addressArgs] as BluetoothDevice
val characteristic = mCharacteristics[hashCodeArgs] as BluetoothGattCharacteristic
val notifying = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val statusCode = mServer.notifyCharacteristicChanged(
device, characteristic, confirmArgs, valueArgs
)
statusCode == BluetoothStatusCodes.SUCCESS
} else {
// TODO: remove this when minSdkVersion >= 33
characteristic.value = valueArgs
mServer.notifyCharacteristicChanged(device, characteristic, confirmArgs)
}
if (!notifying) {
throw IllegalStateException()
}
mNotifyCharacteristicValueChangedCallbacks[addressArgs] = callback
} catch (e: Throwable) {
callback(Result.failure(e))
}
}
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) {
val callback = mAddServiceCallback ?: return
mAddServiceCallback = null
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error = IllegalStateException("Read rssi failed with status: $status")
callback(Result.failure(error))
}
}
fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
mAdvertising = true
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
callback(Result.success(Unit))
}
fun onStartFailure(errorCode: Int) {
val callback = mStartAdvertisingCallback ?: return
mStartAdvertisingCallback = null
val error = IllegalStateException("Start advertising failed with error code: $errorCode")
callback(Result.failure(error))
}
fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
val centralArgs = device.toCentralArgs()
val addressArgs = centralArgs.addressArgs
val stateArgs = newState == BluetoothProfile.STATE_CONNECTED
mDevices[addressArgs] = device
mApi.onConnectionStateChanged(centralArgs, stateArgs) {}
}
fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
val addressArgs = device.address
val mtuArgs = mtu.toLong()
mApi.onMtuChanged(addressArgs, mtuArgs) {}
}
fun onCharacteristicReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onCharacteristicReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
}
fun onCharacteristicWriteRequest(
device: BluetoothDevice,
requestId: Int,
characteristic: BluetoothGattCharacteristic,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onCharacteristicWriteRequest(
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
) {}
}
fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
val addressArgs = device.address
val idArgs = requestId.toLong()
mApi.onExecuteWrite(addressArgs, idArgs, execute) {}
}
fun onDescriptorReadRequest(
device: BluetoothDevice, requestId: Int, offset: Int, descriptor: BluetoothGattDescriptor
) {
val addressArgs = device.address
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
val hashCodeArgs = descriptorArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onDescriptorReadRequest(addressArgs, hashCodeArgs, idArgs, offsetArgs) {}
}
fun onDescriptorWriteRequest(
device: BluetoothDevice,
requestId: Int,
descriptor: BluetoothGattDescriptor,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = descriptor.hashCode()
val descriptorArgs = mDescriptorsArgs[hashCode] ?: return
val hashCodeArgs = descriptorArgs.hashCodeArgs
val idArgs = requestId.toLong()
val offsetArgs = offset.toLong()
mApi.onDescriptorWriteRequest(
addressArgs, hashCodeArgs, idArgs, offsetArgs, value, preparedWrite, responseNeeded
) {}
if (descriptor.uuid == CLIENT_CHARACTERISTIC_CONFIG_UUID) {
val characteristic = descriptor.characteristic
mOnCharacteristicNotifyStateChanged(device, characteristic, value)
}
}
fun onNotificationSent(device: BluetoothDevice, status: Int) {
val addressArgs = device.address
val callback = mNotifyCharacteristicValueChangedCallbacks.remove(addressArgs) ?: return
if (status == BluetoothGatt.GATT_SUCCESS) {
callback(Result.success(Unit))
} else {
val error =
IllegalStateException("Notify characteristic value changed failed with status: $status")
callback(Result.failure(error))
}
}
private fun mClearState() {
if (mAdvertising) {
stopAdvertising()
}
mServicesArgs.clear()
mCharacteristicsArgs.clear()
mDescriptorsArgs.clear()
mDevices.clear()
mServices.clear()
mCharacteristics.clear()
mDescriptors.clear()
mSetUpCallback = null
mAddServiceCallback = null
mStartAdvertisingCallback = null
mNotifyCharacteristicValueChangedCallbacks.clear()
}
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
}
mServer = manager.openGattServer(mContext, bluetoothGattServerCallback)
mOpening = true
}
private fun mCloseServer() {
if (!mOpening) {
return
}
mServer.close()
mOpening = false
}
private fun mOnCharacteristicNotifyStateChanged(
device: BluetoothDevice,
characteristic: BluetoothGattCharacteristic,
value: ByteArray
) {
val addressArgs = device.address
val hashCode = characteristic.hashCode()
val characteristicArgs = mCharacteristicsArgs[hashCode] ?: return
val hashCodeArgs = characteristicArgs.hashCodeArgs
val stateArgs = value.toNotifyStateArgs()
val stateNumberArgs = stateArgs.raw.toLong()
mApi.onCharacteristicNotifyStateChanged(addressArgs, hashCodeArgs, stateNumberArgs) {}
}
}

View File

@ -1,18 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
class MyRequestPermissionResultListener(manager: MyBluetoothLowEnergyManager) :
RequestPermissionsResultListener {
private val mManager: MyBluetoothLowEnergyManager
init {
mManager = manager
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, results: IntArray
): Boolean {
return mManager.onRequestPermissionsResult(requestCode, permissions, results)
}
}

View File

@ -0,0 +1,10 @@
package dev.hebei.bluetooth_low_energy_android
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/

View File

@ -1,27 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_android
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import kotlin.test.Test
import org.mockito.Mockito
/*
* This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
*
* Once you have built the plugin's example app, you can run these tests from the command
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
* you can run them directly from IDEs that support JUnit such as Android Studio.
*/
internal class BluetoothLowEnergyAndroidPluginTest {
@Test
fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
val plugin = BluetoothLowEnergyAndroidPlugin()
val call = MethodCall("getPlatformVersion", null)
val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
plugin.onMethodCall(call, mockResult)
Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
}
}

View File

@ -27,7 +27,6 @@ migrate_working_dir/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

View File

@ -1,6 +1,6 @@
# bluetooth_low_energy_example
# bluetooth_low_energy_android_example
Demonstrates how to use the bluetooth_low_energy plugin.
Demonstrates how to use the bluetooth_low_energy_android plugin.
## Getting Started

View File

@ -1,68 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = '1'
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = '1.0'
flutterVersionName = "1.0"
}
android {
namespace "dev.yanshouwang.bluetooth_low_energy_example"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
namespace = "dev.hebei.bluetooth_low_energy_android_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.yanshouwang.bluetooth_low_energy_example"
applicationId = "dev.hebei.bluetooth_low_energy_android_example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig = signingConfigs.debug
}
}
}
flutter {
source '../..'
source = "../.."
}
dependencies {}

View File

@ -1,12 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="bluetooth_low_energy_example"
android:label="bluetooth_low_energy_android_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@ -30,4 +31,15 @@
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,5 @@
package dev.hebei.bluetooth_low_energy_android_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -1,6 +0,0 @@
package dev.yanshouwang.bluetooth_low_energy_example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
@ -18,12 +5,12 @@ allprojects {
}
}
rootProject.buildDir = '../build'
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {

View File

@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@ -5,16 +5,21 @@ pluginManagement {
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
}()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
include ":app"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
include ":app"

View File

@ -6,17 +6,8 @@
// For more information about Flutter integration tests, please see
// https://docs.flutter.dev/cookbook/testing/integration/introduction
// import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// testWidgets('getPlatformVersion test', (WidgetTester tester) async {
// final BluetoothLowEnergy plugin = BluetoothLowEnergy();
// final String? version = await plugin.getPlatformVersion();
// // The version string depends on the host platform running the test, so
// // just assert that some non-empty string is returned.
// expect(version?.isNotEmpty, true);
// });
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
export 'models/log.dart';

View File

@ -0,0 +1,10 @@
class Log {
final DateTime time;
final String type;
final String message;
Log({
required this.type,
required this.message,
}) : time = DateTime.now();
}

View File

@ -0,0 +1,85 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:clover/clover.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
import 'view_models.dart';
import 'views.dart';
final routerConfig = GoRouter(
redirect: (context, state) {
if (state.matchedLocation == '/') {
return '/central';
}
return null;
},
routes: [
StatefulShellRoute(
// builder: (context, state, navigationShell) {
// return HomeView(navigationShell: navigationShell);
// },
builder: (context, state, navigationShell) => navigationShell,
navigatorContainerBuilder: (context, navigationShell, children) {
final navigators = children.mapIndexed(
(index, element) {
if (index == 0) {
return ViewModelBinding(
viewBuilder: (context) => element,
viewModelBuilder: (context) => CentralManagerViewModel(),
);
} else {
return element;
}
},
).toList();
return HomeView(
navigationShell: navigationShell,
navigators: navigators,
);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/central',
builder: (context, state) {
return const CentralManagerView();
},
routes: [
GoRoute(
path: ':uuid',
builder: (context, state) {
final uuidValue = state.pathParameters['uuid']!;
final uuid = UUID.fromString(uuidValue);
final viewModel =
ViewModel.of<CentralManagerViewModel>(context);
final eventArgs = viewModel.discoveries.firstWhere(
(discovery) => discovery.peripheral.uuid == uuid);
return ViewModelBinding(
viewBuilder: (context) => PeripheralView(),
viewModelBuilder: (context) =>
PeripheralViewModel(eventArgs),
);
},
),
],
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/peripheral',
builder: (context, state) {
return ViewModelBinding(
viewBuilder: (context) => const PeripheralManagerView(),
viewModelBuilder: (context) => PeripheralManagerViewModel(),
);
},
),
],
),
],
),
],
);

View File

@ -0,0 +1,6 @@
export 'view_models/central_manager_view_model.dart';
export 'view_models/peripheral_view_model.dart';
export 'view_models/service_view_model.dart';
export 'view_models/characteristic_view_model.dart';
export 'view_models/descriptor_view_model.dart';
export 'view_models/peripheral_manager_view_model.dart';

View File

@ -0,0 +1,74 @@
import 'dart:async';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
class CentralManagerViewModel extends ViewModel {
final CentralManager _manager;
final List<DiscoveredEventArgs> _discoveries;
bool _discovering;
late final StreamSubscription _stateChangedSubscription;
late final StreamSubscription _discoveredSubscription;
CentralManagerViewModel()
: _manager = CentralManager()..logLevel = Level.INFO,
_discoveries = [],
_discovering = false {
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized) {
await _manager.authorize();
}
notifyListeners();
});
_discoveredSubscription = _manager.discovered.listen((eventArgs) {
final peripheral = eventArgs.peripheral;
final index = _discoveries.indexWhere((i) => i.peripheral == peripheral);
if (index < 0) {
_discoveries.add(eventArgs);
} else {
_discoveries[index] = eventArgs;
}
notifyListeners();
});
}
BluetoothLowEnergyState get state => _manager.state;
bool get discovering => _discovering;
List<DiscoveredEventArgs> get discoveries => _discoveries;
Future<void> showAppSettings() async {
await _manager.showAppSettings();
}
Future<void> startDiscovery({
List<UUID>? serviceUUIDs,
}) async {
if (_discovering) {
return;
}
_discoveries.clear();
await _manager.startDiscovery(
serviceUUIDs: serviceUUIDs,
);
_discovering = true;
notifyListeners();
}
Future<void> stopDiscovery() async {
if (!_discovering) {
return;
}
await _manager.stopDiscovery();
_discovering = false;
notifyListeners();
}
@override
void dispose() {
_stateChangedSubscription.cancel();
_discoveredSubscription.cancel();
super.dispose();
}
}

View File

@ -0,0 +1,145 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluetooth_low_energy_android_example/models.dart';
import 'package:clover/clover.dart';
import 'descriptor_view_model.dart';
class CharacteristicViewModel extends ViewModel {
final CentralManager _manager;
final Peripheral _peripheral;
final GATTCharacteristic _characteristic;
final List<DescriptorViewModel> _descriptorViewModels;
final List<Log> _logs;
GATTCharacteristicWriteType _writeType;
bool _notifyState;
late final StreamSubscription _characteristicNotifiedSubscription;
CharacteristicViewModel({
required CentralManager manager,
required Peripheral peripheral,
required GATTCharacteristic characteristic,
}) : _manager = manager,
_peripheral = peripheral,
_characteristic = characteristic,
_descriptorViewModels = characteristic.descriptors
.map((descriptor) => DescriptorViewModel(descriptor))
.toList(),
_logs = [],
_writeType = GATTCharacteristicWriteType.withResponse,
_notifyState = false {
if (!canWrite && canWriteWithoutResponse) {
_writeType = GATTCharacteristicWriteType.withoutResponse;
}
_characteristicNotifiedSubscription =
_manager.characteristicNotified.listen((eventArgs) {
if (eventArgs.characteristic != _characteristic) {
return;
}
final log = Log(
type: 'Notified',
message: '[${eventArgs.value.length}] ${eventArgs.value}',
);
_logs.add(log);
notifyListeners();
});
}
UUID get uuid => _characteristic.uuid;
bool get canRead =>
_characteristic.properties.contains(GATTCharacteristicProperty.read);
bool get canWrite =>
_characteristic.properties.contains(GATTCharacteristicProperty.write);
bool get canWriteWithoutResponse => _characteristic.properties
.contains(GATTCharacteristicProperty.writeWithoutResponse);
bool get canNotify =>
_characteristic.properties.contains(GATTCharacteristicProperty.notify) ||
_characteristic.properties.contains(GATTCharacteristicProperty.indicate);
List<DescriptorViewModel> get descriptorViewModels => _descriptorViewModels;
List<Log> get logs => _logs;
GATTCharacteristicWriteType get writeType => _writeType;
bool get notifyState => _notifyState;
Future<void> read() async {
final value = await _manager.readCharacteristic(
_peripheral,
_characteristic,
);
final log = Log(
type: 'Read',
message: '[${value.length}] $value',
);
_logs.add(log);
notifyListeners();
}
void setWriteType(GATTCharacteristicWriteType type) {
if (type == GATTCharacteristicWriteType.withResponse && !canWrite) {
throw ArgumentError.value(type);
}
if (type == GATTCharacteristicWriteType.withoutResponse &&
!canWriteWithoutResponse) {
throw ArgumentError.value(type);
}
_writeType = type;
notifyListeners();
}
Future<void> write(Uint8List value) async {
// Fragments the value by maximumWriteLength.
final fragmentSize = await _manager.getMaximumWriteLength(
_peripheral,
type: writeType,
);
var start = 0;
while (start < value.length) {
final end = start + fragmentSize;
final fragmentedValue =
end < value.length ? value.sublist(start, end) : value.sublist(start);
final type = writeType;
await _manager.writeCharacteristic(
_peripheral,
_characteristic,
value: fragmentedValue,
type: type,
);
final log = Log(
type: type == GATTCharacteristicWriteType.withResponse
? 'Write'
: 'Write without response',
message: '[${value.length}] $value',
);
_logs.add(log);
notifyListeners();
start = end;
}
}
Future<void> setNotifyState(bool state) async {
await _manager.setCharacteristicNotifyState(
_peripheral,
_characteristic,
state: state,
);
_notifyState = state;
notifyListeners();
}
void clearLogs() {
_logs.clear();
notifyListeners();
}
@override
void dispose() {
_characteristicNotifiedSubscription.cancel();
for (var descriptorViewModel in descriptorViewModels) {
descriptorViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,10 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:clover/clover.dart';
class DescriptorViewModel extends ViewModel {
final GATTDescriptor _descriptor;
DescriptorViewModel(this._descriptor);
UUID get uuid => _descriptor.uuid;
}

View File

@ -0,0 +1,170 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluetooth_low_energy_android_example/models.dart';
import 'package:clover/clover.dart';
import 'package:logging/logging.dart';
class PeripheralManagerViewModel extends ViewModel {
final PeripheralManager _manager;
final List<Log> _logs;
bool _advertising;
late final StreamSubscription _stateChangedSubscription;
late final StreamSubscription _characteristicReadRequestedSubscription;
late final StreamSubscription _characteristicWriteRequestedSubscription;
late final StreamSubscription _characteristicNotifyStateChangedSubscription;
PeripheralManagerViewModel()
: _manager = PeripheralManager()..logLevel = Level.INFO,
_logs = [],
_advertising = false {
_stateChangedSubscription = _manager.stateChanged.listen((eventArgs) async {
if (eventArgs.state == BluetoothLowEnergyState.unauthorized) {
await _manager.authorize();
}
notifyListeners();
});
_characteristicReadRequestedSubscription =
_manager.characteristicReadRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final log = Log(
type: 'Characteristic read requested',
message: '${central.uuid}, ${characteristic.uuid}, $offset',
);
_logs.add(log);
notifyListeners();
final elements = List.generate(100, (i) => i % 256);
final value = Uint8List.fromList(elements);
final trimmedValue = value.sublist(offset);
await _manager.respondReadRequestWithValue(
request,
value: trimmedValue,
);
});
_characteristicWriteRequestedSubscription =
_manager.characteristicWriteRequested.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final request = eventArgs.request;
final offset = request.offset;
final value = request.value;
final log = Log(
type: 'Characteristic write requested',
message:
'[${value.length}] ${central.uuid}, ${characteristic.uuid}, $offset, $value',
);
_logs.add(log);
notifyListeners();
await _manager.respondWriteRequest(request);
});
_characteristicNotifyStateChangedSubscription =
_manager.characteristicNotifyStateChanged.listen((eventArgs) async {
final central = eventArgs.central;
final characteristic = eventArgs.characteristic;
final state = eventArgs.state;
final log = Log(
type: 'Characteristic notify state changed',
message: '${central.uuid}, ${characteristic.uuid}, $state',
);
_logs.add(log);
notifyListeners();
// Write someting to the central when notify started.
if (state) {
final maximumNotifyLength =
await _manager.getMaximumNotifyLength(central);
final elements = List.generate(maximumNotifyLength, (i) => i % 256);
final value = Uint8List.fromList(elements);
await _manager.notifyCharacteristic(
central,
characteristic,
value: value,
);
}
});
}
BluetoothLowEnergyState get state => _manager.state;
bool get advertising => _advertising;
List<Log> get logs => _logs;
Future<void> showAppSettings() async {
await _manager.showAppSettings();
}
Future<void> startAdvertising() async {
if (_advertising) {
return;
}
await _manager.removeAllServices();
final elements = List.generate(100, (i) => i % 256);
final value = Uint8List.fromList(elements);
final service = GATTService(
uuid: UUID.short(100),
isPrimary: true,
includedServices: [],
characteristics: [
GATTCharacteristic.immutable(
uuid: UUID.short(200),
value: value,
descriptors: [],
),
GATTCharacteristic.mutable(
uuid: UUID.short(201),
properties: [
GATTCharacteristicProperty.read,
GATTCharacteristicProperty.write,
GATTCharacteristicProperty.writeWithoutResponse,
GATTCharacteristicProperty.notify,
GATTCharacteristicProperty.indicate,
],
permissions: [
GATTCharacteristicPermission.read,
GATTCharacteristicPermission.write,
],
descriptors: [],
),
],
);
await _manager.addService(service);
final advertisement = Advertisement(
name: 'BLE-12138',
manufacturerSpecificData: [
ManufacturerSpecificData(
id: 0x2e19,
data: Uint8List.fromList([0x01, 0x02, 0x03]),
)
],
);
await _manager.startAdvertising(advertisement);
_advertising = true;
notifyListeners();
}
Future<void> stopAdvertising() async {
if (!_advertising) {
return;
}
await _manager.stopAdvertising();
_advertising = false;
notifyListeners();
}
void clearLogs() {
_logs.clear();
notifyListeners();
}
@override
void dispose() {
_stateChangedSubscription.cancel();
_characteristicReadRequestedSubscription.cancel();
_characteristicWriteRequestedSubscription.cancel();
_characteristicNotifyStateChangedSubscription.cancel();
super.dispose();
}
}

View File

@ -0,0 +1,83 @@
import 'dart:async';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:clover/clover.dart';
import 'package:hybrid_logging/hybrid_logging.dart';
import 'service_view_model.dart';
class PeripheralViewModel extends ViewModel with TypeLogger {
final CentralManager _manager;
final Peripheral _peripheral;
final String? _name;
bool _connected;
List<ServiceViewModel> _serviceViewModels;
late final StreamSubscription _connectionStateChangedSubscription;
late final StreamSubscription _mtuChangedChangedSubscription;
PeripheralViewModel(DiscoveredEventArgs eventArgs)
: _manager = CentralManager(),
_peripheral = eventArgs.peripheral,
_name = eventArgs.advertisement.name,
_connected = false,
_serviceViewModels = [] {
_connectionStateChangedSubscription =
_manager.connectionStateChanged.listen((eventArgs) {
if (eventArgs.peripheral != _peripheral) {
return;
}
if (eventArgs.state == ConnectionState.connected) {
_connected = true;
} else {
_connected = false;
_serviceViewModels = [];
}
notifyListeners();
});
_mtuChangedChangedSubscription = _manager.mtuChanged.listen((eventArgs) {
if (eventArgs.peripheral != _peripheral) {
return;
}
logger.info('MTU chanaged: ${eventArgs.mtu}');
});
}
UUID get uuid => _peripheral.uuid;
String? get name => _name;
bool get connected => _connected;
List<ServiceViewModel> get serviceViewModels => _serviceViewModels;
Future<void> connect() async {
await _manager.connect(_peripheral);
}
Future<void> disconnect() async {
await _manager.disconnect(_peripheral);
}
Future<void> discoverGATT() async {
final services = await _manager.discoverGATT(_peripheral);
_serviceViewModels = services
.map((service) => ServiceViewModel(
manager: _manager,
peripheral: _peripheral,
service: service,
))
.toList();
notifyListeners();
}
@override
void dispose() {
if (connected) {
disconnect();
}
_connectionStateChangedSubscription.cancel();
_mtuChangedChangedSubscription.cancel();
for (var serviceViewModel in serviceViewModels) {
serviceViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,46 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:clover/clover.dart';
import 'characteristic_view_model.dart';
class ServiceViewModel extends ViewModel {
final GATTService _service;
final List<ServiceViewModel> _includedServiceViewModels;
final List<CharacteristicViewModel> _characteristicViewModels;
ServiceViewModel({
required CentralManager manager,
required Peripheral peripheral,
required GATTService service,
}) : _service = service,
_includedServiceViewModels = service.includedServices
.map((service) => ServiceViewModel(
manager: manager,
peripheral: peripheral,
service: service,
))
.toList(),
_characteristicViewModels = service.characteristics
.map((characteristic) => CharacteristicViewModel(
manager: manager,
peripheral: peripheral,
characteristic: characteristic,
))
.toList();
UUID get uuid => _service.uuid;
bool get isPrimary => _service.isPrimary;
List<ServiceViewModel> get includedServiceViewModels =>
_includedServiceViewModels;
List<CharacteristicViewModel> get characteristicViewModels =>
_characteristicViewModels;
@override
void dispose() {
for (var characteristicViewModel in characteristicViewModels) {
characteristicViewModel.dispose();
}
super.dispose();
}
}

View File

@ -0,0 +1,4 @@
export 'views/home_view.dart';
export 'views/central_manager_view.dart';
export 'views/peripheral_manager_view.dart';
export 'views/peripheral_view.dart';

View File

@ -0,0 +1,69 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:convert/convert.dart';
import 'package:flutter/material.dart';
class AdvertisementView extends StatelessWidget {
final Advertisement advertisement;
const AdvertisementView({
super.key,
required this.advertisement,
});
@override
Widget build(BuildContext context) {
final manufacturerSpecificData = advertisement.manufacturerSpecificData;
return LayoutBuilder(
builder: (context, constraints) {
const idWidth = 100.0;
final valueWidth = constraints.maxWidth - idWidth - 16.0 * 2.0;
return DataTable(
columnSpacing: 0.0,
horizontalMargin: 16.0,
columns: [
DataColumn(
label: Container(
width: idWidth,
alignment: Alignment.center,
child: const Text('Id'),
),
),
DataColumn(
label: Container(
width: valueWidth,
alignment: Alignment.center,
child: const Text('Value'),
),
),
],
rows: manufacturerSpecificData.map((item) {
final id = '0x${item.id.toRadixString(16).padLeft(4, '0')}';
final value = hex.encode(item.data);
return DataRow(
cells: [
DataCell(
Container(
width: idWidth,
alignment: Alignment.center,
child: Text(id),
),
),
DataCell(
Container(
width: valueWidth,
alignment: Alignment.center,
child: Text(
value,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
],
);
}).toList(),
);
},
);
}
}

View File

@ -0,0 +1,124 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:bluetooth_low_energy_android_example/widgets.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'advertisement_view.dart';
class CentralManagerView extends StatelessWidget {
const CentralManagerView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
final state = viewModel.state;
final discovering = viewModel.discovering;
return Scaffold(
appBar: AppBar(
title: const Text('Central Manager'),
actions: [
TextButton(
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (discovering) {
await viewModel.stopDiscovery();
} else {
await viewModel.startDiscovery();
}
}
: null,
child: Text(discovering ? 'END' : 'BEGIN'),
),
],
),
body: buildBody(context),
);
}
Widget buildBody(BuildContext context) {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
final state = viewModel.state;
if (state == BluetoothLowEnergyState.unauthorized) {
return Center(
child: TextButton(
onPressed: () => viewModel.showAppSettings(),
child: const Text('Go to settings'),
),
);
} else if (state == BluetoothLowEnergyState.poweredOn) {
final discoveries = viewModel.discoveries;
return ListView.separated(
itemBuilder: (context, index) {
final theme = Theme.of(context);
final discovery = discoveries[index];
final uuid = discovery.peripheral.uuid;
final name = discovery.advertisement.name;
final rssi = discovery.rssi;
return ListTile(
onTap: () {
onTapDissovery(context, discovery);
},
onLongPress: () {
onLongPressDiscovery(context, discovery);
},
title: Text(name ?? ''),
subtitle: Text(
'$uuid',
style: theme.textTheme.bodySmall,
softWrap: false,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
RSSIIndicator(rssi),
Text('$rssi'),
],
),
);
},
separatorBuilder: (context, i) {
return const Divider(
height: 0.0,
);
},
itemCount: discoveries.length,
);
} else {
return Center(
child: Text(
'$state',
style: Theme.of(context).textTheme.titleMedium,
),
);
}
}
void onTapDissovery(
BuildContext context, DiscoveredEventArgs discovery) async {
final viewModel = ViewModel.of<CentralManagerViewModel>(context);
if (viewModel.discovering) {
await viewModel.stopDiscovery();
if (!context.mounted) {
return;
}
}
final uuid = discovery.peripheral.uuid;
context.go('/central/$uuid');
}
void onLongPressDiscovery(
BuildContext context, DiscoveredEventArgs discovery) async {
await showModalBottomSheet(
context: context,
builder: (context) {
return AdvertisementView(
advertisement: discovery.advertisement,
);
},
);
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class CharacteristicTreeNodeView extends StatelessWidget {
const CharacteristicTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
);
}
}

View File

@ -0,0 +1,178 @@
import 'dart:convert';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'log_view.dart';
class CharacteristicView extends StatefulWidget {
const CharacteristicView({super.key});
@override
State<CharacteristicView> createState() => _CharacteristicViewState();
}
class _CharacteristicViewState extends State<CharacteristicView> {
late final TextEditingController _textController;
@override
void initState() {
super.initState();
_textController = TextEditingController();
}
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<CharacteristicViewModel>(context);
final logs = viewModel.logs;
return Column(
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return OverflowBox(
alignment: Alignment.topCenter,
maxHeight: constraints.maxHeight + 1.0,
child: Row(
children: [
Expanded(
child: InputDecorator(
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(12.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.vertical(
top: const Radius.circular(12.0),
bottom: viewModel.canWrite ||
viewModel.canWriteWithoutResponse
? Radius.zero
: const Radius.circular(12.0),
),
),
),
child: ListView.builder(
itemBuilder: (context, i) {
final log = logs[i];
return LogView(
log: log,
);
},
itemCount: logs.length,
),
),
),
Column(
children: [
Visibility(
visible: viewModel.canNotify,
child: viewModel.notifyState
? IconButton.filled(
onPressed: () async {
await viewModel.setNotifyState(false);
},
icon:
const Icon(Symbols.notifications_active),
)
: IconButton.filledTonal(
onPressed: () async {
await viewModel.setNotifyState(true);
},
icon: const Icon(Symbols.notifications_off),
),
),
Visibility(
visible: viewModel.canRead,
child: IconButton.filled(
onPressed: () async {
await viewModel.read();
},
icon: const Icon(Symbols.arrow_downward),
),
),
Visibility(
visible: viewModel.canWrite ||
viewModel.canWriteWithoutResponse,
child: viewModel.writeType ==
GATTCharacteristicWriteType.withResponse
? IconButton.filled(
onPressed: viewModel.canWriteWithoutResponse
? () {
viewModel.setWriteType(
GATTCharacteristicWriteType
.withoutResponse);
}
: null,
icon: const Icon(Symbols.swap_vert),
)
: IconButton.filledTonal(
onPressed: viewModel.canWrite
? () {
viewModel.setWriteType(
GATTCharacteristicWriteType
.withResponse);
}
: null,
icon: const Icon(Symbols.arrow_upward),
),
),
IconButton.filled(
onPressed: () => viewModel.clearLogs(),
icon: const Icon(Symbols.delete),
),
],
),
],
),
);
},
),
),
Visibility(
visible: viewModel.canWrite || viewModel.canWriteWithoutResponse,
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 12.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12.0),
),
),
),
),
),
ValueListenableBuilder(
valueListenable: _textController,
builder: (context, tev, child) {
final text = tev.text;
return IconButton.filled(
onPressed: text.isEmpty
? null
: () async {
final value = utf8.encode(text);
await viewModel.write(value);
},
icon: const Icon(Symbols.pets),
);
},
),
],
),
),
],
);
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class DescriptorTreeNodeView extends StatelessWidget {
const DescriptorTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<DescriptorViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.tertiary,
),
);
}
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class HomeView extends StatefulWidget {
final StatefulNavigationShell navigationShell;
final List<Widget> navigators;
const HomeView({
super.key,
required this.navigationShell,
required this.navigators,
});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
late final PageController _controller;
@override
void initState() {
super.initState();
_controller = PageController(
initialPage: widget.navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
final navigationShell = widget.navigationShell;
final navigators = widget.navigators;
return Scaffold(
// body: navigationShell,
body: PageView.builder(
controller: _controller,
onPageChanged: (index) {
// Ignore tap events.
if (index == navigationShell.currentIndex) {
return;
}
navigationShell.goBranch(
index,
initialLocation: false,
);
},
itemBuilder: (context, index) {
return navigators[index];
},
itemCount: navigators.length,
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.radar),
label: 'Central',
),
BottomNavigationBarItem(
icon: Icon(Icons.sensors),
label: 'Peripheral',
),
],
currentIndex: widget.navigationShell.currentIndex,
),
);
}
@override
void didUpdateWidget(covariant HomeView oldWidget) {
super.didUpdateWidget(oldWidget);
final navigationShell = widget.navigationShell;
final page = _controller.page ?? _controller.initialPage;
final index = page.round();
// Ignore swipe events.
if (index == navigationShell.currentIndex) {
return;
}
_controller.animateToPage(
navigationShell.currentIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,43 @@
import 'package:bluetooth_low_energy_android_example/models.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class LogView extends StatelessWidget {
final Log log;
const LogView({
super.key,
required this.log,
});
@override
Widget build(BuildContext context) {
final formatter = DateFormat.Hms();
final time = formatter.format(log.time);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text.rich(
TextSpan(
text: '[$time] ',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.error,
),
children: [
TextSpan(
text: log.type,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
)),
],
),
),
Text(
log.message,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}

View File

@ -0,0 +1,76 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'log_view.dart';
class PeripheralManagerView extends StatelessWidget {
const PeripheralManagerView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
final state = viewModel.state;
final advertising = viewModel.advertising;
return Scaffold(
appBar: AppBar(
title: const Text('Peripheral Manager'),
actions: [
TextButton(
onPressed: state == BluetoothLowEnergyState.poweredOn
? () async {
if (advertising) {
await viewModel.stopAdvertising();
} else {
await viewModel.startAdvertising();
}
}
: null,
child: Text(advertising ? 'END' : 'BEGIN'),
),
],
),
body: buildBody(context),
floatingActionButton: state == BluetoothLowEnergyState.poweredOn
? FloatingActionButton(
onPressed: () => viewModel.clearLogs(),
child: const Icon(Symbols.delete),
)
: null,
);
}
Widget buildBody(BuildContext context) {
final viewModel = ViewModel.of<PeripheralManagerViewModel>(context);
final state = viewModel.state;
if (state == BluetoothLowEnergyState.unauthorized) {
return Center(
child: TextButton(
onPressed: () => viewModel.showAppSettings(),
child: const Text('Go to settings'),
),
);
} else if (state == BluetoothLowEnergyState.poweredOn) {
final logs = viewModel.logs;
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
final log = logs[i];
return LogView(
log: log,
);
},
itemCount: logs.length,
);
} else {
return Center(
child: Text(
'$state',
style: Theme.of(context).textTheme.titleMedium,
),
);
}
}
}

View File

@ -0,0 +1,107 @@
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
import 'characteristic_tree_node_view.dart';
import 'characteristic_view.dart';
import 'descriptor_tree_node_view.dart';
import 'service_tree_node_view.dart';
class PeripheralView extends StatelessWidget {
final TreeController _treeController;
PeripheralView({super.key})
: _treeController = TreeController(
allNodesExpanded: false,
);
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<PeripheralViewModel>(context);
final connected = viewModel.connected;
final serviceViewModels = viewModel.serviceViewModels;
return Scaffold(
appBar: AppBar(
title: Text(viewModel.name ?? '${viewModel.uuid}'),
actions: [
TextButton(
onPressed: () async {
if (connected) {
await viewModel.disconnect();
} else {
await viewModel.connect();
await viewModel.discoverGATT();
}
},
child: Text(connected ? 'DISCONNECT' : 'CONNECT'),
),
],
),
body: SingleChildScrollView(
child: TreeView(
treeController: _treeController,
indent: 0.0,
nodes: _buildServiceTreeNodes(serviceViewModels),
),
),
);
}
List<TreeNode> _buildServiceTreeNodes(List<ServiceViewModel> viewModels) {
return viewModels.map((viewModel) {
final includedServiceViewModels = viewModel.includedServiceViewModels;
final characteristicViewModels = viewModel.characteristicViewModels;
return TreeNode(
children: [
..._buildServiceTreeNodes(includedServiceViewModels),
..._buildCharacteristicTreeNodes(characteristicViewModels),
],
content: InheritedViewModel(
view: const ServiceTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
List<TreeNode> _buildCharacteristicTreeNodes(
List<CharacteristicViewModel> viewModels) {
return viewModels.map((viewModel) {
final descriptorViewModels = viewModel.descriptorViewModels;
return TreeNode(
children: [
TreeNode(
content: Expanded(
child: Container(
margin: const EdgeInsets.only(right: 40.0),
height: 360.0,
child: InheritedViewModel(
view: const CharacteristicView(),
viewModel: viewModel,
),
),
),
),
..._buildDescriptorTreeNodes(descriptorViewModels),
],
content: InheritedViewModel(
view: const CharacteristicTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
List<TreeNode> _buildDescriptorTreeNodes(
List<DescriptorViewModel> viewModels) {
return viewModels.map((viewModel) {
return TreeNode(
content: InheritedViewModel(
view: const DescriptorTreeNodeView(),
viewModel: viewModel,
),
);
}).toList();
}
}

View File

@ -0,0 +1,18 @@
import 'package:bluetooth_low_energy_android_example/view_models.dart';
import 'package:clover/clover.dart';
import 'package:flutter/material.dart';
class ServiceTreeNodeView extends StatelessWidget {
const ServiceTreeNodeView({super.key});
@override
Widget build(BuildContext context) {
final viewModel = ViewModel.of<ServiceViewModel>(context);
return Text(
'${viewModel.uuid}',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
);
}
}

View File

@ -0,0 +1 @@
export 'widgets/rssi_indicator.dart';

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class RSSIIndicator extends StatelessWidget {
final int rssi;
const RSSIIndicator(
this.rssi, {
super.key,
});
@override
Widget build(BuildContext context) {
final IconData icon;
if (rssi > -70) {
icon = Icons.wifi_rounded;
} else if (rssi > -100) {
icon = Icons.wifi_2_bar_rounded;
} else {
icon = Icons.wifi_1_bar_rounded;
}
return Icon(icon);
}
}

View File

@ -15,15 +15,15 @@ packages:
path: ".."
relative: true
source: path
version: "5.0.4"
version: "6.0.0"
bluetooth_low_energy_platform_interface:
dependency: "direct main"
description:
name: bluetooth_low_energy_platform_interface
sha256: "5af74eb8f97a896dfdbcff2da284d4fe5b4e2e49ebb2f46f826ac1810f66e4d7"
sha256: bc2e8d97c141653e5747bcb3cdc9fe956541b6ecc6e5f158b99a2f3abc2d946a
url: "https://pub.dev"
source: hosted
version: "5.0.2"
version: "6.0.0"
boolean_selector:
dependency: transitive
description:
@ -48,8 +48,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
clover:
dependency: "direct main"
description:
name: clover
sha256: eba28e69b32f174a51c093eef098cf5ae646470322727081d5d3d8f66c786487
url: "https://pub.dev"
source: hosted
version: "3.0.0"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
@ -68,10 +76,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "1.0.8"
fake_async:
dependency: transitive
description:
@ -102,7 +110,15 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_simple_treeview:
dependency: "direct main"
description:
name: flutter_simple_treeview
sha256: ad4978d2668dd078d3a09966832da111bef9102dd636e572c50c80133b7ff4d9
url: "https://pub.dev"
source: hosted
version: "3.0.2"
@ -111,11 +127,32 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: "6ad5662b014c06c20fa46ab78715c96b2222a7fe4f87bf77e0289592c2539e86"
url: "https://pub.dev"
source: hosted
version: "14.1.3"
hybrid_logging:
dependency: "direct main"
description:
name: hybrid_logging
sha256: "54248d52ce68c14702a42fbc4083bac5c6be30f6afad8a41be4bbadd197b8af5"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
integration_test:
dependency: "direct dev"
description: flutter
@ -133,44 +170,36 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
log_service:
dependency: transitive
description:
name: log_service
sha256: "21124936899e227d1779268077921d46d57456e2592d1562e455be273594e2e4"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "4.0.0"
logging:
dependency: transitive
dependency: "direct main"
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
@ -193,14 +222,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.8.0"
material_symbols_icons:
dependency: "direct main"
description:
name: material_symbols_icons
sha256: b2d3cbc3c42b8a217715b0d97ff03aebb14b2b4592875736e5599c603fb2db7e
url: "https://pub.dev"
source: hosted
version: "4.2758.0"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.12.0"
path:
dependency: transitive
description:
@ -290,10 +327,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.0"
typed_data:
dependency: transitive
description:
@ -314,10 +351,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.2.1"
webdriver:
dependency: transitive
description:
@ -327,5 +364,5 @@ packages:
source: hosted
version: "3.0.3"
sdks:
dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.0.0"
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@ -1,5 +1,5 @@
name: bluetooth_low_energy_example
description: Demonstrates how to use the bluetooth_low_energy plugin.
name: bluetooth_low_energy_android_example
description: "Demonstrates how to use the bluetooth_low_energy_android plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@ -17,10 +17,10 @@ dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^5.0.0
bluetooth_low_energy_platform_interface: ^6.0.0
bluetooth_low_energy_android:
# When depending on this package from a real application you should use:
# bluetooth_low_energy: ^x.y.z
# bluetooth_low_energy_android: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
@ -28,9 +28,16 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
convert: ^3.1.1
cupertino_icons: ^1.0.6
clover: ^3.0.0
go_router: ^14.1.3
logging: ^1.2.0
hybrid_logging: ^1.0.0
intl: ^0.19.0
collection: ^1.18.0
convert: ^3.1.1
flutter_simple_treeview: ^3.0.2
material_symbols_icons: ^4.2744.0
dev_dependencies:
integration_test:
@ -43,7 +50,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -5,23 +5,4 @@
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bluetooth_low_energy_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}
void main() {}

View File

@ -3,9 +3,9 @@ import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_pla
import 'src/my_central_manager.dart';
import 'src/my_peripheral_manager.dart';
abstract class BluetoothLowEnergyAndroid {
abstract class BluetoothLowEnergyAndroidPlugin {
static void registerWith() {
CentralManager.instance = MyCentralManager();
PeripheralManager.instance = MyPeripheralManager();
PlatformCentralManager.instance = MyCentralManager();
PlatformPeripheralManager.instance = MyPeripheralManager();
}
}

View File

@ -3,15 +3,11 @@ import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_api.g.dart';
import 'my_central2.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_gatt_service2.dart';
import 'my_peripheral2.dart';
import 'my_central.dart';
import 'my_gatt.dart';
import 'my_peripheral.dart';
export 'my_api.g.dart';
// ToObject
// ToObj
extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
BluetoothLowEnergyState toState() {
switch (this) {
@ -31,209 +27,251 @@ extension MyBluetoothLowEnergyStateArgsX on MyBluetoothLowEnergyStateArgs {
}
}
extension MyGattCharacteristicPropertyArgsX
on MyGattCharacteristicPropertyArgs {
GattCharacteristicProperty toProperty() {
return GattCharacteristicProperty.values[index];
extension MyConnectionStateArgsX on MyConnectionStateArgs {
ConnectionState toState() {
switch (this) {
case MyConnectionStateArgs.disconnected:
case MyConnectionStateArgs.connecting:
return ConnectionState.disconnected;
case MyConnectionStateArgs.connected:
case MyConnectionStateArgs.disconnecting:
return ConnectionState.connected;
}
}
}
extension MyGATTCharacteristicPropertyArgsX
on MyGATTCharacteristicPropertyArgs {
GATTCharacteristicProperty toProperty() {
return GATTCharacteristicProperty.values[index];
}
}
extension MyManufacturerSpecificDataArgsX on MyManufacturerSpecificDataArgs {
ManufacturerSpecificData toManufacturerSpecificData() {
final id = idArgs;
final data = dataArgs;
return ManufacturerSpecificData(
id: id,
data: data,
id: idArgs,
data: dataArgs,
);
}
}
extension MyAdvertisementArgsX on MyAdvertisementArgs {
Advertisement toAdvertisement() {
final name = nameArgs;
final serviceUUIDs =
serviceUUIDsArgs.cast<String>().map((args) => args.toUUID()).toList();
final serviceData = serviceDataArgs.cast<String, Uint8List>().map(
(uuidArgs, dataArgs) {
final uuid = uuidArgs.toUUID();
final data = dataArgs;
return MapEntry(uuid, data);
},
);
final manufacturerSpecificData =
manufacturerSpecificDataArgs?.toManufacturerSpecificData();
return Advertisement(
name: name,
serviceUUIDs: serviceUUIDs,
serviceData: serviceData,
manufacturerSpecificData: manufacturerSpecificData,
);
}
}
extension MyCentralArgsX on MyCentralArgs {
MyCentral2 toCentral() {
return MyCentral2(
address: addressArgs,
name: nameArgs,
serviceUUIDs: serviceUUIDsArgs
.cast<String>()
.map((args) => UUID.fromString(args))
.toList(),
serviceData: serviceDataArgs.cast<String, Uint8List>().map(
(uuidArgs, dataArgs) {
final uuid = UUID.fromString(uuidArgs);
return MapEntry(uuid, dataArgs);
},
),
manufacturerSpecificData: manufacturerSpecificDataArgs
.cast<MyManufacturerSpecificDataArgs>()
.map((args) => args.toManufacturerSpecificData())
.toList(),
);
}
}
extension MyPeripheralArgsX on MyPeripheralArgs {
MyPeripheral2 toPeripheral() {
return MyPeripheral2(
MyPeripheral toPeripheral() {
return MyPeripheral(
addressArgs: addressArgs,
);
}
}
extension MyGATTDescriptorArgsX on MyGATTDescriptorArgs {
MyGATTDescriptor toDescriptor() {
return MyGATTDescriptor(
hashCodeArgs: hashCodeArgs,
uuid: UUID.fromString(uuidArgs),
);
}
}
extension MyGATTCharacteristicArgsX on MyGATTCharacteristicArgs {
MyGATTCharacteristic toCharacteristic() {
return MyGATTCharacteristic(
hashCodeArgs: hashCodeArgs,
uuid: UUID.fromString(uuidArgs),
properties: propertyNumbersArgs.cast<int>().map((args) {
final propertyArgs = MyGATTCharacteristicPropertyArgs.values[args];
return propertyArgs.toProperty();
}).toList(),
descriptors: descriptorsArgs
.cast<MyGATTDescriptorArgs>()
.map((args) => args.toDescriptor())
.toList(),
);
}
}
extension MyGATTServiceArgsX on MyGATTServiceArgs {
MyGATTService toService() {
return MyGATTService(
hashCodeArgs: hashCodeArgs,
uuid: UUID.fromString(uuidArgs),
isPrimary: isPrimaryArgs,
includedServices: includedServicesArgs
.cast<MyGATTServiceArgs>()
.map((args) => args.toService())
.toList(),
characteristics: characteristicsArgs
.cast<MyGATTCharacteristicArgs>()
.map((args) => args.toCharacteristic())
.toList(),
);
}
}
extension MyCentralArgsX on MyCentralArgs {
MyCentral toCentral() {
return MyCentral(
address: addressArgs,
);
}
}
extension MyGattDescriptorArgsX on MyGattDescriptorArgs {
MyGattDescriptor2 toDescriptor2(MyPeripheral2 peripheral) {
final hashCode = hashCodeArgs;
final uuid = uuidArgs.toUUID();
return MyGattDescriptor2(
peripheral: peripheral,
hashCode: hashCode,
uuid: uuid,
);
}
}
extension MyGattCharacteristicArgsX on MyGattCharacteristicArgs {
MyGattCharacteristic2 toCharacteristic2(MyPeripheral2 peripheral) {
final hashCode = hashCodeArgs;
final uuid = uuidArgs.toUUID();
final properties = propertyNumbersArgs.cast<int>().map(
(args) {
final propertyArgs = MyGattCharacteristicPropertyArgs.values[args];
return propertyArgs.toProperty();
},
).toList();
final descriptors = descriptorsArgs
.cast<MyGattDescriptorArgs>()
.map((args) => args.toDescriptor2(peripheral))
.toList();
return MyGattCharacteristic2(
peripheral: peripheral,
hashCode: hashCode,
uuid: uuid,
properties: properties,
descriptors: descriptors,
);
}
}
extension MyGattServiceArgsX on MyGattServiceArgs {
MyGattService2 toService2(MyPeripheral2 peripheral) {
final hashCode = hashCodeArgs;
final uuid = uuidArgs.toUUID();
final characteristics = characteristicsArgs
.cast<MyGattCharacteristicArgs>()
.map((args) => args.toCharacteristic2(peripheral))
.toList();
return MyGattService2(
peripheral: peripheral,
hashCode: hashCode,
uuid: uuid,
characteristics: characteristics,
);
}
}
extension MyUuidArgsX on String {
UUID toUUID() {
return UUID.fromString(this);
}
}
// ToArgs
extension GattCharacteristicPropertyX on GattCharacteristicProperty {
MyGattCharacteristicPropertyArgs toArgs() {
return MyGattCharacteristicPropertyArgs.values[index];
extension UUIDX on UUID {
String toArgs() {
return toString();
}
}
extension GattCharacteristicWriteTypeX on GattCharacteristicWriteType {
MyGattCharacteristicWriteTypeArgs toArgs() {
return MyGattCharacteristicWriteTypeArgs.values[index];
extension GATTCharacteristicWriteTypeX on GATTCharacteristicWriteType {
MyGATTCharacteristicWriteTypeArgs toArgs() {
return MyGATTCharacteristicWriteTypeArgs.values[index];
}
}
extension GATTCharacteristicPropertyX on GATTCharacteristicProperty {
MyGATTCharacteristicPropertyArgs toArgs() {
return MyGATTCharacteristicPropertyArgs.values[index];
}
}
extension GATTCharacteristicPermissionX on GATTCharacteristicPermission {
MyGATTCharacteristicPermissionArgs toArgs() {
return MyGATTCharacteristicPermissionArgs.values[index];
}
}
extension GATTErrorX on GATTError {
MyGATTStatusArgs toArgs() {
switch (this) {
case GATTError.readNotPermitted:
return MyGATTStatusArgs.readNotPermitted;
case GATTError.writeNotPermitted:
return MyGATTStatusArgs.writeNotPermitted;
case GATTError.insufficientAuthentication:
return MyGATTStatusArgs.insufficientAuthentication;
case GATTError.requestNotSupported:
return MyGATTStatusArgs.requestNotSupported;
case GATTError.invalidOffset:
return MyGATTStatusArgs.invalidOffset;
case GATTError.insufficientAuthorization:
return MyGATTStatusArgs.insufficientAuthorization;
case GATTError.invalidAttributeValueLength:
return MyGATTStatusArgs.invalidAttributeLength;
case GATTError.insufficientEncryption:
return MyGATTStatusArgs.insufficientEncryption;
default:
return MyGATTStatusArgs.failure;
}
}
}
extension ManufacturerSpecificDataX on ManufacturerSpecificData {
MyManufacturerSpecificDataArgs toArgs() {
final idArgs = id;
final dataArgs = data;
return MyManufacturerSpecificDataArgs(
idArgs: idArgs,
dataArgs: dataArgs,
idArgs: id,
dataArgs: data,
);
}
}
extension AdvertisementX on Advertisement {
MyAdvertisementArgs toArgs() {
final nameArgs = name;
final serviceUUIDsArgs = serviceUUIDs.map((uuid) => uuid.toArgs()).toList();
final serviceDataArgs = serviceData.map((uuid, data) {
final uuidArgs = uuid.toArgs();
final dataArgs = data;
return MapEntry(uuidArgs, dataArgs);
});
final manufacturerSpecificDataArgs = manufacturerSpecificData?.toArgs();
return MyAdvertisementArgs(
nameArgs: nameArgs,
serviceUUIDsArgs: serviceUUIDsArgs,
serviceDataArgs: serviceDataArgs,
manufacturerSpecificDataArgs: manufacturerSpecificDataArgs,
nameArgs: name,
serviceUUIDsArgs: serviceUUIDs.map((uuid) => uuid.toArgs()).toList(),
serviceDataArgs: serviceData.map((uuid, data) {
final uuidArgs = uuid.toArgs();
final dataArgs = data;
return MapEntry(uuidArgs, dataArgs);
}),
manufacturerSpecificDataArgs:
manufacturerSpecificData.map((data) => data.toArgs()).toList(),
);
}
}
extension MyGattDescriptorX on MyGattDescriptor {
MyGattDescriptorArgs toArgs() {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toArgs();
final valueArgs = value;
return MyGattDescriptorArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
valueArgs: valueArgs,
extension MutableGATTDescriptorX on MutableGATTDescriptor {
MyMutableGATTDescriptorArgs toArgs() {
return MyMutableGATTDescriptorArgs(
hashCodeArgs: hashCode,
uuidArgs: uuid.toArgs(),
permissionNumbersArgs: permissions.map((permission) {
final permissionArgs = permission.toArgs();
return permissionArgs.index;
}).toList(),
);
}
}
extension MyGattCharacteristicX on MyGattCharacteristic {
MyGattCharacteristicArgs toArgs(List<MyGattDescriptorArgs> descriptorsArgs) {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toArgs();
final propertyNumbersArgs = properties.map((property) {
final propertyArgs = property.toArgs();
return propertyArgs.index;
}).toList();
return MyGattCharacteristicArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
propertyNumbersArgs: propertyNumbersArgs,
extension MutableGATTCharacteristicX on MutableGATTCharacteristic {
MyMutableGATTCharacteristicArgs toArgs() {
// Add CCC descriptor.
final cccUUID = UUID.short(0x2902);
final descriptorsArgs = descriptors
.cast<MutableGATTDescriptor>()
.where((descriptor) => descriptor.uuid != cccUUID)
.map((descriptor) => descriptor.toArgs())
.toList();
final cccDescriptor = MutableGATTDescriptor(
uuid: cccUUID,
permissions: [
GATTCharacteristicPermission.read,
GATTCharacteristicPermission.write,
],
);
final cccDescriptorArgs = cccDescriptor.toArgs();
descriptorsArgs.add(cccDescriptorArgs);
return MyMutableGATTCharacteristicArgs(
hashCodeArgs: hashCode,
uuidArgs: uuid.toArgs(),
permissionNumbersArgs: permissions.map((permission) {
final permissionArgs = permission.toArgs();
return permissionArgs.index;
}).toList(),
propertyNumbersArgs: properties.map((property) {
final propertyArgs = property.toArgs();
return propertyArgs.index;
}).toList(),
descriptorsArgs: descriptorsArgs,
);
}
}
extension MyGattServiceX on MyGattService {
MyGattServiceArgs toArgs(List<MyGattCharacteristicArgs> characteristicsArgs) {
final hashCodeArgs = hashCode;
final uuidArgs = uuid.toArgs();
return MyGattServiceArgs(
hashCodeArgs: hashCodeArgs,
uuidArgs: uuidArgs,
characteristicsArgs: characteristicsArgs,
extension GATTServiceX on GATTService {
MyMutableGATTServiceArgs toArgs() {
return MyMutableGATTServiceArgs(
hashCodeArgs: hashCode,
uuidArgs: uuid.toArgs(),
isPrimaryArgs: isPrimary,
includedServicesArgs:
includedServices.map((service) => service.toArgs()).toList(),
characteristicsArgs: characteristics
.cast<MutableGATTCharacteristic>()
.map((characteristic) => characteristic.toArgs())
.toList(),
);
}
}
extension UuidX on UUID {
String toArgs() {
return toString();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
class MyCentral2 extends MyCentral {
final class MyCentral extends Central {
final String address;
MyCentral2({
MyCentral({
required this.address,
}) : super(
uuid: UUID.fromAddress(address),

View File

@ -1,70 +1,100 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart' hide ConnectionState;
import 'my_api.dart';
import 'my_gatt_characteristic2.dart';
import 'my_gatt_descriptor2.dart';
import 'my_peripheral2.dart';
import 'my_api.g.dart';
import 'my_gatt.dart';
import 'my_peripheral.dart';
class MyCentralManager extends CentralManager
implements MyCentralManagerFlutterApi {
final MyCentralManagerHostApi _api;
final class MyCentralManager extends PlatformCentralManager
with WidgetsBindingObserver
implements MyCentralManagerFlutterAPI {
final MyCentralManagerHostAPI _api;
final StreamController<BluetoothLowEnergyStateChangedEventArgs>
_stateChangedController;
final StreamController<DiscoveredEventArgs> _discoveredController;
final StreamController<ConnectionStateChangedEventArgs>
final StreamController<PeripheralConnectionStateChangedEventArgs>
_connectionStateChangedController;
final StreamController<GattCharacteristicNotifiedEventArgs>
final StreamController<PeripheralMTUChangedEventArgs> _mtuChangedController;
final StreamController<GATTCharacteristicNotifiedEventArgs>
_characteristicNotifiedController;
final Map<String, MyPeripheral2> _peripherals;
final Map<String, Map<int, MyGattCharacteristic2>> _characteristics;
final Map<String, int> _mtus;
late final MyCentralManagerArgs _args;
BluetoothLowEnergyState _state;
MyCentralManager()
: _api = MyCentralManagerHostApi(),
: _api = MyCentralManagerHostAPI(),
_stateChangedController = StreamController.broadcast(),
_discoveredController = StreamController.broadcast(),
_connectionStateChangedController = StreamController.broadcast(),
_mtuChangedController = StreamController.broadcast(),
_characteristicNotifiedController = StreamController.broadcast(),
_peripherals = {},
_characteristics = {},
_mtus = {},
_state = BluetoothLowEnergyState.unknown;
UUID get cccUUID => UUID.short(0x2902);
@override
BluetoothLowEnergyState get state => _state;
@override
Stream<BluetoothLowEnergyStateChangedEventArgs> get stateChanged =>
_stateChangedController.stream;
@override
Stream<DiscoveredEventArgs> get discovered => _discoveredController.stream;
@override
Stream<ConnectionStateChangedEventArgs> get connectionStateChanged =>
_connectionStateChangedController.stream;
Stream<PeripheralConnectionStateChangedEventArgs>
get connectionStateChanged => _connectionStateChangedController.stream;
@override
Stream<GattCharacteristicNotifiedEventArgs> get characteristicNotified =>
Stream<PeripheralMTUChangedEventArgs> get mtuChanged =>
_mtuChangedController.stream;
@override
Stream<GATTCharacteristicNotifiedEventArgs> get characteristicNotified =>
_characteristicNotifiedController.stream;
@override
Future<void> setUp() async {
logger.info('setUp');
await _api.setUp();
MyCentralManagerFlutterApi.setup(this);
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
logger.info('didChangeAppLifecycleState: $state');
if (state != AppLifecycleState.resumed) {
return;
}
_getState();
}
@override
Future<BluetoothLowEnergyState> getState() {
logger.info('getState');
return Future.value(_state);
void initialize() {
MyCentralManagerFlutterAPI.setUp(this);
final binding = WidgetsFlutterBinding.ensureInitialized();
binding.addObserver(this);
_initialize();
}
@override
Future<void> startDiscovery() async {
logger.info('startDiscovery');
await _api.startDiscovery();
Future<bool> authorize() async {
logger.info('authorize');
final authorized = await _api.authorize();
return authorized;
}
@override
Future<void> showAppSettings() async {
logger.info('showAppSettings');
await _api.showAppSettings();
}
@override
Future<void> startDiscovery({
List<UUID>? serviceUUIDs,
}) async {
final serviceUUIDsArgs =
serviceUUIDs?.map((uuid) => uuid.toArgs()).toList() ?? [];
logger.info('startDiscovery: $serviceUUIDsArgs');
await _api.startDiscovery(serviceUUIDsArgs);
}
@override
@ -73,74 +103,103 @@ class MyCentralManager extends CentralManager
await _api.stopDiscovery();
}
@override
Future<List<Peripheral>> retrieveConnectedPeripherals() async {
logger.info('retrieveConnectedPeripherals');
final peripheralsArgs = await _api.retrieveConnectedPeripherals();
final peripherals = peripheralsArgs
.cast<MyPeripheralArgs>()
.map((args) => args.toPeripheral())
.toList();
return peripherals;
}
@override
Future<void> connect(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.address;
final addressArgs = peripheral.addressArgs;
logger.info('connect: $addressArgs');
await _api.connect(addressArgs);
try {
await _api.requestMTU(addressArgs, 517);
} catch (error, stackTrace) {
// 忽略协商 MTU 错误
logger.warning('requstMTU failed.', error, stackTrace);
}
}
@override
Future<void> disconnect(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.address;
final addressArgs = peripheral.addressArgs;
logger.info('disconnect: $addressArgs');
await _api.disconnect(addressArgs);
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
Future<int> requestMTU(
Peripheral peripheral, {
required int mtu,
}) async {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.address;
final addressArgs = peripheral.addressArgs;
final mtuArgs = mtu;
logger.info('requestMTU: $addressArgs - $mtuArgs');
final size = await _api.requestMTU(addressArgs, mtuArgs);
return size;
}
@override
Future<int> getMaximumWriteLength(
Peripheral peripheral, {
required GATTCharacteristicWriteType type,
}) {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.addressArgs;
final mtu = _mtus[addressArgs] ?? 23;
final maximumWriteLength = (mtu - 3).clamp(20, 512);
return Future.value(maximumWriteLength);
}
@override
Future<int> readRSSI(Peripheral peripheral) async {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.addressArgs;
logger.info('readRSSI: $addressArgs');
final rssi = await _api.readRSSI(addressArgs);
return rssi;
}
@override
Future<List<GattService>> discoverGATT(Peripheral peripheral) async {
if (peripheral is! MyPeripheral2) {
Future<List<GATTService>> discoverGATT(Peripheral peripheral) async {
if (peripheral is! MyPeripheral) {
throw TypeError();
}
final addressArgs = peripheral.address;
logger.info('discoverServices: $addressArgs');
final servicesArgs = await _api.discoverServices(addressArgs);
final addressArgs = peripheral.addressArgs;
logger.info('discoverGATT: $addressArgs');
final servicesArgs = await _api.discoverGATT(addressArgs);
final services = servicesArgs
.cast<MyGattServiceArgs>()
.map((args) => args.toService2(peripheral))
.cast<MyGATTServiceArgs>()
.map((args) => args.toService())
.toList();
final characteristics =
services.expand((service) => service.characteristics).toList();
_characteristics[addressArgs] = {
for (var characteristic in characteristics)
characteristic.hashCode: characteristic
};
return services;
}
@override
Future<Uint8List> readCharacteristic(
GattCharacteristic characteristic,
Peripheral peripheral,
GATTCharacteristic characteristic,
) async {
if (characteristic is! MyGattCharacteristic2) {
if (peripheral is! MyPeripheral ||
characteristic is! MyGATTCharacteristic) {
throw TypeError();
}
final peripheral = characteristic.peripheral;
final addressArgs = peripheral.address;
final hashCodeArgs = characteristic.hashCode;
final addressArgs = peripheral.addressArgs;
final hashCodeArgs = characteristic.hashCodeArgs;
logger.info('readCharacteristic: $addressArgs.$hashCodeArgs');
final value = await _api.readCharacteristic(addressArgs, hashCodeArgs);
return value;
@ -148,86 +207,75 @@ class MyCentralManager extends CentralManager
@override
Future<void> writeCharacteristic(
GattCharacteristic characteristic, {
Peripheral peripheral,
GATTCharacteristic characteristic, {
required Uint8List value,
required GattCharacteristicWriteType type,
required GATTCharacteristicWriteType type,
}) async {
if (characteristic is! MyGattCharacteristic2) {
if (peripheral is! MyPeripheral ||
characteristic is! MyGATTCharacteristic) {
throw TypeError();
}
final peripheral = characteristic.peripheral;
final addressArgs = peripheral.address;
final hashCodeArgs = characteristic.hashCode;
final trimmedValueArgs = value.trimGATT();
final addressArgs = peripheral.addressArgs;
final hashCodeArgs = characteristic.hashCodeArgs;
final valueArgs = value;
final typeArgs = type.toArgs();
final typeNumberArgs = typeArgs.index;
// When write without response, fragments the value by MTU - 3 size.
// If mtu is null, use 23 as default MTU size.
if (type == GattCharacteristicWriteType.withResponse) {
logger.info(
'writeCharacteristic: $addressArgs.$hashCodeArgs - $trimmedValueArgs, $typeArgs');
await _api.writeCharacteristic(
addressArgs,
hashCodeArgs,
trimmedValueArgs,
typeNumberArgs,
);
} else {
final mtu = _mtus[addressArgs] ?? 23;
final fragmentSize = (mtu - 3).clamp(20, 512);
var start = 0;
while (start < trimmedValueArgs.length) {
final end = start + fragmentSize;
final fragmentedValueArgs = end < trimmedValueArgs.length
? trimmedValueArgs.sublist(start, end)
: trimmedValueArgs.sublist(start);
logger.info(
'writeCharacteristic: $addressArgs.$hashCodeArgs - $fragmentedValueArgs, $typeArgs');
await _api.writeCharacteristic(
addressArgs,
hashCodeArgs,
fragmentedValueArgs,
typeNumberArgs,
);
start = end;
}
}
}
@override
Future<void> setCharacteristicNotifyState(
GattCharacteristic characteristic, {
required bool state,
}) async {
if (characteristic is! MyGattCharacteristic2) {
throw TypeError();
}
final peripheral = characteristic.peripheral;
final addressArgs = peripheral.address;
final hashCodeArgs = characteristic.hashCode;
final stateArgs = state
? characteristic.properties.contains(GattCharacteristicProperty.notify)
? MyGattCharacteristicNotifyStateArgs.notify
: MyGattCharacteristicNotifyStateArgs.indicate
: MyGattCharacteristicNotifyStateArgs.none;
final stateNumberArgs = stateArgs.index;
logger.info(
'setCharacteristicNotifyState: $addressArgs.$hashCodeArgs - $stateArgs');
await _api.setCharacteristicNotifyState(
'writeCharacteristic: $addressArgs.$hashCodeArgs - $valueArgs, $typeArgs');
await _api.writeCharacteristic(
addressArgs,
hashCodeArgs,
stateNumberArgs,
valueArgs,
typeArgs,
);
}
@override
Future<Uint8List> readDescriptor(GattDescriptor descriptor) async {
if (descriptor is! MyGattDescriptor2) {
Future<void> setCharacteristicNotifyState(
Peripheral peripheral,
GATTCharacteristic characteristic, {
required bool state,
}) async {
if (peripheral is! MyPeripheral ||
characteristic is! MyGATTCharacteristic) {
throw TypeError();
}
final peripheral = descriptor.peripheral;
final addressArgs = peripheral.address;
final hashCodeArgs = descriptor.hashCode;
final addressArgs = peripheral.addressArgs;
final hashCodeArgs = characteristic.hashCodeArgs;
final enableArgs = state;
logger.info(
'setCharacteristicNotification: $addressArgs.$hashCodeArgs - $enableArgs');
await _api.setCharacteristicNotification(
addressArgs,
hashCodeArgs,
enableArgs,
);
// Seems the docs is not correct, this operation is necessary for all characteristics.
// https://developer.android.com/guide/topics/connectivity/bluetooth/transfer-ble-data#notification
final descriptor = characteristic.descriptors
.firstWhere((descriptor) => descriptor.uuid == cccUUID);
final value = state
? characteristic.properties.contains(GATTCharacteristicProperty.notify)
? _args.enableNotificationValue
: _args.enableIndicationValue
: _args.disableNotificationValue;
await writeDescriptor(
peripheral,
descriptor,
value: value,
);
}
@override
Future<Uint8List> readDescriptor(
Peripheral peripheral,
GATTDescriptor descriptor,
) async {
if (peripheral is! MyPeripheral || descriptor is! MyGATTDescriptor) {
throw TypeError();
}
final addressArgs = peripheral.addressArgs;
final hashCodeArgs = descriptor.hashCodeArgs;
logger.info('readDescriptor: $addressArgs.$hashCodeArgs');
final value = await _api.readDescriptor(addressArgs, hashCodeArgs);
return value;
@ -235,24 +283,22 @@ class MyCentralManager extends CentralManager
@override
Future<void> writeDescriptor(
GattDescriptor descriptor, {
Peripheral peripheral,
GATTDescriptor descriptor, {
required Uint8List value,
}) async {
if (descriptor is! MyGattDescriptor2) {
if (peripheral is! MyPeripheral || descriptor is! MyGATTDescriptor) {
throw TypeError();
}
final peripheral = descriptor.peripheral;
final addressArgs = peripheral.address;
final hashCodeArgs = descriptor.hashCode;
final trimmedValueArgs = value.trimGATT();
logger.info(
'writeDescriptor: $addressArgs.$hashCodeArgs - $trimmedValueArgs');
await _api.writeDescriptor(addressArgs, hashCodeArgs, trimmedValueArgs);
final addressArgs = peripheral.addressArgs;
final hashCodeArgs = descriptor.hashCodeArgs;
final valueArgs = value;
logger.info('writeDescriptor: $addressArgs.$hashCodeArgs - $valueArgs');
await _api.writeDescriptor(addressArgs, hashCodeArgs, valueArgs);
}
@override
void onStateChanged(int stateNumberArgs) {
final stateArgs = MyBluetoothLowEnergyStateArgs.values[stateNumberArgs];
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs) {
logger.info('onStateChanged: $stateArgs');
final state = stateArgs.toState();
if (_state == state) {
@ -271,10 +317,7 @@ class MyCentralManager extends CentralManager
) {
final addressArgs = peripheralArgs.addressArgs;
logger.info('onDiscovered: $addressArgs - $rssiArgs, $advertisementArgs');
final peripheral = _peripherals.putIfAbsent(
peripheralArgs.addressArgs,
() => peripheralArgs.toPeripheral(),
);
final peripheral = peripheralArgs.toPeripheral();
final rssi = rssiArgs;
final advertisement = advertisementArgs.toAdvertisement();
final eventArgs = DiscoveredEventArgs(
@ -286,56 +329,76 @@ class MyCentralManager extends CentralManager
}
@override
void onConnectionStateChanged(String addressArgs, bool stateArgs) {
void onConnectionStateChanged(
MyPeripheralArgs peripheralArgs,
MyConnectionStateArgs stateArgs,
) {
final addressArgs = peripheralArgs.addressArgs;
logger.info('onConnectionStateChanged: $addressArgs - $stateArgs');
final peripheral = _peripherals[addressArgs];
if (peripheral == null) {
return;
}
final state = stateArgs;
final eventArgs = ConnectionStateChangedEventArgs(peripheral, state);
_connectionStateChangedController.add(eventArgs);
if (!state) {
_characteristics.remove(addressArgs);
final peripheral = peripheralArgs.toPeripheral();
final state = stateArgs.toState();
if (state == ConnectionState.disconnected) {
_mtus.remove(addressArgs);
}
final eventArgs = PeripheralConnectionStateChangedEventArgs(
peripheral,
state,
);
_connectionStateChangedController.add(eventArgs);
}
@override
void onMtuChanged(String addressArgs, int mtuArgs) {
logger.info('onMtuChanged: $addressArgs - $mtuArgs');
void onMTUChanged(MyPeripheralArgs peripheralArgs, int mtuArgs) {
final addressArgs = peripheralArgs.addressArgs;
logger.info('onMTUChanged: $addressArgs - $mtuArgs');
final peripheral = peripheralArgs.toPeripheral();
final mtu = mtuArgs;
_mtus[addressArgs] = mtu;
final eventArgs = PeripheralMTUChangedEventArgs(peripheral, mtu);
_mtuChangedController.add(eventArgs);
}
@override
void onCharacteristicNotified(
String addressArgs,
int hashCodeArgs,
MyPeripheralArgs peripheralArgs,
MyGATTCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
) {
final addressArgs = peripheralArgs.addressArgs;
final hashCodeArgs = characteristicArgs.hashCodeArgs;
logger.info(
'onCharacteristicNotified: $addressArgs.$hashCodeArgs - $valueArgs');
final characteristic = _retrieveCharacteristic(addressArgs, hashCodeArgs);
if (characteristic == null) {
return;
}
final peripheral = peripheralArgs.toPeripheral();
final characteristic = characteristicArgs.toCharacteristic();
final value = valueArgs;
final eventArgs = GattCharacteristicNotifiedEventArgs(
final eventArgs = GATTCharacteristicNotifiedEventArgs(
peripheral,
characteristic,
value,
);
_characteristicNotifiedController.add(eventArgs);
}
MyGattCharacteristic2? _retrieveCharacteristic(
String addressArgs,
int hashCodeArgs,
) {
final characteristics = _characteristics[addressArgs];
if (characteristics == null) {
return null;
Future<void> _initialize() async {
// Here we use `Future()` to make it possible to change the `logLevel` before `initialize()`.
await Future(() async {
try {
logger.info('initialize');
_args = await _api.initialize();
_getState();
} catch (e) {
logger.severe('initialize failed.', e);
}
});
}
Future<void> _getState() async {
try {
logger.info('getState');
final stateArgs = await _api.getState();
onStateChanged(stateArgs);
} catch (e) {
logger.severe('getState failed.', e);
}
return characteristics[hashCodeArgs];
}
}

View File

@ -0,0 +1,95 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
final class MyGATTDescriptor extends GATTDescriptor {
final int hashCodeArgs;
MyGATTDescriptor({
required this.hashCodeArgs,
required super.uuid,
});
@override
int get hashCode => hashCodeArgs;
@override
bool operator ==(Object other) {
return other is MyGATTDescriptor && other.hashCodeArgs == hashCodeArgs;
}
}
final class MyGATTCharacteristic extends GATTCharacteristic {
final int hashCodeArgs;
MyGATTCharacteristic({
required this.hashCodeArgs,
required super.uuid,
required super.properties,
required List<MyGATTDescriptor> descriptors,
}) : super(
descriptors: descriptors,
);
@override
List<MyGATTDescriptor> get descriptors =>
super.descriptors.cast<MyGATTDescriptor>();
@override
int get hashCode => hashCodeArgs;
@override
bool operator ==(Object other) {
return other is MyGATTCharacteristic && other.hashCodeArgs == hashCodeArgs;
}
}
final class MyGATTService extends GATTService {
final int hashCodeArgs;
MyGATTService({
required this.hashCodeArgs,
required super.uuid,
required super.isPrimary,
required List<MyGATTService> includedServices,
required List<MyGATTCharacteristic> characteristics,
}) : super(
includedServices: includedServices,
characteristics: characteristics,
);
@override
List<MyGATTCharacteristic> get characteristics =>
super.characteristics.cast<MyGATTCharacteristic>();
@override
int get hashCode => hashCodeArgs;
@override
bool operator ==(Object other) {
return other is MyGATTService && other.hashCodeArgs == hashCodeArgs;
}
}
final class MyGATTReadRequest extends GATTReadRequest {
final String addressArgs;
final int idArgs;
MyGATTReadRequest({
required this.addressArgs,
required this.idArgs,
required super.offset,
});
}
final class MyGATTWriteRequest extends GATTWriteRequest {
final String addressArgs;
final int idArgs;
final bool responseNeededArgs;
MyGATTWriteRequest({
required this.addressArgs,
required this.idArgs,
required this.responseNeededArgs,
required super.offset,
required super.value,
});
}

View File

@ -1,29 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_descriptor2.dart';
import 'my_peripheral2.dart';
class MyGattCharacteristic2 extends MyGattCharacteristic {
final MyPeripheral2 peripheral;
@override
final int hashCode;
MyGattCharacteristic2({
required this.peripheral,
required this.hashCode,
required super.uuid,
required super.properties,
required List<MyGattDescriptor2> descriptors,
}) : super(descriptors: descriptors);
@override
List<MyGattDescriptor2> get descriptors =>
super.descriptors.cast<MyGattDescriptor2>();
@override
bool operator ==(Object other) {
return other is MyGattCharacteristic2 &&
other.peripheral == peripheral &&
other.hashCode == hashCode;
}
}

View File

@ -1,22 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_peripheral2.dart';
class MyGattDescriptor2 extends MyGattDescriptor {
final MyPeripheral2 peripheral;
@override
final int hashCode;
MyGattDescriptor2({
required this.peripheral,
required this.hashCode,
required super.uuid,
});
@override
bool operator ==(Object other) {
return other is MyGattDescriptor2 &&
other.peripheral == peripheral &&
other.hashCode == hashCode;
}
}

View File

@ -1,28 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
import 'my_gatt_characteristic2.dart';
import 'my_peripheral2.dart';
class MyGattService2 extends MyGattService {
final MyPeripheral2 peripheral;
@override
final int hashCode;
MyGattService2({
required this.peripheral,
required this.hashCode,
required super.uuid,
required List<MyGattCharacteristic2> characteristics,
}) : super(characteristics: characteristics);
@override
List<MyGattCharacteristic2> get characteristics =>
super.characteristics.cast<MyGattCharacteristic2>();
@override
bool operator ==(Object other) {
return other is MyGattService2 &&
other.peripheral == peripheral &&
other.hashCode == hashCode;
}
}

View File

@ -0,0 +1,11 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
final class MyPeripheral extends Peripheral {
final String addressArgs;
MyPeripheral({
required this.addressArgs,
}) : super(
uuid: UUID.fromAddress(addressArgs),
);
}

View File

@ -1,11 +0,0 @@
import 'package:bluetooth_low_energy_platform_interface/bluetooth_low_energy_platform_interface.dart';
class MyPeripheral2 extends MyPeripheral {
final String address;
MyPeripheral2({
required this.address,
}) : super(
uuid: UUID.fromAddress(address),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,16 @@
// Run with `dart run pigeon --input my_api.dart`.
import 'package:pigeon/pigeon.dart';
// TODO: Use `@ProxyApi` to manage instancs when this feature released:
// https://github.com/flutter/flutter/issues/147486
@ConfigurePigeon(
PigeonOptions(
dartOut: 'lib/src/my_api.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/src/main/kotlin/dev/yanshouwang/bluetooth_low_energy_android/MyApi.g.kt',
'android/src/main/kotlin/dev/hebei/bluetooth_low_energy_android/MyAPI.g.kt',
kotlinOptions: KotlinOptions(
package: 'dev.yanshouwang.bluetooth_low_energy_android',
package: 'dev.hebei.bluetooth_low_energy_android',
),
),
)
@ -21,7 +24,14 @@ enum MyBluetoothLowEnergyStateArgs {
turningOff,
}
enum MyGattCharacteristicPropertyArgs {
enum MyConnectionStateArgs {
disconnected,
connecting,
connected,
disconnecting,
}
enum MyGATTCharacteristicPropertyArgs {
read,
write,
writeWithoutResponse,
@ -29,30 +39,56 @@ enum MyGattCharacteristicPropertyArgs {
indicate,
}
enum MyGattCharacteristicWriteTypeArgs {
enum MyGATTCharacteristicPermissionArgs {
read,
readEncrypted,
write,
writeEncrypted,
}
enum MyGATTCharacteristicWriteTypeArgs {
withResponse,
withoutResponse,
}
enum MyGattCharacteristicNotifyStateArgs {
none,
notify,
indicate,
}
enum MyGattStatusArgs {
enum MyGATTStatusArgs {
success,
readNotPermitted,
writeNotPermitted,
requestNotSupported,
invalidOffset,
insufficientAuthentication,
requestNotSupported,
insufficientEncryption,
invalidOffset,
insufficientAuthorization,
invalidAttributeLength,
connectionCongested,
failure,
}
class MyCentralManagerArgs {
final Uint8List enableNotificationValue;
final Uint8List enableIndicationValue;
final Uint8List disableNotificationValue;
MyCentralManagerArgs(
this.enableNotificationValue,
this.enableIndicationValue,
this.disableNotificationValue,
);
}
class MyPeripheralManagerArgs {
final Uint8List enableNotificationValue;
final Uint8List enableIndicationValue;
final Uint8List disableNotificationValue;
MyPeripheralManagerArgs(
this.enableNotificationValue,
this.enableIndicationValue,
this.disableNotificationValue,
);
}
class MyManufacturerSpecificDataArgs {
final int idArgs;
final Uint8List dataArgs;
@ -64,7 +100,7 @@ class MyAdvertisementArgs {
final String? nameArgs;
final List<String?> serviceUUIDsArgs;
final Map<String?, Uint8List?> serviceDataArgs;
final MyManufacturerSpecificDataArgs? manufacturerSpecificDataArgs;
final List<MyManufacturerSpecificDataArgs?> manufacturerSpecificDataArgs;
MyAdvertisementArgs(
this.nameArgs,
@ -86,25 +122,23 @@ class MyPeripheralArgs {
MyPeripheralArgs(this.addressArgs);
}
class MyGattDescriptorArgs {
class MyGATTDescriptorArgs {
final int hashCodeArgs;
final String uuidArgs;
final Uint8List? valueArgs;
MyGattDescriptorArgs(
MyGATTDescriptorArgs(
this.hashCodeArgs,
this.uuidArgs,
this.valueArgs,
);
}
class MyGattCharacteristicArgs {
class MyGATTCharacteristicArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<int?> propertyNumbersArgs;
final List<MyGattDescriptorArgs?> descriptorsArgs;
final List<MyGATTDescriptorArgs?> descriptorsArgs;
MyGattCharacteristicArgs(
MyGATTCharacteristicArgs(
this.hashCodeArgs,
this.uuidArgs,
this.propertyNumbersArgs,
@ -112,35 +146,88 @@ class MyGattCharacteristicArgs {
);
}
class MyGattServiceArgs {
class MyGATTServiceArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<MyGattCharacteristicArgs?> characteristicsArgs;
final bool isPrimaryArgs;
final List<MyGATTServiceArgs?> includedServicesArgs;
final List<MyGATTCharacteristicArgs?> characteristicsArgs;
MyGattServiceArgs(
MyGATTServiceArgs(
this.hashCodeArgs,
this.uuidArgs,
this.isPrimaryArgs,
this.includedServicesArgs,
this.characteristicsArgs,
);
}
class MyMutableGATTDescriptorArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<int?> permissionNumbersArgs;
MyMutableGATTDescriptorArgs(
this.hashCodeArgs,
this.uuidArgs,
this.permissionNumbersArgs,
);
}
class MyMutableGATTCharacteristicArgs {
final int hashCodeArgs;
final String uuidArgs;
final List<int?> permissionNumbersArgs;
final List<int?> propertyNumbersArgs;
final List<MyMutableGATTDescriptorArgs?> descriptorsArgs;
MyMutableGATTCharacteristicArgs(
this.hashCodeArgs,
this.uuidArgs,
this.permissionNumbersArgs,
this.propertyNumbersArgs,
this.descriptorsArgs,
);
}
class MyMutableGATTServiceArgs {
final int hashCodeArgs;
final String uuidArgs;
final bool isPrimaryArgs;
final List<MyMutableGATTServiceArgs?> includedServicesArgs;
final List<MyMutableGATTCharacteristicArgs?> characteristicsArgs;
MyMutableGATTServiceArgs(
this.hashCodeArgs,
this.uuidArgs,
this.isPrimaryArgs,
this.includedServicesArgs,
this.characteristicsArgs,
);
}
@HostApi()
abstract class MyCentralManagerHostApi {
abstract class MyCentralManagerHostAPI {
MyCentralManagerArgs initialize();
MyBluetoothLowEnergyStateArgs getState();
@async
void setUp();
bool authorize();
@async
void startDiscovery();
void showAppSettings();
@async
void startDiscovery(List<String> serviceUUIDsArgs);
void stopDiscovery();
@async
void connect(String addressArgs);
@async
void disconnect(String addressArgs);
List<MyPeripheralArgs> retrieveConnectedPeripherals();
@async
int requestMTU(String addressArgs, int mtuArgs);
@async
int readRSSI(String addressArgs);
@async
List<MyGattServiceArgs> discoverServices(String addressArgs);
List<MyGATTServiceArgs> discoverGATT(String addressArgs);
@async
Uint8List readCharacteristic(String addressArgs, int hashCodeArgs);
@async
@ -148,13 +235,12 @@ abstract class MyCentralManagerHostApi {
String addressArgs,
int hashCodeArgs,
Uint8List valueArgs,
int typeNumberArgs,
MyGATTCharacteristicWriteTypeArgs typeArgs,
);
@async
void setCharacteristicNotifyState(
void setCharacteristicNotification(
String addressArgs,
int hashCodeArgs,
int stateNumberArgs,
bool enableArgs,
);
@async
Uint8List readDescriptor(String addressArgs, int hashCodeArgs);
@ -167,91 +253,99 @@ abstract class MyCentralManagerHostApi {
}
@FlutterApi()
abstract class MyCentralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
abstract class MyCentralManagerFlutterAPI {
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs);
void onDiscovered(
MyPeripheralArgs peripheralArgs,
int rssiArgs,
MyAdvertisementArgs advertisementArgs,
);
void onConnectionStateChanged(String addressArgs, bool stateArgs);
void onMtuChanged(String addressArgs, int mtuArgs);
void onConnectionStateChanged(
MyPeripheralArgs peripheralArgs,
MyConnectionStateArgs stateArgs,
);
void onMTUChanged(MyPeripheralArgs peripheralArgs, int mtuArgs);
void onCharacteristicNotified(
String addressArgs,
int hashCodeArgs,
MyPeripheralArgs peripheralArgs,
MyGATTCharacteristicArgs characteristicArgs,
Uint8List valueArgs,
);
}
@HostApi()
abstract class MyPeripheralManagerHostApi {
abstract class MyPeripheralManagerHostAPI {
MyPeripheralManagerArgs initialize();
MyBluetoothLowEnergyStateArgs getState();
@async
void setUp();
bool authorize();
@async
void addService(MyGattServiceArgs serviceArgs);
void showAppSettings();
void openGATTServer();
void closeGATTServer();
@async
void addService(MyMutableGATTServiceArgs serviceArgs);
void removeService(int hashCodeArgs);
void clearServices();
void removeAllServices();
@async
void startAdvertising(MyAdvertisementArgs advertisementArgs);
void stopAdvertising();
void sendResponse(
String addressArgs,
int idArgs,
int statusNumberArgs,
MyGATTStatusArgs statusArgs,
int offsetArgs,
Uint8List? valueArgs,
);
@async
void notifyCharacteristicChanged(
int hashCodeArgs,
Uint8List valueArgs,
bool confirmArgs,
String addressArgs,
int hashCodeArgs,
bool confirmArgs,
Uint8List valueArgs,
);
}
@FlutterApi()
abstract class MyPeripheralManagerFlutterApi {
void onStateChanged(int stateNumberArgs);
void onConnectionStateChanged(MyCentralArgs centralArgs, bool stateArgs);
void onMtuChanged(String addressArgs, int mtuArgs);
abstract class MyPeripheralManagerFlutterAPI {
void onStateChanged(MyBluetoothLowEnergyStateArgs stateArgs);
void onConnectionStateChanged(
MyCentralArgs centralArgs,
int statusArgs,
MyConnectionStateArgs stateArgs,
);
void onMTUChanged(MyCentralArgs centralArgs, int mtuArgs);
void onCharacteristicReadRequest(
String addressArgs,
int hashCodeArgs,
MyCentralArgs centralArgs,
int idArgs,
int offsetArgs,
int hashCodeArgs,
);
void onCharacteristicWriteRequest(
String addressArgs,
int hashCodeArgs,
MyCentralArgs centralArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
int hashCodeArgs,
bool preparedWriteArgs,
bool responseNeededArgs,
);
void onCharacteristicNotifyStateChanged(
String addressArgs,
int hashCodeArgs,
int stateNumberArgs,
int offsetArgs,
Uint8List valueArgs,
);
void onDescriptorReadRequest(
String addressArgs,
int hashCodeArgs,
MyCentralArgs centralArgs,
int idArgs,
int offsetArgs,
int hashCodeArgs,
);
void onDescriptorWriteRequest(
String addressArgs,
int hashCodeArgs,
MyCentralArgs centralArgs,
int idArgs,
int offsetArgs,
Uint8List valueArgs,
int hashCodeArgs,
bool preparedWriteArgs,
bool responseNeededArgs,
int offsetArgs,
Uint8List valueArgs,
);
void onExecuteWrite(
String addressArgs,
MyCentralArgs centralArgs,
int idArgs,
bool executeArgs,
);

View File

@ -1,28 +1,38 @@
name: bluetooth_low_energy_android
description: Android implementation of the bluetooth_low_energy plugin.
version: 5.0.5
description: "Android implementation of the bluetooth_low_energy plugin."
version: 6.0.0
homepage: https://github.com/yanshouwang/bluetooth_low_energy
repository: https://github.com/yanshouwang/bluetooth_low_energy
issue_tracker: https://github.com/yanshouwang/bluetooth_low_energy/issues
topics:
- bluetooth
- bluetooth-low-energy
- ble
funding:
- https://paypal.me/yanshouwang5112
- https://afdian.net/a/yanshouwang
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.0.0'
dependencies:
flutter:
sdk: flutter
bluetooth_low_energy_platform_interface: ^5.0.2
bluetooth_low_energy_platform_interface: ^6.0.0
collection: ^1.18.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
pigeon: ^15.0.2
flutter_lints: ^4.0.0
pigeon: ^19.0.0
flutter:
plugin:
implements: bluetooth_low_energy
platforms:
android:
package: dev.yanshouwang.bluetooth_low_energy_android
pluginClass: BluetoothLowEnergyAndroid
dartPluginClass: BluetoothLowEnergyAndroid
package: dev.hebei.bluetooth_low_energy_android
pluginClass: BluetoothLowEnergyAndroidPlugin
dartPluginClass: BluetoothLowEnergyAndroidPlugin

View File

@ -0,0 +1 @@
void main() {}