diff --git a/plugin.xml b/plugin.xml index 77f49be..5ba18ae 100644 --- a/plugin.xml +++ b/plugin.xml @@ -24,6 +24,8 @@ + + diff --git a/readme.md b/readme.md index 708ebf5..fcc7c61 100644 --- a/readme.md +++ b/readme.md @@ -61,6 +61,12 @@ This plugin allows you to interact with Bluetooth LE devices on Android, iOS, an - [isDiscovered](#isdiscovered) - [hasPermission](#haspermission) - [requestPermission](#requestpermission) + - [hasPermissionBtScan](#haspermissionBtScan) + - [requestPermissionBtScan](#requestpermissionBtScan) + - [hasPermissionBtConnect](#haspermissionBtConnect) + - [requestPermissionBtConnect](#requestpermissionBtConnect) + - [hasPermissionBtAdvertise](#haspermissionBtAdvertise) + - [requestPermissionBtAdvertise](#requestpermissionBtAdvertise) - [isLocationEnabled](#islocationenabled) - [requestLocation](#requestlocation) - [setPin](#setPin) @@ -240,6 +246,12 @@ Neither Android nor iOS support Bluetooth on emulators, so you'll need to test o * [bluetoothle.isDiscovered](#isdiscovered) * [bluetoothle.hasPermission](#haspermission) (Android 6+) * [bluetoothle.requestPermission](#requestpermission) (Android 6+) +* [bluetoothle.hasPermissionBtScan](#haspermissionBtScan) (Android 31+) +* [bluetoothle.requestPermissionBtScan](#requestpermissionBtScan) (Android 31+) +* [bluetoothle.hasPermissionBtConnect](#haspermissionBtConnect) (Android 31+) +* [bluetoothle.requestPermissionBtConnect](#requestpermissionBtConnect) (Android 31+) +* [bluetoothle.hasPermissionBtAdvertise](#haspermissionBtAdvertise) (Android 31+) +* [bluetoothle.requestPermissionBtAdvertise](#requestpermissionBtAdvertise) (Android 31+) * [bluetoothle.isLocationEnabled](#islocationenabled) (Android 6+) * [bluetoothle.requestLocation](#requestlocation) (Android 6+) * [bluetoothle.retrievePeripheralsByAddress](#retrievePeripheralsByAddress) (iOS) @@ -417,7 +429,7 @@ The successCallback contains the following properties: ### startScan ### -Scan for Bluetooth LE devices. Since scanning is expensive, stop as soon as possible. The Cordova app should use a timer to limit the scan interval. Also, Android uses an AND operator for filtering, while iOS uses an OR operator. Android API >= 23 requires ACCESS_COARSE_LOCATION permissions to find unpaired devices. Permissions can be requested by using the hasPermission and requestPermission functions. Android API >= 23 also requires location services to be enabled. Use ```isLocationEnabled``` to determine whether location services are enabled. If not enabled, use ```requestLocation``` to prompt the location services settings page. +Scan for Bluetooth LE devices. Since scanning is expensive, stop as soon as possible. The Cordova app should use a timer to limit the scan interval. Also, Android uses an AND operator for filtering, while iOS uses an OR operator. Android API >= 23 requires ACCESS_COARSE_LOCATION permissions to find unpaired devices. Permissions can be requested by using the hasPermission and requestPermission functions. Android API >= 23 also requires location services to be enabled. Use ```isLocationEnabled``` to determine whether location services are enabled. If not enabled, use ```requestLocation``` to prompt the location services settings page. Android API >= 31 also requires BLUETOOTH_SCAN permissions to perform scanning. You can use ```hasPermissionBtScan``` to determine whether scanning permission is granted or use ```requestPermissionBtScan``` to prompt for it. ```javascript bluetoothle.startScan(startScanSuccess, startScanError, params); @@ -616,7 +628,7 @@ bluetoothle.unbond(unbondSuccess, unbondError, params); ### connect ### -Connect to a Bluetooth LE device. The app should use a timer to limit the connecting time in case connecting is never successful. Once a device is connected, it may disconnect without user intervention. The original connection callback will be called again and receive an object with status => disconnected. To reconnect to the device, use the reconnect method. If a timeout occurs, the connection attempt should be canceled using disconnect(). For simplicity, I recommend just using connect() and close(), don't use reconnect() or disconnect(). +Connect to a Bluetooth LE device. The app should use a timer to limit the connecting time in case connecting is never successful. Once a device is connected, it may disconnect without user intervention. The original connection callback will be called again and receive an object with status => disconnected. To reconnect to the device, use the reconnect method. If a timeout occurs, the connection attempt should be canceled using disconnect(). For simplicity, I recommend just using connect() and close(), don't use reconnect() or disconnect(). Android API >= 31 requires BLUETOOTH_CONNECT permissions to connect to devices. You can use ```hasPermissionBtConnect``` to determine whether connect permission is granted or use ```requestPermissionBtConnect``` to prompt for it. ```javascript bluetoothle.connect(connectSuccess, connectError, params); @@ -1739,6 +1751,114 @@ bluetoothle.requestPermission(requestPermissionSuccess, requestPermissionError); +### hasPermissionBtScan ### +Determine whether Bluetooth scanning privileges are granted since scanning for unpaired devices requies it in Android API 31 + +```javascript +bluetoothle.hasPermissionBtScan(hasPermissionSuccess); +``` + +##### Success ##### +* status => hasPermission = true/false + +```javascript +{ + "hasPermission": true +} +``` + + + +### requestPermissionBtScan ### +Request Bluetooth scanning privileges since scanning for unpaired devices requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0. + +```javascript +bluetoothle.requestPermissionBtScan(requestPermissionSuccess, requestPermissionError); +``` + +##### Success ##### +* status => requestPermission = true/false + +```javascript +{ + "requestPermission": true +} +``` + + + +### hasPermissionBtConnect ### +Determine whether Bluetooth connect privileges are granted since connecting to unpaired devices requies it in Android API 31 + +```javascript +bluetoothle.hasPermissionBtConnect(hasPermissionSuccess); +``` + +##### Success ##### +* status => hasPermission = true/false + +```javascript +{ + "hasPermission": true +} +``` + + + +### requestPermissionBtConnect ### +Request Bluetooth connect privileges since connecting to unpaired devices requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0. + +```javascript +bluetoothle.requestPermissionBtConnect(requestPermissionSuccess, requestPermissionError); +``` + +##### Success ##### +* status => requestPermission = true/false + +```javascript +{ + "requestPermission": true +} +``` + + + +### hasPermissionBtAdvertise ### +Determine whether Bluetooth advertise privileges are granted since making the current device discoverable requies it in Android API 31 + +```javascript +bluetoothle.hasPermissionBtAdvertise(hasPermissionSuccess); +``` + +##### Success ##### +* status => hasPermission = true/false + +```javascript +{ + "hasPermission": true +} +``` + + + +### requestPermissionBtAdvertise ### +Request Bluetooth advertise privileges since making the current device discoverable requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0. + +```javascript +bluetoothle.requestPermissionBtAdvertise(requestPermissionSuccess, requestPermissionError); +``` + +##### Success ##### +* status => requestPermission = true/false + +```javascript +{ + "requestPermission": true +} +``` + + + ### isLocationEnabled ### Determine if location services are enabled or not. Location Services are required to find devices in Android API 23. @@ -2041,7 +2161,8 @@ bluetoothle.removeAllServices(success, error); ### startAdvertising ### Start advertising as a BLE device. Note: This needs to be improved so services can be used for both Android and iOS. -On iOS, the advertising devices likes to rename itself back to the name of the device, i.e. Rand' iPhone +On iOS, the advertising devices likes to rename itself back to the name of the device, i.e. Rand' iPhone. +Android API >= 31 also requires BLUETOOTH_ADVERTISE permissions to perform advertising. You can use ```hasPermissionBtAdvertise``` to determine whether advertise permission is granted or use ```requestPermissionBtAdvertise``` to prompt for it. ```javascript bluetoothle.startAdvertising(success, error, params); diff --git a/src/android/BluetoothLePlugin.java b/src/android/BluetoothLePlugin.java index 0d8048c..02bfae8 100644 --- a/src/android/BluetoothLePlugin.java +++ b/src/android/BluetoothLePlugin.java @@ -59,6 +59,9 @@ public class BluetoothLePlugin extends CordovaPlugin { private final int REQUEST_BT_ENABLE = 59627; /*Random integer*/ private final int REQUEST_ACCESS_FINE_LOCATION = 59628; private final int REQUEST_LOCATION_SOURCE_SETTINGS = 59629; + private final int REQUEST_BLUETOOTH_SCAN = 59630; + private final int REQUEST_BLUETOOTH_ADVERTISE = 59631; + private final int REQUEST_BLUETOOTH_CONNECT = 59632; private BluetoothAdapter bluetoothAdapter; private boolean isReceiverRegistered = false; private boolean isBondReceiverRegistered = false; @@ -393,6 +396,18 @@ public boolean execute(String action, final JSONArray args, final CallbackContex hasPermissionAction(callbackContext); } else if ("requestPermission".equals(action)) { requestPermissionAction(callbackContext); + } else if ("hasPermissionBtScan".equals(action)) { + hasPermissionBtScanAction(callbackContext); + } else if ("requestPermissionBtScan".equals(action)) { + requestPermissionBtScanAction(callbackContext); + } else if ("hasPermissionBtConnect".equals(action)) { + hasPermissionBtConnectAction(callbackContext); + } else if ("requestPermissionBtConnect".equals(action)) { + requestPermissionBtConnectAction(callbackContext); + } else if ("hasPermissionBtAdvertise".equals(action)) { + hasPermissionBtAdvertiseAction(callbackContext); + } else if ("requestPermissionBtAdvertise".equals(action)) { + requestPermissionBtAdvertiseAction(callbackContext); } else if ("isLocationEnabled".equals(action)) { isLocationEnabledAction(callbackContext); } else if ("requestLocation".equals(action)) { @@ -879,6 +894,10 @@ private void notifyAction(JSONArray args, CallbackContext callbackContext) { } } + /** + * ACCESS_FINE_LOCATION + */ + public void hasPermissionAction(CallbackContext callbackContext) { JSONObject returnObj = new JSONObject(); @@ -900,6 +919,81 @@ public void requestPermissionAction(CallbackContext callbackContext) { cordova.requestPermission(this, REQUEST_ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); } + /** + * BLUETOOTH_SCAN + */ + + public void hasPermissionBtScanAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "hasPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_SCAN)); + + callbackContext.success(returnObj); + } + + public void requestPermissionBtScanAction(CallbackContext callbackContext) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, "requestPermission"); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + return; + } + + permissionsCallback = callbackContext; + cordova.requestPermission(this, REQUEST_BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_SCAN); + } + + /** + * BLUETOOTH_CONNECT + */ + + public void hasPermissionBtConnectAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "hasPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_CONNECT)); + + callbackContext.success(returnObj); + } + + public void requestPermissionBtConnectAction(CallbackContext callbackContext) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, "requestPermission"); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + return; + } + + permissionsCallback = callbackContext; + cordova.requestPermission(this, REQUEST_BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_CONNECT); + } + + /** + * BLUETOOTH_ADVERTISE + */ + + public void hasPermissionBtAdvertiseAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "hasPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_ADVERTISE)); + + callbackContext.success(returnObj); + } + + public void requestPermissionBtAdvertiseAction(CallbackContext callbackContext) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, "requestPermission"); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + return; + } + + permissionsCallback = callbackContext; + cordova.requestPermission(this, REQUEST_BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_ADVERTISE); + } + public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { if (permissionsCallback == null) { return; @@ -908,7 +1002,25 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int //Just call hasPermission again to verify JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)); + switch (requestCode) { + case REQUEST_ACCESS_FINE_LOCATION: + addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)); + break; + case REQUEST_BLUETOOTH_SCAN: + addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_SCAN)); + break; + case REQUEST_BLUETOOTH_CONNECT: + addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_CONNECT)); + break; + case REQUEST_BLUETOOTH_ADVERTISE: + addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.BLUETOOTH_ADVERTISE)); + break; + default: + addProperty(returnObj, keyError, "requestPermission"); + addProperty(returnObj, keyMessage, logOperationUnsupported); + permissionsCallback.error(returnObj); + return; + } permissionsCallback.success(returnObj); } diff --git a/types/ble.ext.js b/types/ble.ext.js index eafe4ed..1640558 100644 --- a/types/ble.ext.js +++ b/types/ble.ext.js @@ -280,6 +280,42 @@ BluetoothlePlugin.Bluetoothle.prototype.hasPermission = function (success) { }; BluetoothlePlugin.Bluetoothle.prototype.requestPermission = function (success) { }; +/** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.hasPermissionBtScan = function (success) { }; + + + /** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.requestPermissionBtScan = function (success) { }; + + + /** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.hasPermissionBtConnect = function (success) { }; + + +/** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.requestPermissionBtConnect = function (success) { }; + + +/** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.hasPermissionBtAdvertise = function (success) { }; + + +/** + * @param {function(result:[object Object])} success + */ +BluetoothlePlugin.Bluetoothle.prototype.requestPermissionBtAdvertise = function (success) { }; + + /** * @param {function(result:[object Object])} isLocationEnabledSuccess * @param {function(error:BluetoothlePlugin.Error)} isLocationEnabledError diff --git a/types/index.d.ts b/types/index.d.ts index 64d99f3..638d062 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -413,6 +413,51 @@ declare namespace BluetoothlePlugin { requestPermission( success: (result: { requestPermission: boolean }) => void): void; + /** + * Determine whether Bluetooth scan privileges are granted since scanning for unpaired devices requires it in Android API 31. + * @param success The success callback that is passed with has permission value + */ + hasPermissionBtScan( + success: (result: { hasPermission: boolean }) => void): void; + + /** + * Request Bluetooth scan privileges since scanning for unpaired devices requires it in Android API 31. + * Will return an error if called on iOS or Android versions prior to 6.0. + * @param success The success callback that is passed with request permission value + */ + requestPermissionBtScan( + success: (result: { requestPermission: boolean }) => void): void; + + /** + * Determine whether Bluetooth connect privileges are granted since connecting to devices requires it in Android API 31. + * @param success The success callback that is passed with has permission value + */ + hasPermissionBtConnect( + success: (result: { hasPermission: boolean }) => void): void; + + /** + * Request Bluetooth connect privileges since connecting to devices requires it in Android API 31. + * Will return an error if called on iOS or Android versions prior to 6.0. + * @param success The success callback that is passed with request permission value + */ + requestPermissionBtConnect( + success: (result: { requestPermission: boolean }) => void): void; + + /** + * Determine whether Bluetooth advertise privileges are granted since making the current device discoverable requires it in Android API 31. + * @param success The success callback that is passed with has permission value + */ + hasPermissionBtAdvertise( + success: (result: { hasPermission: boolean }) => void): void; + + /** + * Request Bluetooth advertise privileges since making the current device discoverable requires it in Android API 31. + * Will return an error if called on iOS or Android versions prior to 6.0. + * @param success The success callback that is passed with request permission value + */ + requestPermissionBtAdvertise( + success: (result: { requestPermission: boolean }) => void): void; + /** * Determine if location services are enabled or not. Location Services are required to find devices in Android API 23 * @param isLocationEnabledSuccess The success callback that is passed with isLocationEnabled value diff --git a/www/bluetoothle.js b/www/bluetoothle.js index 3cf20b7..73b344f 100644 --- a/www/bluetoothle.js +++ b/www/bluetoothle.js @@ -147,6 +147,24 @@ var bluetoothle = { requestPermission: function(successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, bluetoothleName, "requestPermission", []); }, + hasPermissionBtScan: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "hasPermissionBtScan", []); + }, + requestPermissionBtScan: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "requestPermissionBtScan", []); + }, + hasPermissionBtConnect: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "hasPermissionBtConnect", []); + }, + requestPermissionBtConnect: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "requestPermissionBtConnect", []); + }, + hasPermissionBtAdvertise: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "hasPermissionBtAdvertise", []); + }, + requestPermissionBtAdvertise: function(successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, bluetoothleName, "requestPermissionBtAdvertise", []); + }, isLocationEnabled: function(successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, bluetoothleName, "isLocationEnabled", []); },