Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ android/keystores/debug.keystore
.turbo/

# generated by bob
lib/
dist/

# React Native Codegen
ios/generated
Expand Down
216 changes: 216 additions & 0 deletions android/src/main/java/so/onekey/lib/ble/utils/BleUtilsModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package so.onekey.lib.ble.utils

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile.GATT
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import so.onekey.lib.ble.utils.data.Peripheral


class BleUtilsModule(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

private var bluetoothManager: BluetoothManager? = null
private val mBleBroadcastReceiver = lazy {
MyBroadcastReceiver(this)
}

override fun getName() = NAME

private fun getBluetoothManager(): BluetoothManager? {
if (bluetoothManager == null) {
bluetoothManager =
reactContext.getSystemService(Context.BLUETOOTH_SERVICE)
}
return bluetoothManager
}

private fun getBluetoothAdapter(): BluetoothAdapter? {
return getBluetoothManager()?.adapter
}

init {
registerBluetoothReceiver()
}

private fun registerBluetoothReceiver() {
if (getBluetoothAdapter() == null) {
Log.d(LOG_TAG, "No bluetooth support")
return
}

val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
val intentFilter = IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
intentFilter.priority = IntentFilter.SYSTEM_HIGH_PRIORITY
if (Build.VERSION.SDK_INT >= 34) {
// Google in 2023 decides that flag RECEIVER_NOT_EXPORTED or RECEIVER_EXPORTED should be explicit set SDK 34(UPSIDE_DOWN_CAKE) on registering receivers.
// Also the export flags are available on Android 8 and higher, should be used with caution so that don't break compability with that devices.
reactContext.registerReceiver(mBleBroadcastReceiver.value, filter, Context.RECEIVER_EXPORTED)
reactContext.registerReceiver(
mBleBroadcastReceiver.value,
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
reactContext.registerReceiver(mBleBroadcastReceiver.value, filter)
reactContext.registerReceiver(mBleBroadcastReceiver.value, intentFilter)
}
Log.d(LOG_TAG, "BleManager initialized")
}

// 向JS端发送设备绑定状态变化事件
fun emitOnDeviceBondState(params: WritableMap) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("onDeviceBondState", params)
}

// 事件监听管理
@ReactMethod
fun addListener(eventName: String) {
// 实现为空,仅用于满足RN事件监听器注册需求
Log.d(LOG_TAG, "addListener $eventName")
}

@ReactMethod
fun removeListeners(count: Int) {
// 实现为空,仅用于满足RN事件监听器注册需求
Log.d(LOG_TAG, "removeListeners $count")
}

@ReactMethod
fun checkState(callback: Callback) {
Log.d(LOG_TAG, "checkState")

val adapter = getBluetoothAdapter()
var state = "off"
if (!reactContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
state = "unsupported"
} else if (adapter != null) {
when (adapter.state) {
BluetoothAdapter.STATE_ON -> state = "on"
BluetoothAdapter.STATE_TURNING_ON -> state = "turning_on"
BluetoothAdapter.STATE_TURNING_OFF -> {
state = "turning_off"
}

BluetoothAdapter.STATE_OFF -> {
// should not happen as per https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#getState()
state = "off"
}

else -> {
state = "off"
}
}
}

val map: WritableMap = Arguments.createMap()
map.putString("state", state)
Log.d(LOG_TAG, "state:$state")
callback.invoke(state)
}

@SuppressLint("MissingPermission")
@ReactMethod
fun getBondedPeripherals(callback: Callback) {
val map: WritableArray = Arguments.createArray()
val deviceSet: Set<BluetoothDevice> = getBluetoothAdapter()?.getBondedDevices() ?: emptySet()
for (device in deviceSet) {
val peripheral = Peripheral(device)
val jsonBundle: WritableMap = peripheral.asWritableMap()
map.pushMap(jsonBundle)
}
callback.invoke(null, map)
}

@SuppressLint("MissingPermission")
@ReactMethod
fun getConnectedPeripherals(serviceUUIDs: ReadableArray?, callback: Callback) {
Log.d(LOG_TAG, "Get connected peripherals")
val map: WritableArray = Arguments.createArray()

if (getBluetoothAdapter() == null) {
Log.d(LOG_TAG, "No bluetooth support")
callback.invoke("No bluetooth support")
return
}

val peripherals: List<BluetoothDevice> =
getBluetoothManager()?.getConnectedDevices(GATT) ?: emptyList()
for (entry in peripherals) {
val peripheral = Peripheral(entry)
val jsonBundle: WritableMap = peripheral.asWritableMap()
map.pushMap(jsonBundle)
}
callback.invoke(null, map)
}

private class MyBroadcastReceiver(private val module: BleUtilsModule) : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
Log.d(LOG_TAG, "onReceive")
val action = intent.action

if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
val prevState = intent.getIntExtra(
BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR
)
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java
)
} else {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}

var bondStateStr = "UNKNOWN"
when (bondState) {
BluetoothDevice.BOND_BONDED -> bondStateStr = "BOND_BONDED"
BluetoothDevice.BOND_BONDING -> bondStateStr = "BOND_BONDING"
BluetoothDevice.BOND_NONE -> bondStateStr = "BOND_NONE"
BluetoothDevice.ERROR -> bondStateStr = "BOND_ERROR"
}

var prevBondStateStr = "UNKNOWN"
when (prevState) {
BluetoothDevice.BOND_BONDED -> prevBondStateStr = "BOND_BONDED"
BluetoothDevice.BOND_BONDING -> prevBondStateStr = "BOND_BONDING"
BluetoothDevice.BOND_NONE -> prevBondStateStr = "BOND_NONE"
BluetoothDevice.ERROR -> bondStateStr = "BOND_ERROR"
}
Log.d(LOG_TAG, "bond state: $bondStateStr")
Log.d(LOG_TAG, "bond state: $prevBondStateStr")

val bond = Arguments.createMap()
bond.putString("state", bondStateStr)
bond.putString("preState", prevBondStateStr)

val peripheral = Peripheral(device!!)
val map = peripheral.asWritableMap()
map.putMap("bondState", bond)
Log.d(LOG_TAG, "onReceive BluetoothDevice BondState Change ${map}")
module.emitOnDeviceBondState(map)
}
}
}

companion object {
const val NAME = "BleUtilsModule"
const val LOG_TAG: String = "RNBleUtils"
}
}
17 changes: 17 additions & 0 deletions android/src/main/java/so/onekey/lib/ble/utils/BleUtilsPackage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package so.onekey.lib.ble.utils

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager


class BleUtilsPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(BleUtilsModule(reactContext))
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
35 changes: 35 additions & 0 deletions android/src/main/java/so/onekey/lib/ble/utils/data/Peripheral.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package so.onekey.lib.ble.utils.data

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.util.Log
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap


class Peripheral(
private val device: BluetoothDevice
) {
@SuppressLint("MissingPermission")
fun asWritableMap(): WritableMap {
val map: WritableMap = Arguments.createMap()
val advertising: WritableMap = Arguments.createMap()

try {
map.putString("name", device.getName())
map.putString("id", device.getAddress()) // mac address

val name: String = device.getName()
if (name != null) advertising.putString("localName", name)

// No scanResult to access so we can't check if peripheral is connectable
advertising.putBoolean("isConnectable", true)

map.putMap("advertising", advertising)
} catch (e: Exception) { // this shouldn't happen
Log.e("BleUtils", "Unexpected error on asWritableMap", e)
}

return map
}
}
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "@onekeyfe/react-native-ble-utils",
"version": "0.1.0",
"version": "0.1.1",
"description": "ble uilts",
"source": "./src/index.tsx",
"main": "./lib/commonjs/index.js",
"module": "./lib/module/index.js",
"main": "./dist/commonjs/index.js",
"module": "./dist/module/index.js",
"react-native": "src/index.ts",
"types": "./lib/typescript/module/src/index.d.ts",
"types": "./dist/typescript/module/src/index.d.ts",
"files": [
"src",
"lib",
"dist",
"android",
"ios",
"cpp",
Expand All @@ -31,7 +31,7 @@
"test": "jest",
"typecheck": "tsc",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build dist",
"prepare": "bob build",
"release": "release-it"
},
Expand Down Expand Up @@ -90,7 +90,7 @@
"preset": "react-native",
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
"<rootDir>/dist/"
]
},
"commitlint": {
Expand Down Expand Up @@ -137,7 +137,7 @@
},
"eslintIgnore": [
"node_modules/",
"lib/"
"dist/"
],
"prettier": {
"quoteProps": "consistent",
Expand All @@ -148,7 +148,7 @@
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"output": "dist",
"targets": [
[
"commonjs",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "./tsconfig",
"exclude": ["example", "lib"]
"exclude": ["example", "dist"]
}
Loading