Skip to content

Commit

Permalink
feat(android): add createBond and isBonded
Browse files Browse the repository at this point in the history
* feat(android): add createBond and isBonded

* chore(): fix swiftlint scripts

* fix(web): remove unused options
  • Loading branch information
pwespi authored Apr 10, 2021
1 parent 94d5dca commit c9e8688
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 42 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Below is an index of all the methods available.
- [`requestLEScan(...)`](#requestlescan)
- [`stopLEScan()`](#stoplescan)
- [`connect(...)`](#connect)
- [`createBond(...)`](#createbond)
- [`isBonded(...)`](#isbonded)
- [`disconnect(...)`](#disconnect)
- [`read(...)`](#read)
- [`write(...)`](#write)
Expand Down Expand Up @@ -409,6 +411,38 @@ Connect to a peripheral BLE device. For an example, see [usage](#usage).

---

### createBond(...)

```typescript
createBond(deviceId: string) => Promise<void>
```

Create a bond with a peripheral BLE device.
Only available on Android.

| Param | Type | Description |
| -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- |
| **`deviceId`** | <code>string</code> | The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan)) |

---

### isBonded(...)

```typescript
isBonded(deviceId: string) => Promise<boolean>
```

Report whether a peripheral BLE device is bonded.
Only available on Android.

| Param | Type | Description |
| -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- |
| **`deviceId`** | <code>string</code> | The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan)) |

**Returns:** <code>Promise&lt;boolean&gt;</code>

---

### disconnect(...)

```typescript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,12 @@ class BluetoothLe : Plugin() {
@PluginMethod
fun startEnabledNotifications(call: PluginCall) {
assertBluetoothAdapter(call) ?: return
createStateReceiver()

try {
val intentFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
context.registerReceiver(stateReceiver, intentFilter);
createStateReceiver()
} catch (e: Error) {
call.reject("Error")
Log.e(TAG, "Error while registering enabled state receiver: ${e.localizedMessage}")
call.reject("startEnabledNotifications failed.")
return
}
call.resolve()
Expand All @@ -99,6 +98,8 @@ class BluetoothLe : Plugin() {
}
}
}
val intentFilter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
context.registerReceiver(stateReceiver, intentFilter)
}
}

Expand Down Expand Up @@ -199,27 +200,7 @@ class BluetoothLe : Plugin() {

@PluginMethod
fun connect(call: PluginCall) {
assertBluetoothAdapter(call) ?: return
val deviceId = call.getString("deviceId", null)
if (deviceId == null) {
call.reject("deviceId required.")
return
}

val device: Device
try {
device = Device(
activity.applicationContext,
bluetoothAdapter!!,
deviceId
) { ->
onDisconnect(deviceId)
}
} catch (e: IllegalArgumentException) {
call.reject("Invalid deviceId")
return
}
deviceMap[deviceId] = device
val device = getOrCreateDevice(call) ?: return
device.connect { response ->
run {
if (response.success) {
Expand All @@ -235,6 +216,29 @@ class BluetoothLe : Plugin() {
notifyListeners("disconnected|${deviceId}", null)
}

@PluginMethod
fun createBond(call: PluginCall) {
val device = getOrCreateDevice(call) ?: return
device.createBond { response ->
run {
if (response.success) {
call.resolve()
} else {
call.reject(response.value)
}
}
}
}

@PluginMethod
fun isBonded(call: PluginCall) {
val device = getOrCreateDevice(call) ?: return
val isBonded = device.isBonded()
val result = JSObject()
result.put("value", isBonded)
call.resolve(result)
}

@PluginMethod
fun disconnect(call: PluginCall) {
val device = getDevice(call) ?: return
Expand Down Expand Up @@ -479,14 +483,41 @@ class BluetoothLe : Plugin() {
)
}


private fun getDevice(call: PluginCall): Device? {
assertBluetoothAdapter(call) ?: return null
private fun getDeviceId(call: PluginCall): String? {
val deviceId = call.getString("deviceId", null)
if (deviceId == null) {
call.reject("deviceId required.")
return null
}
return deviceId
}

private fun getOrCreateDevice(call: PluginCall): Device? {
assertBluetoothAdapter(call) ?: return null
val deviceId = getDeviceId(call) ?: return null
val device = deviceMap[deviceId]
if (device != null) {
return device
}
try {
val newDevice = Device(
activity.applicationContext,
bluetoothAdapter!!,
deviceId
) { ->
onDisconnect(deviceId)
}
deviceMap[deviceId] = newDevice
return newDevice
} catch (e: IllegalArgumentException) {
call.reject("Invalid deviceId")
return null
}
}

private fun getDevice(call: PluginCall): Device? {
assertBluetoothAdapter(call) ?: return null
val deviceId = getDeviceId(call) ?: return null
val device = deviceMap[deviceId]
if (device == null || !device.isConnected()) {
call.reject("Not connected to device.")
Expand All @@ -495,7 +526,6 @@ class BluetoothLe : Plugin() {
return device
}


private fun getCharacteristic(call: PluginCall): Pair<UUID, UUID>? {
val serviceString = call.getString("service", null)
val serviceUUID: UUID?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.capacitorjs.community.plugins.bluetoothle

import android.bluetooth.*
import android.bluetooth.BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.util.Log
import java.util.*
Expand Down Expand Up @@ -36,6 +38,7 @@ class Device(
private var callbackMap = HashMap<String, ((CallbackResponse) -> Unit)>()
private var timeoutMap = HashMap<String, Handler>()
private var setNotificationsKey = ""
private var bondStateReceiver: BroadcastReceiver? = null

private val gattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(
Expand Down Expand Up @@ -174,6 +177,62 @@ class Device(
}
}

fun createBond(callback: (CallbackResponse) -> Unit) {
val key = "createBond"
callbackMap[key] = callback
try {
createBondStateReceiver()
} catch (e: Error) {
Log.e(TAG, "Error while registering bondStateReceiver: ${e.localizedMessage}")
reject(key, "Creating bond failed.")
return
}
val result = device.createBond()
if (!result) {
reject(key, "Creating bond failed.")
return
}
// if already bonded, resolve immediately
if (isBonded()) {
resolve(key, "Creating bond succeeded.")
return
}
// otherwise, wait for bond state change
}

private fun createBondStateReceiver() {
if (bondStateReceiver == null) {
bondStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val key = "createBond"
val updatedDevice = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
// BroadcastReceiver receives bond state updates from all devices, need to filter by device
if (device.address == updatedDevice?.address) {
val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1)
val previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
Log.d(TAG, "Bond state transition $previousBondState -> $bondState")
if (bondState == BluetoothDevice.BOND_BONDED) {
resolve(key, "Creating bond succeeded.")
} else if (previousBondState == BluetoothDevice.BOND_BONDING && bondState == BluetoothDevice.BOND_NONE) {
reject(key, "Creating bond failed.")
} else if (bondState == -1) {
reject(key, "Creating bond failed.")
}
}
}
}
}
val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
context.registerReceiver(bondStateReceiver, intentFilter)
}
}

fun isBonded(): Boolean {
return device.bondState == BluetoothDevice.BOND_BONDED
}

fun disconnect(callback: (CallbackResponse) -> Unit) {
callbackMap["disconnect"] = callback
if (bluetoothGatt == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.capacitorjs.community.plugins.bluetoothle

import android.bluetooth.BluetoothDevice
import java.util.ArrayList
import java.util.*

class DeviceList {
private val devices: ArrayList<BluetoothDevice> = ArrayList()
Expand Down
2 changes: 2 additions & 0 deletions ios/Plugin/Plugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
CAP_PLUGIN_METHOD(requestLEScan, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(stopLEScan, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(connect, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(createBond, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(isBonded, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(disconnect, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(read, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(write, CAPPluginReturnPromise);
Expand Down
8 changes: 8 additions & 0 deletions ios/Plugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ public class BluetoothLe: CAPPlugin {

}

@objc func createBond(_ call: CAPPluginCall) {
call.reject("Unavailable")
}

@objc func isBonded(_ call: CAPPluginCall) {
call.reject("Unavailable")
}

@objc func disconnect(_ call: CAPPluginCall) {
guard self.getDeviceManager(call) != nil else { return }
guard let device = self.getDevice(call) else { return }
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"verify:ios": "cd ios && pod install && xcodebuild clean build test -workspace Plugin.xcworkspace -scheme Plugin -destination \"platform=iOS Simulator,name=iPhone 12\" && cd ..",
"verify:android": "cd android && ./gradlew clean build test && cd ..",
"verify:web": "npm run build",
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- autocorrect --format",
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint ios",
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- lint --fix --format ios",
"eslint": "eslint . --ext ts",
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
"swiftlint": "node-swiftlint",
Expand Down
28 changes: 28 additions & 0 deletions src/bleClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ export interface BleClientInterface {
onDisconnect?: (deviceId: string) => void,
): Promise<void>;

/**
* Create a bond with a peripheral BLE device.
* Only available on Android.
* @param deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))
*/
createBond(deviceId: string): Promise<void>;

/**
* Report whether a peripheral BLE device is bonded.
* Only available on Android.
* @param deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))
*/
isBonded(deviceId: string): Promise<boolean>;

/**
* Disconnect from a peripheral BLE device. For an example, see [usage](#usage).
* @param deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))
Expand Down Expand Up @@ -250,6 +264,20 @@ class BleClientClass implements BleClientInterface {
});
}

async createBond(deviceId: string): Promise<void> {
await this.queue(async () => {
await BluetoothLe.createBond({ deviceId });
});
}

async isBonded(deviceId: string): Promise<boolean> {
const isBonded = await this.queue(async () => {
const result = await BluetoothLe.isBonded({ deviceId });
return result.value;
});
return isBonded;
}

async disconnect(deviceId: string): Promise<void> {
await this.queue(async () => {
await BluetoothLe.disconnect({ deviceId });
Expand Down
Loading

0 comments on commit c9e8688

Please sign in to comment.