Skip to content

Commit

Permalink
Merge pull request #843 from peitschie/bonding
Browse files Browse the repository at this point in the history
Implement createBond/getBondState on Android (#605)
  • Loading branch information
peitschie authored Feb 18, 2023
2 parents 18400f5 + 27e1235 commit 58e212a
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 8 deletions.
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ For the best understanding about which permissions are needed for which combinat
- [ble.startScanWithOptions](#startscanwithoptions)
- [ble.stopScan](#stopscan)
- [ble.setPin](#setpin)
- [ble.bond](#bond)
- [ble.unbond](#unbond)
- [ble.readBondState](#readbondstate)
- [ble.connect](#connect)
- [ble.autoConnect](#autoconnect)
- [ble.disconnect](#disconnect)
Expand Down Expand Up @@ -312,6 +315,83 @@ Function `setPin` sets the pin when device requires it.
- **success**: Success callback function that is invoked when the function is invoked. [optional]
- **failure**: Error callback function, invoked when error occurs. [optional]

## bond

Initiate a bond with a remote device

ble.bond(device_id, [success], [failure], [options]);
// Or using await with promises
await ble.withPromises.bond(device_id);
await ble.withPromises.bond(device_id, { usePairingDialog: true });

### Description

Function `bond` initialises a bond with a peripheral. The success callback will be called when the
bonding is complete. The bonding process may prompt the user to confirm the pairing process.

### Parameters

- **device_id**: UUID or MAC address of the peripheral
- **success**: Success callback function that is invoked when the bonding succeeds. [optional]
- **failure**: Error callback function, invoked when the bonding fails. [optional]
- **options**: an object specifying a set of name-value pairs. The currently acceptable options are:
- _usePairingDialog_: _true_ (default) Show pairing request as a dialog rather than a notification [optional]

### Supported Platforms

- Android

## unbond

Remove a bond for a remote device

ble.unbond(device_id, [success], [failure]);
// Or using await with promises
await ble.withPromises.unbond(device_id);

### Description

Function `unbond` removes an existing bond for a remote device. The success callback will be called when the
bond has been removed.

### Parameters

- **device_id**: UUID or MAC address of the peripheral
- **success**: Success callback function that is invoked when the bond is removed. [optional]
- **failure**: Error callback function, invoked when the bond removal fails. [optional]

### Supported Platforms

- Android

## readBondState

Get the bond state of a device as a string

ble.readBondState(device_id, [success], [failure]);
// Or using await with promises
const bondState = await ble.withPromises.readBondState(device_id);

### Description

Function `readBondState` retrieves the current bond state of the device.

**States**

- "none"
- "bonding"
- "bonded"

### Parameters

- **device_id**: UUID or MAC address of the peripheral
- **success**: Success callback function that is invoked with the current bond state. [optional]
- **failure**: Error callback function, invoked when error occurs. [optional]

### Supported Platforms

- Android

## connect

Connect to a peripheral.
Expand Down
166 changes: 159 additions & 7 deletions src/android/BLECentralPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@

import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED;
import static android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE;

public class BLECentralPlugin extends CordovaPlugin {
// permissions
Expand All @@ -75,6 +77,9 @@ public class BLECentralPlugin extends CordovaPlugin {

private static final String QUEUE_CLEANUP = "queueCleanup";
private static final String SET_PIN = "setPin";
private static final String BOND = "bond";
private static final String UNBOND = "unbond";
private static final String READ_BOND_STATE = "readBondState";

private static final String REQUEST_MTU = "requestMtu";
private static final String REQUEST_CONNECTION_PRIORITY = "requestConnectionPriority";
Expand Down Expand Up @@ -130,9 +135,13 @@ public class BLECentralPlugin extends CordovaPlugin {
private static final int REQUEST_BLUETOOTH_CONNECT_AUTO = 4;
private static final int REQUEST_GET_BONDED_DEVICES = 5;
private static final int REQUEST_LIST_KNOWN_DEVICES = 6;
private static final int REQUEST_BOND = 7;
private static final int REQUEST_UNBOND = 8;
private static final int REQUEST_READ_BOND_STATE = 9;
private static int COMPILE_SDK_VERSION = -1;
private CallbackContext permissionCallback;
private String deviceMacAddress;
private boolean usePairingDialog;
private UUID[] serviceUUIDs;
private int scanSeconds;
private ScanSettings scanSettings;
Expand All @@ -141,6 +150,8 @@ public class BLECentralPlugin extends CordovaPlugin {
// Bluetooth state notification
CallbackContext stateCallback;
BroadcastReceiver stateReceiver;
private BroadcastReceiver bondStateReceiver;

Map<Integer, String> bluetoothStates = new Hashtable<Integer, String>() {{
put(BluetoothAdapter.STATE_OFF, "off");
put(BluetoothAdapter.STATE_TURNING_OFF, "turningOff");
Expand All @@ -163,6 +174,7 @@ protected void pluginInitialize() {
public void onDestroy() {
removeStateListener();
removeLocationStateListener();
removeBondStateListener();
for(Peripheral peripheral : peripherals.values()) {
peripheral.disconnect();
}
Expand All @@ -172,6 +184,7 @@ public void onDestroy() {
public void onReset() {
removeStateListener();
removeLocationStateListener();
removeBondStateListener();
for(Peripheral peripheral : peripherals.values()) {
peripheral.disconnect();
}
Expand Down Expand Up @@ -244,6 +257,24 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback
String pin = args.getString(0);
setPin(callbackContext, pin);

} else if (action.equals(BOND)) {

String macAddress = args.getString(0);
JSONObject options = args.getJSONObject(1);

boolean usePairingDialog = options != null && options.optBoolean("usePairingDialog", true);
bond(callbackContext, macAddress, usePairingDialog);

} else if (action.equals(UNBOND)) {

String macAddress = args.getString(0);
unbond(callbackContext, macAddress);

} else if (action.equals(READ_BOND_STATE)) {

String macAddress = args.getString(0);
readBondState(callbackContext, macAddress);

} else if (action.equals(REQUEST_MTU)) {

String macAddress = args.getString(0);
Expand Down Expand Up @@ -827,6 +858,87 @@ public void onReceive(Context context, Intent intent) {
}
}

private void bond(CallbackContext callbackContext, String macAddress, boolean usePairingDialog) {
if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S
List<String> missingPermissions = new ArrayList<String>();
if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) {
missingPermissions.add(BLUETOOTH_CONNECT);
}
if (usePairingDialog && !PermissionHelper.hasPermission(this, BLUETOOTH_SCAN)) {
missingPermissions.add(BLUETOOTH_SCAN);
}
if (!missingPermissions.isEmpty()) {
permissionCallback = callbackContext;
deviceMacAddress = macAddress;
this.usePairingDialog = usePairingDialog;
PermissionHelper.requestPermissions(this, REQUEST_BOND, missingPermissions.toArray(new String[0]));
return;
}
}

if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) {
BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress);
Peripheral peripheral = new Peripheral(device);
peripherals.put(macAddress, peripheral);
}

Peripheral peripheral = peripherals.get(macAddress);
if (peripheral != null) {
addBondStateListener();
peripheral.bond(callbackContext, bluetoothAdapter, usePairingDialog);
} else {
callbackContext.error("Peripheral " + macAddress + " not found.");
}
}

private void unbond(CallbackContext callbackContext, String macAddress) {
if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S
if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) {
permissionCallback = callbackContext;
deviceMacAddress = macAddress;
PermissionHelper.requestPermission(this, REQUEST_UNBOND, BLUETOOTH_CONNECT);
return;
}
}

if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) {
BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress);
Peripheral peripheral = new Peripheral(device);
peripherals.put(macAddress, peripheral);
}

Peripheral peripheral = peripherals.get(macAddress);
if (peripheral != null) {
peripheral.unbond(callbackContext);
} else {
callbackContext.success();
}
}

private void readBondState(CallbackContext callbackContext, String macAddress) {
if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S
if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) {
permissionCallback = callbackContext;
deviceMacAddress = macAddress;
PermissionHelper.requestPermission(this, REQUEST_READ_BOND_STATE, BLUETOOTH_CONNECT);
return;
}
}

if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) {
BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress);
Peripheral peripheral = new Peripheral(device);
peripherals.put(macAddress, peripheral);
}

Peripheral peripheral = peripherals.get(macAddress);
if (peripheral != null) {
peripheral.readBondState(callbackContext);
} else {
callbackContext.error("Peripheral " + macAddress + " not found.");
}
}

private void requestMtu(CallbackContext callbackContext, String macAddress, int mtuValue) {
Peripheral peripheral = peripherals.get(macAddress);
if (peripheral != null) {
Expand Down Expand Up @@ -1138,13 +1250,6 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic
return;
}

// return error if already scanning
if (bluetoothAdapter.isDiscovering()) {
LOG.w(TAG, "Tried to start scan while already running.");
callbackContext.error("Tried to start scan while already running.");
return;
}

// clear non-connected cached peripherals
for(Iterator<Map.Entry<String, Peripheral>> iterator = peripherals.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<String, Peripheral> entry = iterator.next();
Expand Down Expand Up @@ -1326,6 +1431,25 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int
LOG.d(TAG, "User granted permissions for list known devices");
listKnownDevices(callback);
break;

case REQUEST_BOND:
LOG.d(TAG, "User granted permissions for bond");
bond(callback, deviceMacAddress, usePairingDialog);
this.deviceMacAddress = null;
this.usePairingDialog = true;
break;

case REQUEST_UNBOND:
LOG.d(TAG, "User granted permissions for unbond");
unbond(callback, deviceMacAddress);
this.deviceMacAddress = null;
break;

case REQUEST_READ_BOND_STATE:
LOG.d(TAG, "User granted permissions for read bond state");
readBondState(callback, deviceMacAddress);
this.deviceMacAddress = null;
break;
}
}

Expand All @@ -1346,4 +1470,32 @@ private void resetScanOptions() {
this.reportDuplicates = false;
}

private void addBondStateListener() {
if (bondStateReceiver == null) {
bondStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (ACTION_BOND_STATE_CHANGED.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Peripheral peripheral = peripherals.get(device.getAddress());

if (peripheral != null) {
int bondState = intent.getIntExtra(EXTRA_BOND_STATE, BluetoothDevice.ERROR);
int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
peripheral.updateBondState(bondState, previousBondState);
}
}
}
};
webView.getContext().registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED));
}
}

private void removeBondStateListener() {
if (bondStateReceiver != null) {
webView.getContext().unregisterReceiver(bondStateReceiver);
bondStateReceiver = null;
}
}
}
Loading

0 comments on commit 58e212a

Please sign in to comment.