* 调整接口

* 临时提交

* 重构 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

@ -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)
}
}