Update Universal BLE Plugin for Peripheral Support and Code Generation

- Updated autogenerated files to reflect changes from Pigeon v26.3.4.
- Added new classes for UniversalBlePeripheralConfig, UniversalBlePeripheralService, UniversalBlePeripheralCharacteristic, UniversalBlePeripheralDescriptor, and UniversalBlePeripheralWriteEvent to handle peripheral configurations and events.
- Modified UniversalBlePlugin to implement methods for peripheral support, including StartPeripheral, StopPeripheral, UpdatePeripheralCharacteristicValue, and NotifyPeripheralCharacteristic, returning appropriate error messages for unsupported features on Windows.
- Updated HasPermissions method to include an additional parameter for Bluetooth advertising permissions.
- Adjusted method signatures and implementations across the plugin to accommodate new peripheral functionality.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-08 03:36:12 +08:00
parent dde6c084b7
commit 1d7108c9d0
26 changed files with 5522 additions and 1183 deletions
+1
View File
@@ -7,4 +7,5 @@
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
</manifest>
@@ -35,12 +35,12 @@ class PermissionHandler(
/**
* Check if we have required permissions
*/
fun hasPermissions(withFineLocation: Boolean): Boolean {
val validationError = validateRequiredPermissions(withFineLocation)
fun hasPermissions(withFineLocation: Boolean, withAdvertise: Boolean): Boolean {
val validationError = validateRequiredPermissions(withFineLocation, withAdvertise)
if (validationError != null) {
throw validationError
}
val permissionsToRequest = getRequiredPermissions(withFineLocation)
val permissionsToRequest = getRequiredPermissions(withFineLocation, withAdvertise)
return permissionsToRequest.isEmpty()
}
@@ -55,17 +55,18 @@ class PermissionHandler(
*/
fun requestPermissions(
withFineLocation: Boolean,
withAdvertise: Boolean,
callback: (Result<Unit>) -> Unit,
) {
// Validate required permissions are declared in manifest
val validationError = validateRequiredPermissions(withFineLocation)
val validationError = validateRequiredPermissions(withFineLocation, withAdvertise)
if (validationError != null) {
callback(Result.failure(validationError))
return
}
// Check which permissions need to be requested
val permissionsToRequest = getRequiredPermissions(withFineLocation)
val permissionsToRequest = getRequiredPermissions(withFineLocation, withAdvertise)
if (permissionsToRequest.isEmpty()) {
// All required permissions are already granted
@@ -163,7 +164,7 @@ class PermissionHandler(
*
* Returns a list of permissions that need to be requested (excluding already granted ones)
*/
private fun getRequiredPermissions(withFineLocation: Boolean): List<String> {
private fun getRequiredPermissions(withFineLocation: Boolean, withAdvertise: Boolean): List<String> {
val permissionsToRequest = mutableListOf<String>()
// Android 12+ (API 31+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -175,6 +176,9 @@ class PermissionHandler(
if (!hasPermissionGranted(Manifest.permission.BLUETOOTH_CONNECT)) {
permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT)
}
if (withAdvertise && !hasPermissionGranted(Manifest.permission.BLUETOOTH_ADVERTISE)) {
permissionsToRequest.add(Manifest.permission.BLUETOOTH_ADVERTISE)
}
// Location permission is optional - only request if user wants it
if (withFineLocation) {
// Prefer ACCESS_FINE_LOCATION over ACCESS_COARSE_LOCATION
@@ -236,7 +240,7 @@ class PermissionHandler(
* @param withFineLocation Whether location permission should be requested
* @return FlutterError if validation fails, null if all required permissions are declared
*/
private fun validateRequiredPermissions(withFineLocation: Boolean): FlutterError? {
private fun validateRequiredPermissions(withFineLocation: Boolean, withAdvertise: Boolean): FlutterError? {
val sdkInt = Build.VERSION.SDK_INT
val missingPermissions = mutableListOf<String>()
@@ -259,6 +263,10 @@ class PermissionHandler(
missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT)
}
if (withAdvertise && !hasPermissionInManifest(Manifest.permission.BLUETOOTH_ADVERTISE)) {
missingPermissions.add(Manifest.permission.BLUETOOTH_ADVERTISE)
}
// Location permission is optional on Android 12+ (depends on neverForLocation and withFineLocation)
// Only validate if it's actually needed
if (withFineLocation && !hasDeclaredLocationPermission) {
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v26.1.4), do not edit directly.
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
@@ -37,36 +37,150 @@ private object UniversalBlePigeonUtils {
)
}
}
fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}
fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}
fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}
fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}
fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
return a.contentEquals(b)
}
if (a is IntArray && b is IntArray) {
return a.contentEquals(b)
return a.contentEquals(b)
}
if (a is LongArray && b is LongArray) {
return a.contentEquals(b)
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
return a.contentEquals(b)
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
return a.size == b.size &&
a.indices.all{ deepEquals(a[it], b[it]) }
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
return a.size == b.size && a.all {
(b as Map<Any?, Any?>).contains(it.key) &&
deepEquals(it.value, b[it.key])
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}
fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}
/**
@@ -79,7 +193,7 @@ class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
) : RuntimeException()
enum class UniversalBleLogLevel(val raw: Int) {
NONE(0),
@@ -219,15 +333,28 @@ data class UniversalBleScanResult (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalBleScanResult) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalBleScanResult
return UniversalBlePigeonUtils.deepEquals(this.deviceId, other.deviceId) && UniversalBlePigeonUtils.deepEquals(this.name, other.name) && UniversalBlePigeonUtils.deepEquals(this.isPaired, other.isPaired) && UniversalBlePigeonUtils.deepEquals(this.rssi, other.rssi) && UniversalBlePigeonUtils.deepEquals(this.manufacturerDataList, other.manufacturerDataList) && UniversalBlePigeonUtils.deepEquals(this.serviceData, other.serviceData) && UniversalBlePigeonUtils.deepEquals(this.services, other.services) && UniversalBlePigeonUtils.deepEquals(this.timestamp, other.timestamp)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.deviceId)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.name)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.isPaired)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.rssi)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.manufacturerDataList)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.serviceData)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.services)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.timestamp)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -250,15 +377,22 @@ data class UniversalBleService (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalBleService) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalBleService
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.characteristics, other.characteristics)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristics)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -284,15 +418,23 @@ data class UniversalBleCharacteristic (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalBleCharacteristic) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalBleCharacteristic
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.properties, other.properties) && UniversalBlePigeonUtils.deepEquals(this.descriptors, other.descriptors)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.properties)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.descriptors)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -312,15 +454,235 @@ data class UniversalBleDescriptor (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalBleDescriptor) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalBleDescriptor
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBlePeripheralConfig (
val advertisedName: String,
val services: List<UniversalBlePeripheralService>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBlePeripheralConfig {
val advertisedName = pigeonVar_list[0] as String
val services = pigeonVar_list[1] as List<UniversalBlePeripheralService>
return UniversalBlePeripheralConfig(advertisedName, services)
}
}
fun toList(): List<Any?> {
return listOf(
advertisedName,
services,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as UniversalBlePeripheralConfig
return UniversalBlePigeonUtils.deepEquals(this.advertisedName, other.advertisedName) && UniversalBlePigeonUtils.deepEquals(this.services, other.services)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.advertisedName)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.services)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBlePeripheralService (
val uuid: String,
val characteristics: List<UniversalBlePeripheralCharacteristic>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBlePeripheralService {
val uuid = pigeonVar_list[0] as String
val characteristics = pigeonVar_list[1] as List<UniversalBlePeripheralCharacteristic>
return UniversalBlePeripheralService(uuid, characteristics)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
characteristics,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as UniversalBlePeripheralService
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.characteristics, other.characteristics)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristics)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBlePeripheralCharacteristic (
val uuid: String,
val properties: List<Long>,
val permissions: List<Long>,
val descriptors: List<UniversalBlePeripheralDescriptor>,
val initialValue: ByteArray? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBlePeripheralCharacteristic {
val uuid = pigeonVar_list[0] as String
val properties = pigeonVar_list[1] as List<Long>
val permissions = pigeonVar_list[2] as List<Long>
val descriptors = pigeonVar_list[3] as List<UniversalBlePeripheralDescriptor>
val initialValue = pigeonVar_list[4] as ByteArray?
return UniversalBlePeripheralCharacteristic(uuid, properties, permissions, descriptors, initialValue)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
properties,
permissions,
descriptors,
initialValue,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as UniversalBlePeripheralCharacteristic
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.properties, other.properties) && UniversalBlePigeonUtils.deepEquals(this.permissions, other.permissions) && UniversalBlePigeonUtils.deepEquals(this.descriptors, other.descriptors) && UniversalBlePigeonUtils.deepEquals(this.initialValue, other.initialValue)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.properties)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.permissions)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.descriptors)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.initialValue)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBlePeripheralDescriptor (
val uuid: String,
val permissions: List<Long>,
val initialValue: ByteArray? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBlePeripheralDescriptor {
val uuid = pigeonVar_list[0] as String
val permissions = pigeonVar_list[1] as List<Long>
val initialValue = pigeonVar_list[2] as ByteArray?
return UniversalBlePeripheralDescriptor(uuid, permissions, initialValue)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
permissions,
initialValue,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as UniversalBlePeripheralDescriptor
return UniversalBlePigeonUtils.deepEquals(this.uuid, other.uuid) && UniversalBlePigeonUtils.deepEquals(this.permissions, other.permissions) && UniversalBlePigeonUtils.deepEquals(this.initialValue, other.initialValue)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.uuid)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.permissions)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.initialValue)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBlePeripheralWriteEvent (
val deviceId: String,
val service: String,
val characteristic: String,
val value: ByteArray
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBlePeripheralWriteEvent {
val deviceId = pigeonVar_list[0] as String
val service = pigeonVar_list[1] as String
val characteristic = pigeonVar_list[2] as String
val value = pigeonVar_list[3] as ByteArray
return UniversalBlePeripheralWriteEvent(deviceId, service, characteristic, value)
}
}
fun toList(): List<Any?> {
return listOf(
deviceId,
service,
characteristic,
value,
)
}
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
val other = other as UniversalBlePeripheralWriteEvent
return UniversalBlePigeonUtils.deepEquals(this.deviceId, other.deviceId) && UniversalBlePigeonUtils.deepEquals(this.service, other.service) && UniversalBlePigeonUtils.deepEquals(this.characteristic, other.characteristic) && UniversalBlePigeonUtils.deepEquals(this.value, other.value)
}
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.deviceId)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.service)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.characteristic)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.value)
return result
}
}
/**
@@ -355,15 +717,23 @@ data class AndroidOptions (
)
}
override fun equals(other: Any?): Boolean {
if (other !is AndroidOptions) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as AndroidOptions
return UniversalBlePigeonUtils.deepEquals(this.requestLocationPermission, other.requestLocationPermission) && UniversalBlePigeonUtils.deepEquals(this.scanMode, other.scanMode) && UniversalBlePigeonUtils.deepEquals(this.reportDelayMillis, other.reportDelayMillis)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.requestLocationPermission)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.scanMode)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.reportDelayMillis)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -383,15 +753,21 @@ data class UniversalScanConfig (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalScanConfig) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalScanConfig
return UniversalBlePigeonUtils.deepEquals(this.android, other.android)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.android)
return result
}
}
/**
@@ -421,15 +797,23 @@ data class UniversalScanFilter (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalScanFilter) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalScanFilter
return UniversalBlePigeonUtils.deepEquals(this.withServices, other.withServices) && UniversalBlePigeonUtils.deepEquals(this.withNamePrefix, other.withNamePrefix) && UniversalBlePigeonUtils.deepEquals(this.withManufacturerData, other.withManufacturerData)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withServices)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withNamePrefix)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.withManufacturerData)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -455,15 +839,23 @@ data class UniversalManufacturerDataFilter (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalManufacturerDataFilter) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalManufacturerDataFilter
return UniversalBlePigeonUtils.deepEquals(this.companyIdentifier, other.companyIdentifier) && UniversalBlePigeonUtils.deepEquals(this.data, other.data) && UniversalBlePigeonUtils.deepEquals(this.mask, other.mask)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.companyIdentifier)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.mask)
return result
}
}
/** Generated class from Pigeon that represents data sent in messages. */
@@ -486,15 +878,22 @@ data class UniversalManufacturerData (
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalManufacturerData) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
val other = other as UniversalManufacturerData
return UniversalBlePigeonUtils.deepEquals(this.companyIdentifier, other.companyIdentifier) && UniversalBlePigeonUtils.deepEquals(this.data, other.data)
}
override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.companyIdentifier)
result = 31 * result + UniversalBlePigeonUtils.deepHash(this.data)
return result
}
}
private open class UniversalBlePigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
@@ -536,25 +935,50 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
}
136.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AndroidOptions.fromList(it)
UniversalBlePeripheralConfig.fromList(it)
}
}
137.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanConfig.fromList(it)
UniversalBlePeripheralService.fromList(it)
}
}
138.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanFilter.fromList(it)
UniversalBlePeripheralCharacteristic.fromList(it)
}
}
139.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
UniversalBlePeripheralDescriptor.fromList(it)
}
}
140.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalBlePeripheralWriteEvent.fromList(it)
}
}
141.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AndroidOptions.fromList(it)
}
}
142.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanConfig.fromList(it)
}
}
143.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanFilter.fromList(it)
}
}
144.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
}
}
145.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerData.fromList(it)
}
@@ -592,26 +1016,46 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
stream.write(135)
writeValue(stream, value.toList())
}
is AndroidOptions -> {
is UniversalBlePeripheralConfig -> {
stream.write(136)
writeValue(stream, value.toList())
}
is UniversalScanConfig -> {
is UniversalBlePeripheralService -> {
stream.write(137)
writeValue(stream, value.toList())
}
is UniversalScanFilter -> {
is UniversalBlePeripheralCharacteristic -> {
stream.write(138)
writeValue(stream, value.toList())
}
is UniversalManufacturerDataFilter -> {
is UniversalBlePeripheralDescriptor -> {
stream.write(139)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
is UniversalBlePeripheralWriteEvent -> {
stream.write(140)
writeValue(stream, value.toList())
}
is AndroidOptions -> {
stream.write(141)
writeValue(stream, value.toList())
}
is UniversalScanConfig -> {
stream.write(142)
writeValue(stream, value.toList())
}
is UniversalScanFilter -> {
stream.write(143)
writeValue(stream, value.toList())
}
is UniversalManufacturerDataFilter -> {
stream.write(144)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
stream.write(145)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
@@ -625,8 +1069,8 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
*/
interface UniversalBlePlatformChannel {
fun getBluetoothAvailabilityState(callback: (Result<Long>) -> Unit)
fun hasPermissions(withAndroidFineLocation: Boolean): Boolean
fun requestPermissions(withAndroidFineLocation: Boolean, callback: (Result<Unit>) -> Unit)
fun hasPermissions(withAndroidFineLocation: Boolean, withAndroidBluetoothAdvertise: Boolean): Boolean
fun requestPermissions(withAndroidFineLocation: Boolean, withAndroidBluetoothAdvertise: Boolean, callback: (Result<Unit>) -> Unit)
fun enableBluetooth(callback: (Result<Boolean>) -> Unit)
fun disableBluetooth(callback: (Result<Boolean>) -> Unit)
fun startScan(filter: UniversalScanFilter?, config: UniversalScanConfig?)
@@ -646,6 +1090,11 @@ interface UniversalBlePlatformChannel {
fun getConnectionState(deviceId: String): Long
fun readRssi(deviceId: String, callback: (Result<Long>) -> Unit)
fun requestConnectionPriority(deviceId: String, priority: Long, callback: (Result<Unit>) -> Unit)
fun isPeripheralSupported(): Boolean
fun startPeripheral(config: UniversalBlePeripheralConfig, callback: (Result<Unit>) -> Unit)
fun stopPeripheral(callback: (Result<Unit>) -> Unit)
fun updatePeripheralCharacteristicValue(service: String, characteristic: String, value: ByteArray, callback: (Result<Unit>) -> Unit)
fun notifyPeripheralCharacteristic(service: String, characteristic: String, value: ByteArray, indicate: Boolean, callback: (Result<Unit>) -> Unit)
fun setLogLevel(logLevel: UniversalBleLogLevel)
companion object {
@@ -681,8 +1130,9 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val withAndroidFineLocationArg = args[0] as Boolean
val withAndroidBluetoothAdvertiseArg = args[1] as Boolean
val wrapped: List<Any?> = try {
listOf(api.hasPermissions(withAndroidFineLocationArg))
listOf(api.hasPermissions(withAndroidFineLocationArg, withAndroidBluetoothAdvertiseArg))
} catch (exception: Throwable) {
UniversalBlePigeonUtils.wrapError(exception)
}
@@ -698,7 +1148,8 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val withAndroidFineLocationArg = args[0] as Boolean
api.requestPermissions(withAndroidFineLocationArg) { result: Result<Unit> ->
val withAndroidBluetoothAdvertiseArg = args[1] as Boolean
api.requestPermissions(withAndroidFineLocationArg, withAndroidBluetoothAdvertiseArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
@@ -1078,6 +1529,100 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPeripheralSupported$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
listOf(api.isPeripheralSupported())
} catch (exception: Throwable) {
UniversalBlePigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startPeripheral$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val configArg = args[0] as UniversalBlePeripheralConfig
api.startPeripheral(configArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
} else {
reply.reply(UniversalBlePigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopPeripheral$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.stopPeripheral{ result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
} else {
reply.reply(UniversalBlePigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.updatePeripheralCharacteristicValue$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val serviceArg = args[0] as String
val characteristicArg = args[1] as String
val valueArg = args[2] as ByteArray
api.updatePeripheralCharacteristicValue(serviceArg, characteristicArg, valueArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
} else {
reply.reply(UniversalBlePigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.notifyPeripheralCharacteristic$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val serviceArg = args[0] as String
val characteristicArg = args[1] as String
val valueArg = args[2] as ByteArray
val indicateArg = args[3] as Boolean
api.notifyPeripheralCharacteristic(serviceArg, characteristicArg, valueArg, indicateArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
} else {
reply.reply(UniversalBlePigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec)
if (api != null) {
@@ -1125,7 +1670,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
fun onPairStateChange(deviceIdArg: String, isPairedArg: Boolean, errorArg: String?, callback: (Result<Unit>) -> Unit)
@@ -1142,7 +1687,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
fun onScanResult(resultArg: UniversalBleScanResult, callback: (Result<Unit>) -> Unit)
@@ -1159,7 +1704,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
fun onValueChanged(deviceIdArg: String, characteristicIdArg: String, valueArg: ByteArray, timestampArg: Long?, callback: (Result<Unit>) -> Unit)
@@ -1176,7 +1721,7 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
fun onConnectionChanged(deviceIdArg: String, connectedArg: Boolean, errorArg: String?, callback: (Result<Unit>) -> Unit)
@@ -1193,7 +1738,58 @@ class UniversalBleCallbackChannel(private val binaryMessenger: BinaryMessenger,
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
fun onPeripheralConnectionChanged(deviceIdArg: String, connectedArg: Boolean, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralConnectionChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, connectedArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
fun onPeripheralWrite(eventArg: UniversalBlePeripheralWriteEvent, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralWrite$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(eventArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
fun onPeripheralSubscriptionChanged(deviceIdArg: String, serviceArg: String, characteristicArg: String, subscribedArg: Boolean, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPeripheralSubscriptionChanged$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, serviceArg, characteristicArg, subscribedArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
} else {
callback(Result.success(Unit))
}
} else {
callback(Result.failure(UniversalBlePigeonUtils.createConnectionError(channelName)))
}
}
}
}
@@ -9,9 +9,16 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
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.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
@@ -24,6 +31,7 @@ import android.content.IntentFilter
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.ParcelUuid
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -63,6 +71,12 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
private val rssiResultFutureList = mutableListOf<RssiResultFuture>()
private val autoConnectDevices = mutableSetOf<String>()
private var peripheralGattServer: BluetoothGattServer? = null
private var peripheralAdvertiser: BluetoothLeAdvertiser? = null
private var peripheralPendingStartCallback: ((Result<Unit>) -> Unit)? = null
private val peripheralConnectedDevices = mutableSetOf<BluetoothDevice>()
private val peripheralCharacteristics = mutableMapOf<String, BluetoothGattCharacteristic>()
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this)
callbackChannel = UniversalBleCallbackChannel(flutterPluginBinding.binaryMessenger)
@@ -84,6 +98,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback)
stopPeripheralInternal()
context.unregisterReceiver(broadcastReceiver)
callbackChannel = null
mainThreadHandler = null
@@ -99,12 +114,19 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
)
}
override fun hasPermissions(withAndroidFineLocation: Boolean): Boolean {
return permissionHandler?.hasPermissions(withAndroidFineLocation) ?: false
override fun hasPermissions(
withAndroidFineLocation: Boolean,
withAndroidBluetoothAdvertise: Boolean,
): Boolean {
return permissionHandler?.hasPermissions(
withAndroidFineLocation,
withAndroidBluetoothAdvertise,
) ?: false
}
override fun requestPermissions(
withAndroidFineLocation: Boolean,
withAndroidBluetoothAdvertise: Boolean,
callback: (Result<Unit>) -> Unit,
) {
if (permissionHandler == null) {
@@ -117,7 +139,11 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
)
)
}
permissionHandler?.requestPermissions(withAndroidFineLocation, callback)
permissionHandler?.requestPermissions(
withAndroidFineLocation,
withAndroidBluetoothAdvertise,
callback,
)
}
override fun enableBluetooth(callback: (Result<Boolean>) -> Unit) {
@@ -801,6 +827,319 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}
override fun isPeripheralSupported(): Boolean {
val adapter = bluetoothManager.adapter ?: return false
return adapter.isEnabled && adapter.isMultipleAdvertisementSupported
}
override fun startPeripheral(
config: UniversalBlePeripheralConfig,
callback: (Result<Unit>) -> Unit,
) {
val adapter = bluetoothManager.adapter
if (adapter == null || !adapter.isEnabled) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.BLUETOOTH_NOT_ENABLED,
"Bluetooth not enabled"
)
)
)
return
}
if (!adapter.isMultipleAdvertisementSupported) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.NOT_SUPPORTED,
"BLE peripheral advertising is not supported on this device"
)
)
)
return
}
if (peripheralPendingStartCallback != null) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.OPERATION_IN_PROGRESS,
"Peripheral start already in progress"
)
)
)
return
}
stopPeripheralInternal()
val server = bluetoothManager.openGattServer(context, peripheralGattServerCallback)
if (server == null) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.FAILED,
"Unable to open GATT server"
)
)
)
return
}
try {
peripheralCharacteristics.clear()
config.services.forEach { serviceConfig ->
val service = BluetoothGattService(
UUID.fromString(serviceConfig.uuid),
BluetoothGattService.SERVICE_TYPE_PRIMARY,
)
serviceConfig.characteristics.forEach { characteristicConfig ->
val characteristic = BluetoothGattCharacteristic(
UUID.fromString(characteristicConfig.uuid),
characteristicConfig.properties.toPeripheralPropertyFlags(),
characteristicConfig.permissions.toPeripheralPermissionFlags(),
)
@Suppress("DEPRECATION")
characteristic.value = characteristicConfig.initialValue ?: ByteArray(0)
characteristicConfig.descriptors.forEach { descriptorConfig ->
val descriptor = BluetoothGattDescriptor(
UUID.fromString(descriptorConfig.uuid),
descriptorConfig.permissions.toPeripheralPermissionFlags(),
)
@Suppress("DEPRECATION")
descriptor.value = descriptorConfig.initialValue ?: ByteArray(0)
characteristic.addDescriptor(descriptor)
}
if (characteristicConfig.properties.any {
it == CharacteristicProperty.Notify.value ||
it == CharacteristicProperty.Indicate.value
} && characteristic.getDescriptor(ccdCharacteristic) == null
) {
characteristic.addDescriptor(
BluetoothGattDescriptor(
ccdCharacteristic,
BluetoothGattDescriptor.PERMISSION_READ or
BluetoothGattDescriptor.PERMISSION_WRITE,
)
)
}
service.addCharacteristic(characteristic)
peripheralCharacteristics[peripheralCharacteristicKey(serviceConfig.uuid, characteristicConfig.uuid)] = characteristic
}
if (!server.addService(service)) {
throw createFlutterError(
UniversalBleErrorCode.FAILED,
"Failed to add GATT service ${serviceConfig.uuid}"
)
}
}
} catch (e: FlutterError) {
server.close()
peripheralCharacteristics.clear()
callback(Result.failure(e))
return
} catch (e: Exception) {
server.close()
peripheralCharacteristics.clear()
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.FAILED,
"Failed to create GATT services",
e.toString()
)
)
)
return
}
peripheralGattServer = server
try {
adapter.name = config.advertisedName
} catch (_: SecurityException) {
}
val advertiser = adapter.bluetoothLeAdvertiser
if (advertiser == null) {
stopPeripheralInternal()
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.NOT_SUPPORTED,
"BLE advertiser not available"
)
)
)
return
}
val settings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.build()
val dataBuilder = AdvertiseData.Builder().setIncludeDeviceName(true)
config.services.forEach { service ->
dataBuilder.addServiceUuid(ParcelUuid(UUID.fromString(service.uuid)))
}
peripheralAdvertiser = advertiser
peripheralPendingStartCallback = callback
advertiser.startAdvertising(settings, dataBuilder.build(), peripheralAdvertiseCallback)
}
override fun stopPeripheral(callback: (Result<Unit>) -> Unit) {
peripheralPendingStartCallback?.invoke(
Result.failure(
createFlutterError(
UniversalBleErrorCode.OPERATION_CANCELLED,
"Peripheral start cancelled"
)
)
)
peripheralPendingStartCallback = null
stopPeripheralInternal()
callback(Result.success(Unit))
}
override fun updatePeripheralCharacteristicValue(
service: String,
characteristic: String,
value: ByteArray,
callback: (Result<Unit>) -> Unit,
) {
val gattCharacteristic = peripheralCharacteristics[peripheralCharacteristicKey(service, characteristic)]
if (gattCharacteristic == null) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.CHARACTERISTIC_NOT_FOUND,
"Unknown peripheral characteristic $characteristic"
)
)
)
return
}
@Suppress("DEPRECATION")
gattCharacteristic.value = value
callback(Result.success(Unit))
}
override fun notifyPeripheralCharacteristic(
service: String,
characteristic: String,
value: ByteArray,
indicate: Boolean,
callback: (Result<Unit>) -> Unit,
) {
val server = peripheralGattServer
val gattCharacteristic = peripheralCharacteristics[peripheralCharacteristicKey(service, characteristic)]
if (server == null || gattCharacteristic == null) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.CHARACTERISTIC_NOT_FOUND,
"Unknown peripheral characteristic $characteristic"
)
)
)
return
}
@Suppress("DEPRECATION")
gattCharacteristic.value = value
val devices = synchronized(peripheralConnectedDevices) {
peripheralConnectedDevices.toList()
}
devices.forEach { device ->
server.notifyCharacteristicChanged(device, gattCharacteristic, indicate)
}
callback(Result.success(Unit))
}
private fun stopPeripheralInternal() {
peripheralAdvertiser?.stopAdvertising(peripheralAdvertiseCallback)
peripheralAdvertiser = null
peripheralGattServer?.close()
peripheralGattServer = null
synchronized(peripheralConnectedDevices) {
peripheralConnectedDevices.clear()
}
peripheralCharacteristics.clear()
}
private fun peripheralCharacteristicKey(service: String, characteristic: String): String {
return "${service.lowercase()}|${characteristic.lowercase()}"
}
private fun List<Long>.toPeripheralPropertyFlags(): Int {
var flags = 0
forEach { property ->
when (CharacteristicProperty.entries.firstOrNull { it.value == property }) {
CharacteristicProperty.Broadcast -> flags = flags or BluetoothGattCharacteristic.PROPERTY_BROADCAST
CharacteristicProperty.Read -> flags = flags or BluetoothGattCharacteristic.PROPERTY_READ
CharacteristicProperty.WriteWithoutResponse -> flags = flags or BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE
CharacteristicProperty.Write -> flags = flags or BluetoothGattCharacteristic.PROPERTY_WRITE
CharacteristicProperty.Notify -> flags = flags or BluetoothGattCharacteristic.PROPERTY_NOTIFY
CharacteristicProperty.Indicate -> flags = flags or BluetoothGattCharacteristic.PROPERTY_INDICATE
CharacteristicProperty.AuthenticatedSignedWrites -> flags = flags or BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE
CharacteristicProperty.ExtendedProperties -> flags = flags or BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS
null -> {}
}
}
return flags
}
private fun List<Long>.toPeripheralPermissionFlags(): Int {
var flags = 0
forEach { permission ->
when (permission) {
0L -> flags = flags or BluetoothGattCharacteristic.PERMISSION_READ
1L -> flags = flags or BluetoothGattCharacteristic.PERMISSION_WRITE
}
}
return flags
}
private fun callbackPeripheralConnection(deviceId: String, connected: Boolean) {
mainThreadHandler?.post {
callbackChannel?.onPeripheralConnectionChanged(deviceId, connected) {}
}
}
private fun callbackPeripheralSubscription(
deviceId: String,
service: String,
characteristic: String,
subscribed: Boolean,
) {
mainThreadHandler?.post {
callbackChannel?.onPeripheralSubscriptionChanged(
deviceId,
service,
characteristic,
subscribed,
) {}
}
}
private fun callbackPeripheralWrite(
deviceId: String,
service: String,
characteristic: String,
value: ByteArray,
) {
mainThreadHandler?.post {
callbackChannel?.onPeripheralWrite(
UniversalBlePeripheralWriteEvent(
deviceId,
service,
characteristic,
value,
)
) {}
}
}
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
val deviceId = gatt?.device?.address ?: return
mtuResultFutureList.removeAll {
@@ -1191,6 +1530,157 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}
private val peripheralAdvertiseCallback = object : AdvertiseCallback() {
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
peripheralPendingStartCallback?.invoke(Result.success(Unit))
peripheralPendingStartCallback = null
}
override fun onStartFailure(errorCode: Int) {
peripheralPendingStartCallback?.invoke(
Result.failure(
createFlutterError(
UniversalBleErrorCode.FAILED,
"BLE advertising failed with code $errorCode",
errorCode.toString()
)
)
)
peripheralPendingStartCallback = null
stopPeripheralInternal()
}
}
private val peripheralGattServerCallback = object : BluetoothGattServerCallback() {
override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
synchronized(peripheralConnectedDevices) {
peripheralConnectedDevices.add(device)
}
callbackPeripheralConnection(device.address, true)
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
synchronized(peripheralConnectedDevices) {
peripheralConnectedDevices.remove(device)
}
callbackPeripheralConnection(device.address, false)
}
}
override fun onCharacteristicReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
characteristic: BluetoothGattCharacteristic,
) {
@Suppress("DEPRECATION")
val value = characteristic.value ?: ByteArray(0)
if (offset > value.size) {
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_INVALID_OFFSET,
offset,
null,
)
return
}
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_SUCCESS,
offset,
value.copyOfRange(offset, value.size),
)
}
override fun onCharacteristicWriteRequest(
device: BluetoothDevice,
requestId: Int,
characteristic: BluetoothGattCharacteristic,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray,
) {
@Suppress("DEPRECATION")
characteristic.value = value
if (responseNeeded) {
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_SUCCESS,
offset,
null,
)
}
callbackPeripheralWrite(
device.address,
characteristic.service.uuid.toString(),
characteristic.uuid.toString(),
value,
)
}
override fun onDescriptorReadRequest(
device: BluetoothDevice,
requestId: Int,
offset: Int,
descriptor: BluetoothGattDescriptor,
) {
@Suppress("DEPRECATION")
val value = descriptor.value ?: ByteArray(0)
if (offset > value.size) {
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_INVALID_OFFSET,
offset,
null,
)
return
}
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_SUCCESS,
offset,
value.copyOfRange(offset, value.size),
)
}
override fun onDescriptorWriteRequest(
device: BluetoothDevice,
requestId: Int,
descriptor: BluetoothGattDescriptor,
preparedWrite: Boolean,
responseNeeded: Boolean,
offset: Int,
value: ByteArray,
) {
@Suppress("DEPRECATION")
descriptor.value = value
if (responseNeeded) {
peripheralGattServer?.sendResponse(
device,
requestId,
BluetoothGatt.GATT_SUCCESS,
offset,
null,
)
}
if (descriptor.uuid == ccdCharacteristic) {
val subscribed = value.contentEquals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) ||
value.contentEquals(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
callbackPeripheralSubscription(
device.address,
descriptor.characteristic.service.uuid.toString(),
descriptor.characteristic.uuid.toString(),
subscribed,
)
}
}
}
override fun onConnectionStateChange(
gatt: BluetoothGatt,