munim-bluetooth 0.3.26 → 0.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.munimbluetooth
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
|
|
8
|
+
internal object BluetoothPermissionUtils {
|
|
9
|
+
fun requiredPermissions(): Array<String> {
|
|
10
|
+
return when {
|
|
11
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> arrayOf(
|
|
12
|
+
Manifest.permission.BLUETOOTH_SCAN,
|
|
13
|
+
Manifest.permission.BLUETOOTH_CONNECT,
|
|
14
|
+
Manifest.permission.BLUETOOTH_ADVERTISE
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> arrayOf(
|
|
18
|
+
Manifest.permission.ACCESS_FINE_LOCATION
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
else -> emptyArray()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun missingPermissions(context: Context): Array<String> {
|
|
26
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
27
|
+
return emptyArray()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return requiredPermissions()
|
|
31
|
+
.filter { permission ->
|
|
32
|
+
context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED
|
|
33
|
+
}
|
|
34
|
+
.toTypedArray()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun hasRequiredPermissions(context: Context): Boolean {
|
|
38
|
+
return missingPermissions(context).isEmpty()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -23,6 +23,7 @@ import android.bluetooth.le.ScanResult
|
|
|
23
23
|
import android.bluetooth.le.ScanSettings
|
|
24
24
|
import android.content.Context
|
|
25
25
|
import android.content.Intent
|
|
26
|
+
import android.content.pm.PackageManager
|
|
26
27
|
import android.os.Build
|
|
27
28
|
import android.os.ParcelUuid
|
|
28
29
|
import android.util.Log
|
|
@@ -30,6 +31,8 @@ import com.facebook.react.bridge.Arguments
|
|
|
30
31
|
import com.facebook.react.bridge.WritableArray
|
|
31
32
|
import com.facebook.react.bridge.WritableMap
|
|
32
33
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
34
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
35
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
33
36
|
import com.margelo.nitro.NitroModules
|
|
34
37
|
import com.margelo.nitro.core.Promise
|
|
35
38
|
import com.margelo.nitro.munimbluetooth.AdvertisingDataTypes
|
|
@@ -80,6 +83,7 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
80
83
|
private val lastCharacteristicValues = mutableMapOf<String, CharacteristicValue>()
|
|
81
84
|
private val lastRssiValues = mutableMapOf<String, Double>()
|
|
82
85
|
private val eventEmitter = NitroEventEmitter(TAG)
|
|
86
|
+
private var nextPermissionRequestCode = BLUETOOTH_PERMISSION_REQUEST_CODE
|
|
83
87
|
|
|
84
88
|
private fun getBluetoothManager(): BluetoothManager? {
|
|
85
89
|
val context = NitroModules.applicationContext ?: return null
|
|
@@ -93,7 +97,35 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
private fun hasRequiredBluetoothPermissions(): Boolean {
|
|
101
|
+
val context = NitroModules.applicationContext ?: return false
|
|
102
|
+
return BluetoothPermissionUtils.hasRequiredPermissions(context)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun ensureBluetoothPermissions(operationName: String): Boolean {
|
|
106
|
+
val context = NitroModules.applicationContext
|
|
107
|
+
if (context == null) {
|
|
108
|
+
Log.w(TAG, "Unable to $operationName: React context unavailable")
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
val missingPermissions = BluetoothPermissionUtils.missingPermissions(context)
|
|
113
|
+
if (missingPermissions.isNotEmpty()) {
|
|
114
|
+
Log.w(
|
|
115
|
+
TAG,
|
|
116
|
+
"Unable to $operationName: missing Bluetooth permissions (${missingPermissions.joinToString()})"
|
|
117
|
+
)
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true
|
|
122
|
+
}
|
|
123
|
+
|
|
96
124
|
override fun startAdvertising(options: AdvertisingOptions) {
|
|
125
|
+
if (!ensureBluetoothPermissions("start advertising")) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
ensureBluetoothManager()
|
|
98
130
|
val adapter = bluetoothAdapter
|
|
99
131
|
if (adapter == null || !adapter.isEnabled) {
|
|
@@ -115,7 +147,12 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
115
147
|
)
|
|
116
148
|
|
|
117
149
|
if (!currentLocalName.isNullOrBlank() && previousAdapterName == null) {
|
|
118
|
-
previousAdapterName =
|
|
150
|
+
previousAdapterName = try {
|
|
151
|
+
adapter.name
|
|
152
|
+
} catch (error: SecurityException) {
|
|
153
|
+
Log.w(TAG, "Unable to read Bluetooth adapter name", error)
|
|
154
|
+
null
|
|
155
|
+
}
|
|
119
156
|
}
|
|
120
157
|
if (!currentLocalName.isNullOrBlank()) {
|
|
121
158
|
try {
|
|
@@ -161,6 +198,10 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
161
198
|
}
|
|
162
199
|
|
|
163
200
|
override fun setServices(services: Array<GATTService>) {
|
|
201
|
+
if (!ensureBluetoothPermissions("set GATT services")) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
164
205
|
ensureBluetoothManager()
|
|
165
206
|
gattServerReady = false
|
|
166
207
|
|
|
@@ -197,15 +238,63 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
197
238
|
}
|
|
198
239
|
|
|
199
240
|
override fun isBluetoothEnabled(): Promise<Boolean> {
|
|
241
|
+
if (!hasRequiredBluetoothPermissions()) {
|
|
242
|
+
return Promise.resolved(false)
|
|
243
|
+
}
|
|
244
|
+
|
|
200
245
|
ensureBluetoothManager()
|
|
201
246
|
return Promise.resolved(bluetoothAdapter?.isEnabled == true)
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
override fun requestBluetoothPermission(): Promise<Boolean> {
|
|
205
|
-
|
|
250
|
+
val context = NitroModules.applicationContext ?: run {
|
|
251
|
+
Log.w(TAG, "Unable to request Bluetooth permissions: React context unavailable")
|
|
252
|
+
return Promise.resolved(false)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
val missingPermissions = BluetoothPermissionUtils.missingPermissions(context)
|
|
256
|
+
if (missingPermissions.isEmpty()) {
|
|
257
|
+
return Promise.resolved(true)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
val activity = context.currentActivity as? PermissionAwareActivity
|
|
261
|
+
if (activity == null) {
|
|
262
|
+
Log.w(TAG, "Unable to request Bluetooth permissions: current activity unavailable")
|
|
263
|
+
return Promise.resolved(false)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
val requestCode = nextPermissionRequestCode++
|
|
267
|
+
val promise = Promise<Boolean>()
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
activity.requestPermissions(
|
|
271
|
+
missingPermissions,
|
|
272
|
+
requestCode,
|
|
273
|
+
PermissionListener { callbackRequestCode, _, grantResults ->
|
|
274
|
+
if (callbackRequestCode != requestCode) {
|
|
275
|
+
return@PermissionListener false
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
val isGranted =
|
|
279
|
+
grantResults.isNotEmpty() &&
|
|
280
|
+
grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
|
281
|
+
promise.resolve(isGranted)
|
|
282
|
+
true
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
} catch (error: IllegalStateException) {
|
|
286
|
+
Log.w(TAG, "Unable to request Bluetooth permissions", error)
|
|
287
|
+
promise.resolve(false)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return promise
|
|
206
291
|
}
|
|
207
292
|
|
|
208
293
|
override fun startScan(options: ScanOptions?) {
|
|
294
|
+
if (!ensureBluetoothPermissions("start scanning")) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
209
298
|
ensureBluetoothManager()
|
|
210
299
|
val adapter = bluetoothAdapter
|
|
211
300
|
if (adapter == null || !adapter.isEnabled) {
|
|
@@ -278,6 +367,10 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
278
367
|
}
|
|
279
368
|
|
|
280
369
|
override fun connect(deviceId: String): Promise<Unit> {
|
|
370
|
+
if (!ensureBluetoothPermissions("connect to BLE device")) {
|
|
371
|
+
return Promise.rejected(IllegalStateException("Bluetooth permissions not granted"))
|
|
372
|
+
}
|
|
373
|
+
|
|
281
374
|
ensureBluetoothManager()
|
|
282
375
|
connectedDevices[deviceId]?.let { existingGatt ->
|
|
283
376
|
if (existingGatt.services != null) {
|
|
@@ -453,6 +546,10 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
453
546
|
return
|
|
454
547
|
}
|
|
455
548
|
|
|
549
|
+
if (!ensureBluetoothPermissions("start background BLE session")) {
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
456
553
|
val intent = Intent(context, MunimBluetoothBackgroundService::class.java).apply {
|
|
457
554
|
action = MunimBluetoothBackgroundService.ACTION_START
|
|
458
555
|
putExtra(
|
|
@@ -517,6 +614,10 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
517
614
|
}
|
|
518
615
|
|
|
519
616
|
private fun restartAdvertising(delayMs: Long) {
|
|
617
|
+
if (!ensureBluetoothPermissions("restart advertising")) {
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
|
|
520
621
|
ensureBluetoothManager()
|
|
521
622
|
val adapter = bluetoothAdapter
|
|
522
623
|
if (adapter == null || !adapter.isEnabled) {
|
|
@@ -1027,6 +1128,7 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
1027
1128
|
|
|
1028
1129
|
companion object {
|
|
1029
1130
|
private const val TAG = "HybridMunimBluetooth"
|
|
1131
|
+
private const val BLUETOOTH_PERMISSION_REQUEST_CODE = 9137
|
|
1030
1132
|
private val CLIENT_CHARACTERISTIC_CONFIG_UUID =
|
|
1031
1133
|
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
|
1032
1134
|
}
|
|
@@ -100,19 +100,38 @@ class MunimBluetoothBackgroundService : Service() {
|
|
|
100
100
|
allowDuplicates: Boolean,
|
|
101
101
|
scanMode: ScanMode
|
|
102
102
|
) {
|
|
103
|
+
if (!BluetoothPermissionUtils.hasRequiredPermissions(applicationContext)) {
|
|
104
|
+
Log.w(TAG, "Unable to start background BLE session: missing runtime permissions")
|
|
105
|
+
stopSelf()
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
bluetoothManager =
|
|
104
110
|
applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
|
|
105
111
|
bluetoothAdapter = bluetoothManager?.adapter
|
|
106
112
|
|
|
107
113
|
val adapter = bluetoothAdapter
|
|
108
|
-
|
|
114
|
+
val isEnabled = try {
|
|
115
|
+
adapter?.isEnabled == true
|
|
116
|
+
} catch (error: SecurityException) {
|
|
117
|
+
Log.w(TAG, "Unable to inspect Bluetooth adapter state for background session", error)
|
|
118
|
+
false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (adapter == null || !isEnabled) {
|
|
109
122
|
Log.w(TAG, "Unable to start background BLE session: Bluetooth unavailable")
|
|
110
123
|
stopSelf()
|
|
111
124
|
return
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
if (!localName.isNullOrBlank() && previousAdapterName == null) {
|
|
115
|
-
previousAdapterName =
|
|
128
|
+
previousAdapterName = try {
|
|
129
|
+
adapter.name
|
|
130
|
+
} catch (error: SecurityException) {
|
|
131
|
+
Log.w(TAG, "Unable to read adapter name for background advertising", error)
|
|
132
|
+
null
|
|
133
|
+
}
|
|
134
|
+
|
|
116
135
|
try {
|
|
117
136
|
adapter.name = localName
|
|
118
137
|
} catch (error: SecurityException) {
|
|
@@ -193,7 +212,11 @@ class MunimBluetoothBackgroundService : Service() {
|
|
|
193
212
|
}
|
|
194
213
|
}
|
|
195
214
|
|
|
196
|
-
|
|
215
|
+
try {
|
|
216
|
+
activeAdvertiser.startAdvertising(settings, data.build(), advertiseCallback)
|
|
217
|
+
} catch (error: SecurityException) {
|
|
218
|
+
Log.w(TAG, "Unable to start background advertising", error)
|
|
219
|
+
}
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
private fun startScan(
|
|
@@ -242,7 +265,11 @@ class MunimBluetoothBackgroundService : Service() {
|
|
|
242
265
|
}
|
|
243
266
|
}
|
|
244
267
|
|
|
245
|
-
|
|
268
|
+
try {
|
|
269
|
+
activeScanner.startScan(filters, settingsBuilder.build(), scanCallback)
|
|
270
|
+
} catch (error: SecurityException) {
|
|
271
|
+
Log.w(TAG, "Unable to start background scan", error)
|
|
272
|
+
}
|
|
246
273
|
}
|
|
247
274
|
|
|
248
275
|
private fun handleScanResult(result: ScanResult, allowDuplicates: Boolean) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "munim-bluetooth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.27",
|
|
4
4
|
"description": "A comprehensive React Native library for all your Bluetooth Low Energy (BLE) needs, supporting both peripheral and central roles with Expo support",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|