diff --git a/android/src/main/java/com/codename1/bluetoothle/BluetoothLePlugin.java b/android/src/main/java/com/codename1/bluetoothle/BluetoothLePlugin.java index 30df569..34605b7 100644 --- a/android/src/main/java/com/codename1/bluetoothle/BluetoothLePlugin.java +++ b/android/src/main/java/com/codename1/bluetoothle/BluetoothLePlugin.java @@ -1,10 +1,8 @@ package com.codename1.bluetoothle; -/*import org.apache.cordova.CallbackContext; - import org.apache.cordova.CordovaPlugin; - import org.apache.cordova.PluginResult; - */ -import com.codename1.cordova.*; +import com.codename1.cordova.CallbackContext; +import com.codename1.cordova.CordovaPlugin; +import com.codename1.cordova.PluginResult; import android.Manifest; @@ -48,4120 +46,4796 @@ import java.util.Set; import java.util.UUID; -import ca.weblite.codename1.json.*; +import ca.weblite.codename1.json.JSONArray; +import ca.weblite.codename1.json.JSONException; +import ca.weblite.codename1.json.JSONObject; import android.util.Log; @SuppressWarnings("unchecked") public class BluetoothLePlugin extends CordovaPlugin { + //Initialization related variables + 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; + + //General callback variables + private CallbackContext initCallbackContext; + private CallbackContext scanCallbackContext; + private CallbackContext permissionsCallback; + private CallbackContext locationCallback; + + private CallbackContext initPeripheralCallback; + private BluetoothGattServer gattServer; + private CallbackContext addServiceCallback; + private CallbackContext advertiseCallbackContext; + private boolean isAdvertising = false; + + //Store connections and all their callbacks + private HashMap> connections; + + //Store bonds + private HashMap bonds = new HashMap(); + + //Discovery related variables + private final int STATE_UNDISCOVERED = 0; + private final int STATE_DISCOVERING = 1; + private final int STATE_DISCOVERED = 2; + + //Quick Writes + private LinkedList queueQuick = new LinkedList(); + + //Object keys + private final String keyStatus = "status"; + private final String keyError = "error"; + private final String keyMessage = "message"; + private final String keyRequest = "request"; + private final String keyStatusReceiver = "statusReceiver"; + private final String keyName = "name"; + private final String keyAddress = "address"; + private final String keyRssi = "rssi"; + private final String keyScanMode = "scanMode"; + private final String keyMatchMode = "matchMode"; + private final String keyMatchNum = "matchNum"; + private final String keyCallbackType = "callbackType"; + private final String keyAdvertisement = "advertisement"; + private final String keyUuid = "uuid"; + private final String keyService = "service"; + private final String keyServices = "services"; + private final String keyCharacteristic = "characteristic"; + private final String keyCharacteristics = "characteristics"; + private final String keyProperties = "properties"; + private final String keyPermissions = "permissions"; + private final String keyDescriptor = "descriptor"; + private final String keyDescriptors = "descriptors"; + private final String keyValue = "value"; + private final String keyType = "type"; + private final String keyIsInitialized = "isInitialized"; + private final String keyIsEnabled = "isEnabled"; + private final String keyIsScanning = "isScanning"; + private final String keyIsBonded = "isBonded"; + private final String keyIsConnected = "isConnected"; + private final String keyIsDiscovered = "isDiscovered"; + private final String keyIsDiscoverable = "isDiscoverable"; + private final String keyPeripheral = "peripheral"; + private final String keyState = "state"; + private final String keyDiscoveredState = "discoveredState"; + private final String keyConnectionPriority = "connectionPriority"; + private final String keyMtu = "mtu"; + private final String keyPin = "pin"; + private final String keyQueue = "queue"; + + //Write Types + private final String writeTypeNoResponse = "noResponse"; + + //Status Types + private final String statusEnabled = "enabled"; + private final String statusDisabled = "disabled"; + private final String statusScanStarted = "scanStarted"; + private final String statusScanStopped = "scanStopped"; + private final String statusScanResult = "scanResult"; + private final String statusBonded = "bonded"; + private final String statusBonding = "bonding"; + private final String statusUnbonded = "unbonded"; + private final String statusConnected = "connected"; + private final String statusDisconnected = "disconnected"; + private final String statusClosed = "closed"; + private final String statusDiscovered = "discovered"; + private final String statusRead = "read"; + private final String statusSubscribed = "subscribed"; + private final String statusSubscribedResult = "subscribedResult"; + private final String statusUnsubscribed = "unsubscribed"; + private final String statusWritten = "written"; + private final String statusReadDescriptor = "readDescriptor"; + private final String statusWrittenDescriptor = "writtenDescriptor"; + private final String statusRssi = "rssi"; + private final String statusConnectionPriorityRequested = "connectionPriorityRequested"; + private final String statusMtu = "mtu"; + + //Properties + private final String propertyBroadcast = "broadcast"; + private final String propertyRead = "read"; + private final String propertyWriteWithoutResponse = "writeWithoutResponse"; + private final String propertyWrite = "write"; + private final String propertyNotify = "notify"; + private final String propertyIndicate = "indicate"; + private final String propertyAuthenticatedSignedWrites = "authenticatedSignedWrites"; + private final String propertyExtendedProperties = "extendedProperties"; + private final String propertyNotifyEncryptionRequired = "notifyEncryptionRequired"; + private final String propertyIndicateEncryptionRequired = "indicateEncryptionRequired"; + private final String propertyConnectionPriorityHigh = "high"; + private final String propertyConnectionPriorityLow = "low"; + private final String propertyConnectionPriorityBalanced = "balanced"; + + //Permissions + private final String permissionRead = "read"; + private final String permissionReadEncrypted = "readEncrypted"; + private final String permissionReadEncryptedMITM = "readEncryptedMITM"; + private final String permissionWrite = "write"; + private final String permissionWriteEncrypted = "writeEncrypted"; + private final String permissionWriteEncryptedMITM = "writeEncryptedMITM"; + private final String permissionWriteSigned = "writeSigned"; + private final String permissionWriteSignedMITM = "writeSignedMITM"; + + //Error Types + private final String errorInitialize = "initialize"; + private final String errorEnable = "enable"; + private final String errorDisable = "disable"; + private final String errorArguments = "arguments"; + private final String errorStartScan = "startScan"; + private final String errorStopScan = "stopScan"; + private final String errorBond = "bond"; + private final String errorUnbond = "unbond"; + private final String errorConnect = "connect"; + private final String errorReconnect = "reconnect"; + private final String errorDiscover = "discover"; + private final String errorServices = "services"; + private final String errorCharacteristics = "characteristics"; + private final String errorDescriptors = "descriptors"; + private final String errorRead = "read"; + private final String errorSubscription = "subscription"; + private final String errorWrite = "write"; + private final String errorReadDescriptor = "readDescriptor"; + private final String errorWriteDescriptor = "writeDescriptor"; + private final String errorRssi = "rssi"; + private final String errorNeverConnected = "neverConnected"; + private final String errorIsNotDisconnected = "isNotDisconnected"; + private final String errorIsNotConnected = "isNotConnected"; + private final String errorIsDisconnected = "isDisconnected"; + private final String errorService = "service"; + private final String errorCharacteristic = "characteristic"; + private final String errorDescriptor = "descriptor"; + private final String errorRequestConnectionPriority = "requestConnectPriority"; + private final String errorMtu = "mtu"; + private final String errorRetrievePeripheralsByAddress = "retrievePeripheralsByAddress"; - //Initialization related variables + //Error Messages + //Initialization + private final String logNotEnabled = "Bluetooth not enabled"; + private final String logNotDisabled = "Bluetooth not disabled"; + private final String logNotInit = "Bluetooth not initialized"; + private final String logOperationUnsupported = "Operation unsupported"; + //Scanning + private final String logAlreadyScanning = "Scanning already in progress"; + private final String logScanStartFail = "Scan failed to start"; + private final String logNotScanning = "Not scanning"; + //Bonding + private final String logBonded = "Device already bonded"; + private final String logBonding = "Device already bonding"; + private final String logUnbonded = "Device already unbonded"; + private final String logBondFail = "Device failed to bond on return"; + private final String logUnbondFail = "Device failed to unbond on return"; + //Connection + private final String logPreviouslyConnected = "Device previously connected, reconnect or close for new device"; + private final String logConnectFail = "Connection failed"; + private final String logNeverConnected = "Never connected to device"; + private final String logIsNotConnected = "Device isn't connected"; + private final String logIsNotDisconnected = "Device isn't disconnected"; + private final String logIsDisconnected = "Device is disconnected"; + private final String logNoAddress = "No device address"; + private final String logNoDevice = "Device not found"; + private final String logReconnectFail = "Reconnection to device failed"; + //Discovery + private final String logAlreadyDiscovering = "Already discovering device"; + private final String logDiscoveryFail = "Unable to discover device"; + //Read/write + private final String logNoArgObj = "Argument object not found"; + private final String logNoService = "Service not found"; + private final String logNoCharacteristic = "Characteristic not found"; + private final String logNoDescriptor = "Descriptor not found"; + private final String logReadFail = "Unable to read"; + private final String logReadFailReturn = "Unable to read on return"; + private final String logSubscribeFail = "Unable to subscribe"; + private final String logSubscribeAlready = "Already subscribed"; + private final String logUnsubscribeFail = "Unable to unsubscribe"; + private final String logUnsubscribeAlready = "Already unsubscribed"; + private final String logWriteFail = "Unable to write"; + private final String logWriteFailReturn = "Unable to write on return"; + private final String logWriteValueNotFound = "Write value not found"; + private final String logWriteValueNotSet = "Write value not set"; + private final String logReadDescriptorFail = "Unable to read descriptor"; + private final String logReadDescriptorFailReturn = "Unable to read descriptor on return"; + private final String logWriteDescriptorNotAllowed = "Unable to write client configuration descriptor"; + private final String logWriteDescriptorFail = "Unable to write descriptor"; + private final String logWriteDescriptorValueNotFound = "Write descriptor value not found"; + private final String logWriteDescriptorValueNotSet = "Write descriptor value not set"; + private final String logWriteDescriptorFailReturn = "Descriptor not written on return"; + private final String logRssiFail = "Unable to read RSSI"; + private final String logRssiFailReturn = "Unable to read RSSI on return"; + //Request Connection Priority + private final String logRequestConnectionPriorityNull = "Request connection priority not set"; + private final String logRequestConnectionPriorityInvalid = "Request connection priority is invalid"; + private final String logRequestConnectionPriorityFailed = "Request connection priority failed"; + //MTU + private final String logMtuFail = "Unable to set MTU"; + private final String logMtuFailReturn = "Unable to set MTU on return"; + + private final String logRequiresAPI21 = "Requires API level 21"; + + private final String operationConnect = "connect"; + private final String operationDiscover = "discover"; + private final String operationRssi = "rssi"; + private final String operationRead = "read"; + private final String operationSubscribe = "subscribe"; + private final String operationUnsubscribe = "unsubscribe"; + private final String operationWrite = "write"; + private final String operationMtu = "mtu"; + + private final String baseUuidStart = "0000"; + private final String baseUuidEnd = "-0000-1000-8000-00805F9B34FB"; + + //Client Configuration UUID for notifying/indicating + private final UUID clientConfigurationDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"); + + public BluetoothLePlugin() { + + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + return; + } + + createScanCallback(); + + createAdvertiseCallback(); + } + + //Actions + @Override + public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { + //Execute the specified action + if ("initialize".equals(action)) { + initializeAction(args, callbackContext); + } else if ("enable".equals(action)) { + enableAction(callbackContext); + } else if ("getAdapterInfo".equals(action)) { + getAdapterInfoAction(callbackContext); + } else if ("disable".equals(action)) { + disableAction(callbackContext); + } else if ("startScan".equals(action)) { + startScanAction(args, callbackContext); + } else if ("stopScan".equals(action)) { + stopScanAction(callbackContext); + } else if ("retrieveConnected".equals(action)) { + retrieveConnectedAction(args, callbackContext); + } else if ("bond".equals(action)) { + bondAction(args, callbackContext); + } else if ("unbond".equals(action)) { + unbondAction(args, callbackContext); + } else if ("connect".equals(action)) { + connectAction(args, callbackContext); + } else if ("reconnect".equals(action)) { + reconnectAction(args, callbackContext); + } else if ("disconnect".equals(action)) { + disconnectAction(args, callbackContext); + } else if ("services".equals(action)) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorServices); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + } else if ("characteristics".equals(action)) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorCharacteristics); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + } else if ("descriptors".equals(action)) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorDescriptors); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + } else if ("close".equals(action)) { + closeAction(args, callbackContext); + } else if ("discover".equals(action)) { + discoverAction(args, callbackContext); + } else if ("read".equals(action)) { + Operation operation = new Operation("read", args, callbackContext); + queueAdd(operation); + } else if ("subscribe".equals(action)) { + Operation operation = new Operation("subscribe", args, callbackContext); + queueAdd(operation); + } else if ("unsubscribe".equals(action)) { + Operation operation = new Operation("unsubscribe", args, callbackContext); + queueAdd(operation); + } else if ("write".equals(action)) { + Operation operation = new Operation("write", args, callbackContext); + queueAdd(operation); + } else if ("writeQ".equals(action)) { + writeQAction(args, callbackContext); + } else if ("readDescriptor".equals(action)) { + Operation operation = new Operation("readDescriptor", args, callbackContext); + queueAdd(operation); + } else if ("writeDescriptor".equals(action)) { + Operation operation = new Operation("writeDescriptor", args, callbackContext); + queueAdd(operation); + } else if ("rssi".equals(action)) { + rssiAction(args, callbackContext); + } else if ("isInitialized".equals(action)) { + isInitializedAction(callbackContext); + } else if ("isEnabled".equals(action)) { + isEnabledAction(callbackContext); + } else if ("isScanning".equals(action)) { + isScanningAction(callbackContext); + } else if ("wasConnected".equals(action)) { + wasConnectedAction(args, callbackContext); + } else if ("isConnected".equals(action)) { + isConnectedAction(args, callbackContext); + } else if ("isDiscovered".equals(action)) { + isDiscoveredAction(args, callbackContext); + } else if ("isBonded".equals(action)) { + isBondedAction(args, callbackContext); + } else if ("requestConnectionPriority".equals(action)) { + requestConnectionPriorityAction(args, callbackContext); + } else if ("mtu".equals(action)) { + mtuAction(args, callbackContext); + } else if ("hasPermission".equals(action)) { + 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)) { + requestLocationAction(callbackContext); + } else if ("initializePeripheral".equals(action)) { + initializePeripheralAction(args, callbackContext); + } else if ("addService".equals(action)) { + addServiceAction(args, callbackContext); + } else if ("removeService".equals(action)) { + removeServiceAction(args, callbackContext); + } else if ("removeAllServices".equals(action)) { + removeAllServicesAction(args, callbackContext); + } else if ("startAdvertising".equals(action)) { + startAdvertisingAction(args, callbackContext); + } else if ("stopAdvertising".equals(action)) { + stopAdvertisingAction(args, callbackContext); + } else if ("isAdvertising".equals(action)) { + isAdvertisingAction(callbackContext); + } else if ("respond".equals(action)) { + respondAction(args, callbackContext); + } else if ("notify".equals(action)) { + notifyAction(args, callbackContext); + } else if ("setPin".equals(action)) { + setPinAction(args, callbackContext); + } else if ("retrievePeripheralsByAddress".equals(action)) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorRetrievePeripheralsByAddress); + addProperty(returnObj, keyMessage, logOperationUnsupported); + callbackContext.error(returnObj); + } else { + return false; + } + return true; + } - private final int REQUEST_BT_ENABLE = 59627; /*Random integer*/ + private void initializePeripheralAction(JSONArray args, CallbackContext callbackContext) { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + JSONObject returnObj = new JSONObject(); - private final int REQUEST_ACCESS_COARSE_LOCATION = 59628; - private final int REQUEST_LOCATION_SOURCE_SETTINGS = 59629; - private BluetoothAdapter bluetoothAdapter; - private boolean isReceiverRegistered = false; + addProperty(returnObj, "error", "initializePeripheral"); + addProperty(returnObj, "message", logOperationUnsupported); - //General callback variables - private CallbackContext initCallbackContext; - private CallbackContext scanCallbackContext; - private CallbackContext permissionsCallback; - private CallbackContext locationCallback; + callbackContext.error(returnObj); + return; + } - private CallbackContext initPeripheralCallback; - private BluetoothGattServer gattServer; - private CallbackContext addServiceCallback; - private CallbackContext advertiseCallbackContext; - private boolean isAdvertising = false; + initPeripheralCallback = callbackContext; - //Store connections and all their callbacks - private HashMap> connections; + initGattServer(); - //Discovery related variables - private final int STATE_UNDISCOVERED = 0; - private final int STATE_DISCOVERING = 1; - private final int STATE_DISCOVERED = 2; + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyStatus, statusEnabled); - //Quick Writes - LinkedList queueQuick = new LinkedList(); - LinkedList queue = new LinkedList(); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); - //Object keys - private final String keyStatus = "status"; - private final String keyError = "error"; - private final String keyMessage = "message"; - private final String keyRequest = "request"; - private final String keyStatusReceiver = "statusReceiver"; - private final String keyName = "name"; - private final String keyAddress = "address"; - private final String keyRssi = "rssi"; - private final String keyScanMode = "scanMode"; - private final String keyMatchMode = "matchMode"; - private final String keyMatchNum = "matchNum"; - private final String keyCallbackType = "callbackType"; - private final String keyAdvertisement = "advertisement"; - private final String keyUuid = "uuid"; - private final String keyService = "service"; - private final String keyServices = "services"; - private final String keyCharacteristic = "characteristic"; - private final String keyCharacteristics = "characteristics"; - private final String keyProperties = "properties"; - private final String keyPermissions = "permissions"; - private final String keyDescriptor = "descriptor"; - private final String keyDescriptors = "descriptors"; - private final String keyValue = "value"; - private final String keyType = "type"; - private final String keyIsInitialized = "isInitialized"; - private final String keyIsEnabled = "isEnabled"; - private final String keyIsScanning = "isScanning"; - private final String keyIsConnected = "isConnected"; - private final String keyIsDiscovered = "isDiscovered"; - private final String keyPeripheral = "peripheral"; - private final String keyState = "state"; - private final String keyDiscoveredState = "discoveredState"; - private final String keyConnectionPriority = "connectionPriority"; - private final String keyMtu = "mtu"; - - //Write Types - private final String writeTypeNoResponse = "noResponse"; - - //Status Types - private final String statusEnabled = "enabled"; - private final String statusDisabled = "disabled"; - private final String statusScanStarted = "scanStarted"; - private final String statusScanStopped = "scanStopped"; - private final String statusScanResult = "scanResult"; - private final String statusConnected = "connected"; - private final String statusDisconnected = "disconnected"; - private final String statusClosed = "closed"; - private final String statusDiscovered = "discovered"; - private final String statusRead = "read"; - private final String statusSubscribed = "subscribed"; - private final String statusSubscribedResult = "subscribedResult"; - private final String statusUnsubscribed = "unsubscribed"; - private final String statusWritten = "written"; - private final String statusReadDescriptor = "readDescriptor"; - private final String statusWrittenDescriptor = "writtenDescriptor"; - private final String statusRssi = "rssi"; - private final String statusConnectionPriorityRequested = "connectionPriorityRequested"; - private final String statusMtu = "mtu"; - - //Properties - private final String propertyBroadcast = "broadcast"; - private final String propertyRead = "read"; - private final String propertyWriteWithoutResponse = "writeWithoutResponse"; - private final String propertyWrite = "write"; - private final String propertyNotify = "notify"; - private final String propertyIndicate = "indicate"; - private final String propertyAuthenticatedSignedWrites = "authenticatedSignedWrites"; - private final String propertyExtendedProperties = "extendedProperties"; - private final String propertyNotifyEncryptionRequired = "notifyEncryptionRequired"; - private final String propertyIndicateEncryptionRequired = "indicateEncryptionRequired"; - private final String propertyConnectionPriorityHigh = "high"; - private final String propertyConnectionPriorityLow = "low"; - private final String propertyConnectionPriorityBalanced = "balanced"; - - //Permissions - private final String permissionRead = "read"; - private final String permissionReadEncrypted = "readEncrypted"; - private final String permissionReadEncryptedMITM = "readEncryptedMITM"; - private final String permissionWrite = "write"; - private final String permissionWriteEncrypted = "writeEncrypted"; - private final String permissionWriteEncryptedMITM = "writeEncryptedMITM"; - private final String permissionWriteSigned = "writeSigned"; - private final String permissionWriteSignedMITM = "writeSignedMITM"; - - //Error Types - private final String errorInitialize = "initialize"; - private final String errorEnable = "enable"; - private final String errorDisable = "disable"; - private final String errorArguments = "arguments"; - private final String errorStartScan = "startScan"; - private final String errorStopScan = "stopScan"; - private final String errorConnect = "connect"; - private final String errorReconnect = "reconnect"; - private final String errorDiscover = "discover"; - private final String errorServices = "services"; - private final String errorCharacteristics = "characteristics"; - private final String errorDescriptors = "descriptors"; - private final String errorRead = "read"; - private final String errorSubscription = "subscription"; - private final String errorWrite = "write"; - private final String errorReadDescriptor = "readDescriptor"; - private final String errorWriteDescriptor = "writeDescriptor"; - private final String errorRssi = "rssi"; - private final String errorNeverConnected = "neverConnected"; - private final String errorIsNotDisconnected = "isNotDisconnected"; - private final String errorIsNotConnected = "isNotConnected"; - private final String errorIsDisconnected = "isDisconnected"; - private final String errorService = "service"; - private final String errorCharacteristic = "characteristic"; - private final String errorDescriptor = "descriptor"; - private final String errorRequestConnectionPriority = "requestConnectPriority"; - private final String errorMtu = "mtu"; + //TODO standardize with main init function as well + } - //Error Messages - //Initialization - private final String logNotEnabled = "Bluetooth not enabled"; - private final String logNotDisabled = "Bluetooth not disabled"; - private final String logNotInit = "Bluetooth not initialized"; - private final String logOperationUnsupported = "Operation unsupported"; - //Scanning - private final String logAlreadyScanning = "Scanning already in progress"; - private final String logScanStartFail = "Scan failed to start"; - private final String logNotScanning = "Not scanning"; - //Connection - private final String logPreviouslyConnected = "Device previously connected, reconnect or close for new device"; - private final String logConnectFail = "Connection failed"; - private final String logNeverConnected = "Never connected to device"; - private final String logIsNotConnected = "Device isn't connected"; - private final String logIsNotDisconnected = "Device isn't disconnected"; - private final String logIsDisconnected = "Device is disconnected"; - private final String logNoAddress = "No device address"; - private final String logNoDevice = "Device not found"; - private final String logReconnectFail = "Reconnection to device failed"; - //Discovery - private final String logAlreadyDiscovering = "Already discovering device"; - private final String logDiscoveryFail = "Unable to discover device"; - //Read/write - private final String logNoArgObj = "Argument object not found"; - private final String logNoService = "Service not found"; - private final String logNoCharacteristic = "Characteristic not found"; - private final String logNoDescriptor = "Descriptor not found"; - private final String logReadFail = "Unable to read"; - private final String logReadFailReturn = "Unable to read on return"; - private final String logSubscribeFail = "Unable to subscribe"; - private final String logSubscribeAlready = "Already subscribed"; - private final String logUnsubscribeFail = "Unable to unsubscribe"; - private final String logUnsubscribeAlready = "Already unsubscribed"; - private final String logWriteFail = "Unable to write"; - private final String logWriteFailReturn = "Unable to write on return"; - private final String logWriteValueNotFound = "Write value not found"; - private final String logWriteValueNotSet = "Write value not set"; - private final String logReadDescriptorFail = "Unable to read descriptor"; - private final String logReadDescriptorFailReturn = "Unable to read descriptor on return"; - private final String logWriteDescriptorNotAllowed = "Unable to write client configuration descriptor"; - private final String logWriteDescriptorFail = "Unable to write descriptor"; - private final String logWriteDescriptorValueNotFound = "Write descriptor value not found"; - private final String logWriteDescriptorValueNotSet = "Write descriptor value not set"; - private final String logWriteDescriptorFailReturn = "Descriptor not written on return"; - private final String logRssiFail = "Unable to read RSSI"; - private final String logRssiFailReturn = "Unable to read RSSI on return"; - //Request Connection Priority - private final String logRequestConnectionPriorityNull = "Request connection priority not set"; - private final String logRequestConnectionPriorityInvalid = "Request connection priority is invalid"; - private final String logRequestConnectionPriorityFailed = "Request connection priority failed"; - //MTU - private final String logMtuFail = "Unable to set MTU"; - private final String logMtuFailReturn = "Unable to set MTU on return"; - - private final String logRequiresAPI21 = "Requires API level 21"; - - private final String operationConnect = "connect"; - private final String operationDiscover = "discover"; - private final String operationRssi = "rssi"; - private final String operationRead = "read"; - private final String operationSubscribe = "subscribe"; - private final String operationUnsubscribe = "unsubscribe"; - private final String operationWrite = "write"; - private final String operationMtu = "mtu"; - - private final String baseUuidStart = "0000"; - private final String baseUuidEnd = "-0000-1000-8000-00805f9b34fb"; - - //Client Configuration UUID for notifying/indicating - private final UUID clientConfigurationDescriptorUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - public BluetoothLePlugin() { - - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - return; + private void addServiceAction(JSONArray args, CallbackContext callbackContext) { + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } + + addServiceCallback = callbackContext; + + UUID uuid = getUUID(obj.optString("service", null)); + + BluetoothGattService service = new BluetoothGattService(uuid, BluetoothGattService.SERVICE_TYPE_PRIMARY); + + JSONArray characteristicsIn = obj.optJSONArray("characteristics"); + + for (int i = 0; i < characteristicsIn.length(); i++) { + JSONObject characteristicIn = null; + + try { + characteristicIn = characteristicsIn.getJSONObject(i); + } catch (JSONException ex) { + continue; + } + + UUID characteristicUuid = getUUID(characteristicIn.optString("uuid", null)); + + boolean includeClientConfiguration = false; + + JSONObject propertiesIn = characteristicIn.optJSONObject("properties"); + int properties = 0; + if (propertiesIn != null) { + if (propertiesIn.optString("broadcast", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_BROADCAST; + } + if (propertiesIn.optString("extendedProps", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS; + } + if (propertiesIn.optString("indicate", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_INDICATE; + includeClientConfiguration = true; + } + if (propertiesIn.optString("notify", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_NOTIFY; + includeClientConfiguration = true; + } + if (propertiesIn.optString("read", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_READ; } + if (propertiesIn.optString("signedWrite", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE; + } + if (propertiesIn.optString("write", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_WRITE; + } + if (propertiesIn.optString("writeNoResponse", null) != null) { + properties |= BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE; + } + if (propertiesIn.optString(propertyNotifyEncryptionRequired, null) != null) { + properties |= 0x100; + } + if (propertiesIn.optString(propertyIndicateEncryptionRequired, null) != null) { + properties |= 0x200; + } + } - createScanCallback(); + JSONObject permissionsIn = characteristicIn.optJSONObject("permissions"); + int permissions = 0; + if (permissionsIn != null) { + if (permissionsIn.optString("read", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_READ; + } + if (permissionsIn.optString("readEncrypted", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED; + } + if (permissionsIn.optString("readEncryptedMITM", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM; + } + if (permissionsIn.optString("write", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE; + } + if (permissionsIn.optString("writeEncrypted", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED; + } + if (permissionsIn.optString("writeEncryptedMITM", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM; + } + if (permissionsIn.optString("writeSigned", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED; + } + if (permissionsIn.optString("writeSignedMITM", null) != null) { + permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM; + } + } - createAdvertiseCallback(); - } + BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(characteristicUuid, properties, permissions); - //Actions - @Override - public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { - Log.d("CN1BT", "execute: " + action + " " + args); - - //Execute the specified action - if ("initialize".equals(action)) { - initializeAction(args, callbackContext); - } else if ("enable".equals(action)) { - enableAction(callbackContext); - } else if ("disable".equals(action)) { - disableAction(callbackContext); - } else if ("startScan".equals(action)) { - startScanAction(args, callbackContext); - } else if ("stopScan".equals(action)) { - stopScanAction(callbackContext); - } else if ("retrieveConnected".equals(action)) { - retrieveConnectedAction(args, callbackContext); - } else if ("connect".equals(action)) { - connectAction(args, callbackContext); - } else if ("reconnect".equals(action)) { - reconnectAction(args, callbackContext); - } else if ("disconnect".equals(action)) { - disconnectAction(args, callbackContext); - } else if ("services".equals(action)) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorServices); - addProperty(returnObj, keyMessage, logOperationUnsupported); - callbackContext.error(returnObj); - } else if ("characteristics".equals(action)) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorCharacteristics); - addProperty(returnObj, keyMessage, logOperationUnsupported); - callbackContext.error(returnObj); - } else if ("descriptors".equals(action)) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorDescriptors); - addProperty(returnObj, keyMessage, logOperationUnsupported); - callbackContext.error(returnObj); - } else if ("close".equals(action)) { - closeAction(args, callbackContext); - } else if ("discover".equals(action)) { - discoverAction(args, callbackContext); - } else if ("read".equals(action)) { - Operation operation = new Operation("read", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("subscribe".equals(action)) { - Operation operation = new Operation("subscribe", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("unsubscribe".equals(action)) { - Operation operation = new Operation("unsubscribe", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("write".equals(action)) { - Operation operation = new Operation("write", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("writeQ".equals(action)) { - writeQAction(args, callbackContext); - } else if ("readDescriptor".equals(action)) { - Operation operation = new Operation("readDescriptor", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("writeDescriptor".equals(action)) { - Operation operation = new Operation("writeDescriptor", args, callbackContext); - queue.add(operation); - queueStart(); - } else if ("rssi".equals(action)) { - rssiAction(args, callbackContext); - } else if ("isInitialized".equals(action)) { - isInitializedAction(callbackContext); - } else if ("isEnabled".equals(action)) { - isEnabledAction(callbackContext); - } else if ("isScanning".equals(action)) { - isScanningAction(callbackContext); - } else if ("wasConnected".equals(action)) { - wasConnectedAction(args, callbackContext); - } else if ("isConnected".equals(action)) { - isConnectedAction(args, callbackContext); - } else if ("isDiscovered".equals(action)) { - isDiscoveredAction(args, callbackContext); - } else if ("requestConnectionPriority".equals(action)) { - requestConnectionPriorityAction(args, callbackContext); - } else if ("mtu".equals(action)) { - mtuAction(args, callbackContext); - } else if ("hasPermission".equals(action)) { - hasPermissionAction(callbackContext); - } else if ("requestPermission".equals(action)) { - requestPermissionAction(callbackContext); - } else if ("isLocationEnabled".equals(action)) { - isLocationEnabledAction(callbackContext); - } else if ("requestLocation".equals(action)) { - requestLocationAction(callbackContext); - } else if ("initializePeripheral".equals(action)) { - initializePeripheralAction(args, callbackContext); - } else if ("addService".equals(action)) { - addServiceAction(args, callbackContext); - } else if ("removeService".equals(action)) { - removeServiceAction(args, callbackContext); - } else if ("removeAllServices".equals(action)) { - removeAllServicesAction(args, callbackContext); - } else if ("startAdvertising".equals(action)) { - startAdvertisingAction(args, callbackContext); - } else if ("stopAdvertising".equals(action)) { - stopAdvertisingAction(args, callbackContext); - } else if ("isAdvertising".equals(action)) { - isAdvertisingAction(callbackContext); - } else if ("respond".equals(action)) { - respondAction(args, callbackContext); - } else if ("notify".equals(action)) { - notifyAction(args, callbackContext); - } else { - return false; + if (includeClientConfiguration) { + BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(clientConfigurationDescriptorUuid, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE); + characteristic.addDescriptor(descriptor); + } + + JSONArray descriptorsIn = obj.optJSONArray("descriptors"); + + if (descriptorsIn != null) { + for (int j = 0; j < descriptorsIn.length(); j++) { + JSONObject descriptorIn = null; + + try { + descriptorIn = descriptorsIn.getJSONObject(j); + } catch (JSONException ex) { + continue; + } + + UUID descriptorUuid = getUUID(descriptorIn.optString("uuid", null)); + + permissionsIn = descriptorIn.optJSONObject("permissions"); + permissions = 0; + if (permissionsIn != null) { + if (permissionsIn.optString("read", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_READ; + } + if (permissionsIn.optString("readEncrypted", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED; + } + if (permissionsIn.optString("readEncryptedMITM", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM; + } + if (permissionsIn.optString("write", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_WRITE; + } + if (permissionsIn.optString("writeEncrypted", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED; + } + if (permissionsIn.optString("writeEncryptedMITM", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM; + } + if (permissionsIn.optString("writeSigned", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED; + } + if (permissionsIn.optString("writeSignedMITM", null) != null) { + permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM; + } + } + + BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(descriptorUuid, permissions); + + characteristic.addDescriptor(descriptor); } - return true; + } + + service.addCharacteristic(characteristic); } - private void initializePeripheralAction(JSONArray args, CallbackContext callbackContext) { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - JSONObject returnObj = new JSONObject(); + boolean result = gattServer.addService(service); + if (result) { + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "initializePeripheral"); - addProperty(returnObj, "message", logOperationUnsupported); + addProperty(returnObj, "service", uuid.toString()); + addProperty(returnObj, "status", "serviceAdded"); - callbackContext.error(returnObj); - return; - } + callbackContext.success(returnObj); + } else { + JSONObject returnObj = new JSONObject(); - initPeripheralCallback = callbackContext; + addProperty(returnObj, "service", uuid.toString()); + addProperty(returnObj, "error", "service"); + addProperty(returnObj, "message", "Failed to add service"); - //Re-opening Gatt server seems to cause some issues - if (gattServer == null) { - Activity activity = cordova.getActivity(); - BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); - gattServer = bluetoothManager.openGattServer(activity.getApplicationContext(), bluetoothGattServerCallback); - } + callbackContext.error(returnObj); + } + } - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyStatus, statusEnabled); + private void removeServiceAction(JSONArray args, CallbackContext callbackContext) { + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); + UUID uuid = getUUID(obj.optString("service", null)); - //TODO standardize with main init function as well + BluetoothGattService service = gattServer.getService(uuid); + if (service == null) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "service", uuid.toString()); + addProperty(returnObj, "error", "service"); + addProperty(returnObj, "message", "Service doesn't exist"); + + callbackContext.error(returnObj); + return; } - private void addServiceAction(JSONArray args, CallbackContext callbackContext) { - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + boolean result = gattServer.removeService(service); + if (result) { + JSONObject returnObj = new JSONObject(); - addServiceCallback = callbackContext; + addProperty(returnObj, "service", uuid.toString()); + addProperty(returnObj, "status", "serviceRemoved"); - UUID uuid = getUUID(obj.optString("service", null)); + callbackContext.success(returnObj); + } else { + JSONObject returnObj = new JSONObject(); - BluetoothGattService service = new BluetoothGattService(uuid, BluetoothGattService.SERVICE_TYPE_PRIMARY); + addProperty(returnObj, "service", uuid.toString()); + addProperty(returnObj, "error", "service"); + addProperty(returnObj, "message", "Failed to remove service"); - JSONArray characteristicsIn = obj.optJSONArray("characteristics"); + callbackContext.error(returnObj); + } + } - for (int i = 0; i < characteristicsIn.length(); i++) { - JSONObject characteristicIn = null; + private void removeAllServicesAction(JSONArray args, CallbackContext callbackContext) { + gattServer.clearServices(); - try { - characteristicIn = characteristicsIn.getJSONObject(i); - } catch (JSONException ex) { - continue; - } + JSONObject returnObj = new JSONObject(); - UUID characteristicUuid = getUUID(characteristicIn.optString("uuid", null)); + addProperty(returnObj, "status", "allServicesRemoved"); - boolean includeClientConfiguration = false; + callbackContext.success(returnObj); + } - JSONObject propertiesIn = characteristicIn.optJSONObject("properties"); - int properties = 0; - if (propertiesIn != null) { - if (propertiesIn.optString("broadcast", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_BROADCAST; - } - if (propertiesIn.optString("extendedProps", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS; - } - if (propertiesIn.optString("indicate", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_INDICATE; - includeClientConfiguration = true; - } - if (propertiesIn.optString("notify", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_NOTIFY; - includeClientConfiguration = true; - } - if (propertiesIn.optString("read", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_READ; - } - if (propertiesIn.optString("signedWrite", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE; - } - if (propertiesIn.optString("write", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_WRITE; - } - if (propertiesIn.optString("writeNoResponse", null) != null) { - properties |= BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE; - } - if (propertiesIn.optString(propertyNotifyEncryptionRequired, null) != null) { - properties |= 0x100; - } - if (propertiesIn.optString(propertyIndicateEncryptionRequired, null) != null) { - properties |= 0x200; - } - } + private void startAdvertisingAction(JSONArray args, CallbackContext callbackContext) { + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - JSONObject permissionsIn = characteristicIn.optJSONObject("permissions"); - int permissions = 0; - if (permissionsIn != null) { - if (permissionsIn.optString("read", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_READ; - } - if (permissionsIn.optString("readEncrypted", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED; - } - if (permissionsIn.optString("readEncryptedMITM", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM; - } - if (permissionsIn.optString("write", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE; - } - if (permissionsIn.optString("writeEncrypted", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED; - } - if (permissionsIn.optString("writeEncryptedMITM", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM; - } - if (permissionsIn.optString("writeSigned", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED; - } - if (permissionsIn.optString("writeSignedMITM", null) != null) { - permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM; - } - } + BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + if (advertiser == null) { + JSONObject returnObj = new JSONObject(); - BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(characteristicUuid, properties, permissions); + addProperty(returnObj, "error", "startAdvertising"); + addProperty(returnObj, "message", "Advertising isn't supported"); - if (includeClientConfiguration) { - BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(clientConfigurationDescriptorUuid, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE); - characteristic.addDescriptor(descriptor); - } + callbackContext.error(returnObj); + return; + } - JSONArray descriptorsIn = obj.optJSONArray("descriptors"); - - if (descriptorsIn != null) { - for (int j = 0; j < descriptorsIn.length(); i++) { - JSONObject descriptorIn = null; - - try { - descriptorIn = descriptorsIn.getJSONObject(i); - } catch (JSONException ex) { - continue; - } - - UUID descriptorUuid = getUUID(descriptorIn.optString("uuid", null)); - - permissionsIn = descriptorIn.optJSONObject("permissions"); - permissions = 0; - if (permissionsIn != null) { - if (permissionsIn.optString("read", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_READ; - } - if (permissionsIn.optString("readEncrypted", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED; - } - if (permissionsIn.optString("readEncryptedMITM", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM; - } - if (permissionsIn.optString("write", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_WRITE; - } - if (permissionsIn.optString("writeEncrypted", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED; - } - if (permissionsIn.optString("writeEncryptedMITM", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM; - } - if (permissionsIn.optString("writeSigned", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED; - } - if (permissionsIn.optString("writeSignedMITM", null) != null) { - permissions |= BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM; - } - } - - BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(descriptorUuid, permissions); - - characteristic.addDescriptor(descriptor); - } - } + AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder(); - service.addCharacteristic(characteristic); - } + String modeS = obj.optString("mode", "balanced"); + int mode = AdvertiseSettings.ADVERTISE_MODE_BALANCED; + if (modeS.equals("lowLatency")) { + mode = AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY; + } else if (modeS.equals("lowPower")) { + mode = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER; + } + settingsBuilder.setAdvertiseMode(mode); - boolean result = gattServer.addService(service); - if (result) { - JSONObject returnObj = new JSONObject(); + boolean connectable = obj.optBoolean("connectable", true); + settingsBuilder.setConnectable(connectable); - addProperty(returnObj, "service", uuid.toString()); - addProperty(returnObj, "status", "serviceAdded"); + int timeout = obj.optInt("timeout", 1000); + if (timeout < 0 || timeout > 180000) { + JSONObject returnObj = new JSONObject(); - callbackContext.success(returnObj); - } else { - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "startAdvertising"); + addProperty(returnObj, "message", "Invalid timeout (0 - 180000)"); - addProperty(returnObj, "service", uuid.toString()); - addProperty(returnObj, "error", "service"); - addProperty(returnObj, "message", "Failed to add service"); + callbackContext.error(returnObj); + return; + } + String adapterName = obj.optString("name"); + bluetoothAdapter.setName(adapterName); + + settingsBuilder.setTimeout(timeout); + + String txPowerLevelS = obj.optString("txPowerLevel", "medium"); + int txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM; + if (txPowerLevelS.equals("high")) { + txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH; + } else if (txPowerLevelS.equals("low")) { + txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_LOW; + } else if (txPowerLevelS.equals("ultraLow")) { + txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW; + } + settingsBuilder.setTxPowerLevel(txPowerLevel); + AdvertiseSettings advertiseSettings = settingsBuilder.build(); - callbackContext.error(returnObj); - } + AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder(); + + int manufacturerId = obj.optInt("manufacturerId", 0); + byte[] manufacturerSpecificData = getPropertyBytes(obj, "manufacturerSpecificData"); + if (manufacturerId >= 0 && manufacturerSpecificData != null) { + dataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData); } - private void removeServiceAction(JSONArray args, CallbackContext callbackContext) { - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + UUID uuid = getUUID(obj.optString("service", null)); + if (uuid != null) { + byte[] serviceData = getPropertyBytes(obj, "serviceData"); + if (serviceData != null) { + dataBuilder.addServiceData(new ParcelUuid(uuid), serviceData); + } else { + dataBuilder.addServiceUuid(new ParcelUuid(uuid)); + } + } - UUID uuid = getUUID(obj.optString("service", null)); + dataBuilder.setIncludeDeviceName(obj.optBoolean("includeDeviceName", true)); - BluetoothGattService service = gattServer.getService(uuid); - if (service == null) { - JSONObject returnObj = new JSONObject(); + dataBuilder.setIncludeTxPowerLevel(obj.optBoolean("includeTxPowerLevel", true)); - addProperty(returnObj, "service", uuid.toString()); - addProperty(returnObj, "error", "service"); - addProperty(returnObj, "message", "Service doesn't exist"); + AdvertiseData advertiseData = dataBuilder.build(); - callbackContext.error(returnObj); - return; - } + advertiseCallbackContext = callbackContext; - boolean result = gattServer.removeService(service); - if (result) { - JSONObject returnObj = new JSONObject(); + advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback); + } - addProperty(returnObj, "service", uuid.toString()); - addProperty(returnObj, "status", "serviceRemoved"); + private void stopAdvertisingAction(JSONArray args, CallbackContext callbackContext) { + BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); + if (advertiser == null) { + JSONObject returnObj = new JSONObject(); - callbackContext.success(returnObj); - } else { - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "stopAdvertising"); + addProperty(returnObj, "message", "Advertising isn't supported"); - addProperty(returnObj, "service", uuid.toString()); - addProperty(returnObj, "error", "service"); - addProperty(returnObj, "message", "Failed to remove service"); + callbackContext.error(returnObj); + return; + } - callbackContext.error(returnObj); - } + advertiser.stopAdvertising(advertiseCallback); + + if (isAdvertising) isAdvertising = false; + + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "status", "advertisingStopped"); + callbackContext.success(returnObj); + } + + private void isAdvertisingAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "isAdvertising", isAdvertising); + + callbackContext.success(returnObj); + } + + private void respondAction(JSONArray args, CallbackContext callbackContext) { + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; } - private void removeAllServicesAction(JSONArray args, CallbackContext callbackContext) { - gattServer.clearServices(); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - JSONObject returnObj = new JSONObject(); + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + + int requestId = obj.optInt("requestId", 0); //TODO validate? + int status = obj.optInt("status", 0); + int offset = obj.optInt("offset", 0); + byte[] value = getPropertyBytes(obj, "value"); + + boolean result = gattServer.sendResponse(device, requestId, 0, offset, value); + if (result) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "status", "responded"); + addProperty(returnObj, "requestId", requestId); + callbackContext.success(returnObj); + } else { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "respond"); + addProperty(returnObj, "message", "Failed to respond"); + addProperty(returnObj, "requestId", requestId); + callbackContext.error(returnObj); + } + } - addProperty(returnObj, "status", "allServicesRemoved"); + private void notifyAction(JSONArray args, CallbackContext callbackContext) { + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - callbackContext.success(returnObj); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + + UUID serviceUuid = getUUID(obj.optString("service", null)); + BluetoothGattService service = gattServer.getService(serviceUuid); + if (service == null) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "service"); + addProperty(returnObj, "message", "Service not found"); + callbackContext.error(returnObj); } - private void startAdvertisingAction(JSONArray args, CallbackContext callbackContext) { - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + UUID characteristicUuid = getUUID(obj.optString("characteristic", null)); + BluetoothGattCharacteristic characteristic = service.getCharacteristic(characteristicUuid); + if (characteristic == null) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "characteristic"); + addProperty(returnObj, "message", "Characteristic not found"); + callbackContext.error(returnObj); + } - BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); - if (advertiser == null || !bluetoothAdapter.isMultipleAdvertisementSupported()) { - JSONObject returnObj = new JSONObject(); + byte[] value = getPropertyBytes(obj, "value"); + boolean setResult = characteristic.setValue(value); + if (!setResult) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "respond"); + addProperty(returnObj, "message", "Failed to set value"); + callbackContext.error(returnObj); + } - addProperty(returnObj, "error", "startAdvertising"); - addProperty(returnObj, "message", "Advertising isn't supported"); + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); + byte[] descriptorValue = descriptor.getValue(); - callbackContext.error(returnObj); - return; - } + boolean isIndicate = false; + if (Arrays.equals(descriptorValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) { + isIndicate = true; + } - AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder(); + //Wait for onNotificationSent event + boolean result = gattServer.notifyCharacteristicChanged(device, characteristic, isIndicate); + if (!result) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "error", "notify"); + addProperty(returnObj, "message", "Failed to notify"); + callbackContext.error(returnObj); + } + } - String modeS = obj.optString("mode", "balanced"); - int mode = AdvertiseSettings.ADVERTISE_MODE_BALANCED; - if (modeS.equals("lowLatency")) { - mode = AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY; - } else if (modeS.equals("lowPower")) { - mode = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER; - } - settingsBuilder.setAdvertiseMode(mode); + /** + * ACCESS_FINE_LOCATION + */ - boolean connectable = obj.optBoolean("connectable", true); - settingsBuilder.setConnectable(connectable); + public void hasPermissionAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); - int timeout = obj.optInt("timeout", 1000); - if (timeout < 1 || timeout > 180000) { - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "hasPermission", cordova.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)); - addProperty(returnObj, "error", "startAdvertising"); - addProperty(returnObj, "message", "Invalid timeout (1 - 180000)"); + callbackContext.success(returnObj); + } - callbackContext.error(returnObj); - return; - } - settingsBuilder.setTimeout(timeout); - - String txPowerLevelS = obj.optString("txPowerLevel", "medium"); - int txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM; - if (txPowerLevelS.equals("high")) { - txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH; - } else if (txPowerLevelS.equals("low")) { - txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_LOW; - } else if (txPowerLevelS.equals("ultraLow")) { - txPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW; - } - settingsBuilder.setTxPowerLevel(txPowerLevel); - AdvertiseSettings advertiseSettings = settingsBuilder.build(); + public void requestPermissionAction(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; + } - AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder(); + permissionsCallback = callbackContext; + cordova.requestPermission(this, REQUEST_ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); + } - int manufacturerId = obj.optInt("manufacturerId", 0); - byte[] manufacturerSpecificData = getPropertyBytes(obj, "manufacturerSpecificData"); - if (manufacturerId >= 0 && manufacturerSpecificData != null) { - dataBuilder.addManufacturerData(manufacturerId, manufacturerSpecificData); - } + /** + * BLUETOOTH_SCAN + */ - //dataBuilder.addServiceData(); - UUID uuid = getUUID(obj.optString("service", null)); - if (uuid != null) { - dataBuilder.addServiceUuid(new ParcelUuid(uuid)); - } + public void hasPermissionBtScanAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "hasPermission", Build.VERSION.SDK_INT < Build.VERSION_CODES.S || cordova.hasPermission(Manifest.permission.BLUETOOTH_SCAN)); + + callbackContext.success(returnObj); + } - dataBuilder.setIncludeDeviceName(obj.optBoolean("includeDeviceName", true)); + 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); + } - dataBuilder.setIncludeTxPowerLevel(obj.optBoolean("includeTxPowerLevel", true)); + /** + * BLUETOOTH_CONNECT + */ - AdvertiseData advertiseData = dataBuilder.build(); + public void hasPermissionBtConnectAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); - advertiseCallbackContext = callbackContext; + addProperty(returnObj, "hasPermission", Build.VERSION.SDK_INT < Build.VERSION_CODES.S || cordova.hasPermission(Manifest.permission.BLUETOOTH_CONNECT)); - advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback); + 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; } - private void stopAdvertisingAction(JSONArray args, CallbackContext callbackContext) { - BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); - if (advertiser == null || !bluetoothAdapter.isMultipleAdvertisementSupported()) { - JSONObject returnObj = new JSONObject(); + 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", Build.VERSION.SDK_INT < Build.VERSION_CODES.S || 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; + } - addProperty(returnObj, "error", "startAdvertising"); - addProperty(returnObj, "message", "Advertising isn't supported"); + permissionsCallback = callbackContext; + cordova.requestPermission(this, REQUEST_BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_ADVERTISE); + } - callbackContext.error(returnObj); - return; - } + public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { + if (permissionsCallback == null) { + return; + } - advertiser.stopAdvertising(advertiseCallback); + //Just call hasPermission again to verify + JSONObject returnObj = new JSONObject(); + + 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; + } - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "status", "advertisingStopped"); - callbackContext.success(returnObj); + permissionsCallback.success(returnObj); + } + + private void isLocationEnabledAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, "isLocationEnabled", isLocationEnabled()); + + callbackContext.success(returnObj); + } + + private boolean isLocationEnabled() { + boolean result = true; + + //Only applies to Android 6.0, which requires the users to have location services enabled to scan for devices + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + result = (Settings.Secure.getInt(cordova.getActivity().getContentResolver(), Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF); + } catch (Settings.SettingNotFoundException e) { + result = true; //Probably better to default to true + } } - private void isAdvertisingAction(CallbackContext callbackContext) { - JSONObject returnObj = new JSONObject(); + return result; + } - addProperty(returnObj, "isAdvertising", isAdvertising); + private void requestLocationAction(CallbackContext callbackContext) { + locationCallback = callbackContext; - callbackContext.success(returnObj); + Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); + cordova.startActivityForResult(this, intent, REQUEST_LOCATION_SOURCE_SETTINGS); + } + + private void initializeAction(JSONArray args, CallbackContext callbackContext) { + //Save init callback + initCallbackContext = callbackContext; + + if (bluetoothAdapter != null) { + JSONObject returnObj = new JSONObject(); + PluginResult pluginResult; + + if (bluetoothAdapter.isEnabled()) { + addProperty(returnObj, keyStatus, statusEnabled); + + pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); + } else { + addProperty(returnObj, keyStatus, statusDisabled); + addProperty(returnObj, keyMessage, logNotEnabled); + + pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); + } + + return; } - private void respondAction(JSONArray args, CallbackContext callbackContext) { - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + Activity activity = cordova.getActivity(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + JSONObject obj = getArgsObject(args); - BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + if (obj != null && getStatusReceiver(obj)) { + //Add a receiver to pick up when Bluetooth state changes + activity.registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); + isReceiverRegistered = true; + } - int requestId = obj.optInt("requestId", 0); //TODO validate? - int status = obj.optInt("status", 0); - int offset = obj.optInt("offset", 0); - byte[] value = getPropertyBytes(obj, "value"); + //Get Bluetooth adapter via Bluetooth Manager + BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); + bluetoothAdapter = bluetoothManager.getAdapter(); - boolean result = gattServer.sendResponse(device, requestId, 0, offset, value); - if (result) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "status", "responded"); - addProperty(returnObj, "requestId", requestId); - callbackContext.success(returnObj); - } else { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "respond"); - addProperty(returnObj, "message", "Failed to respond"); - addProperty(returnObj, "requestId", requestId); - callbackContext.error(returnObj); - } + connections = new HashMap>(); + + JSONObject returnObj = new JSONObject(); + + //If it's already enabled, + if (bluetoothAdapter.isEnabled()) { + addProperty(returnObj, keyStatus, statusEnabled); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); + return; } - private void notifyAction(JSONArray args, CallbackContext callbackContext) { - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; + boolean request = false; + if (obj != null) { + request = getRequest(obj); + } + + //Request user to enable Bluetooth + if (request) { + //Request Bluetooth to be enabled + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + cordova.startActivityForResult(this, enableBtIntent, REQUEST_BT_ENABLE); + } else { + //No request, so send back not enabled + addProperty(returnObj, keyStatus, statusDisabled); + addProperty(returnObj, keyMessage, logNotEnabled); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); + } + } + + + /** + * Retrieves a minimal set of adapter details + * (address, name, initialized state, enabled state, scanning state, discoverable state) + */ + private void getAdapterInfoAction(CallbackContext callbackContext) { + JSONObject returnObj = new JSONObject(); + + // Not yet initialized + if (bluetoothAdapter == null) { + Activity activity = cordova.getActivity(); + BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); + BluetoothAdapter bluetoothAdapterTmp = bluetoothManager.getAdapter(); + + // Since the adapter is not officially initialized, retrieve only the address and the name from the temp ad-hoc adapter + addProperty(returnObj, keyAddress, bluetoothAdapterTmp.getAddress()); + addProperty(returnObj, keyName, bluetoothAdapterTmp.getName()); + addProperty(returnObj, keyIsInitialized, false); + addProperty(returnObj, keyIsEnabled, false); + addProperty(returnObj, keyIsScanning, false); + addProperty(returnObj, keyIsDiscoverable, false); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + return; + } else { + // Already initialized, so use the bluetoothAdapter class property to get all the info + addProperty(returnObj, keyAddress, bluetoothAdapter.getAddress()); + addProperty(returnObj, keyName, bluetoothAdapter.getName()); + addProperty(returnObj, keyIsInitialized, true); + addProperty(returnObj, keyIsEnabled, bluetoothAdapter.isEnabled()); + addProperty(returnObj, keyIsScanning, (scanCallbackContext != null)); + addProperty(returnObj, keyIsDiscoverable, bluetoothAdapter.getScanMode() == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + return; + } + + } + + private void enableAction(CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, false)) { + return; + } + + if (isNotDisabled(callbackContext)) { + return; + } + + boolean result = bluetoothAdapter.enable(); + + if (!result) { + //Throw an enabling error + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, keyError, errorEnable); + addProperty(returnObj, keyMessage, logNotEnabled); + + callbackContext.error(returnObj); + } + + //Else listen to initialize callback for enabling + } + + private void disableAction(CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } + + boolean result = bluetoothAdapter.disable(); + + if (!result) { + //Throw a disabling error + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, keyError, errorDisable); + addProperty(returnObj, keyMessage, logNotDisabled); + + callbackContext.error(returnObj); + } + + //Else listen to initialize callback for disabling + } + + private synchronized void startScanAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } + + //If the adapter is already scanning, don't call another scan. + if (scanCallbackContext != null) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorStartScan); + addProperty(returnObj, keyMessage, logAlreadyScanning); + callbackContext.error(returnObj); + return; + } + + //Get the service UUIDs from the arguments + JSONObject obj = getArgsObject(args); + //Default to empty object if null, ideally part of getArgsObject, but not sure how other functions would be affected + if (obj == null) { + obj = new JSONObject(); + } + UUID[] uuids = getServiceUuids(obj); + + //Save the callback context for reporting back found connections. Also the isScanning flag + scanCallbackContext = callbackContext; + + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + boolean result = uuids.length == 0 ? bluetoothAdapter.startLeScan(scanCallbackKitKat) : bluetoothAdapter.startLeScan(uuids, scanCallbackKitKat); + + if (!result) { // scan did not start + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorStartScan); + addProperty(returnObj, keyMessage, logScanStartFail); + callbackContext.error(returnObj); + scanCallbackContext = null; + return; + } + } else { + /* build the ScanFilters */ + ArrayList scanFilter = new ArrayList(); + for (UUID uuid : uuids) { + ScanFilter.Builder builder = new ScanFilter.Builder(); + builder.setServiceUuid(new ParcelUuid(uuid)); + scanFilter.add(builder.build()); + } + + /* build the ScanSetting */ + ScanSettings.Builder scanSettings = new ScanSettings.Builder(); + scanSettings.setReportDelay(0); + + int scanMode = obj.optInt(keyScanMode, ScanSettings.SCAN_MODE_LOW_LATENCY); + try { + scanSettings.setScanMode(scanMode); + } catch (java.lang.IllegalArgumentException e) { + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + int matchMode = obj.optInt(keyMatchMode, ScanSettings.MATCH_MODE_AGGRESSIVE); + try { + scanSettings.setMatchMode(matchMode); + } catch (java.lang.IllegalArgumentException e) { } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; + int matchNum = obj.optInt(keyMatchNum, ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT); + try { + scanSettings.setNumOfMatches(matchNum); + } catch (java.lang.IllegalArgumentException e) { } - BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); - - UUID serviceUuid = getUUID(obj.optString("service", null)); - BluetoothGattService service = gattServer.getService(serviceUuid); - if (service == null) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "service"); - addProperty(returnObj, "message", "Service not found"); - callbackContext.error(returnObj); + + int callbackType = obj.optInt(keyCallbackType, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + try { + scanSettings.setCallbackType(callbackType); + } catch (java.lang.IllegalArgumentException e) { } + } - UUID characteristicUuid = getUUID(obj.optString("characteristic", null)); - BluetoothGattCharacteristic characteristic = service.getCharacteristic(characteristicUuid); - if (characteristic == null) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "characteristic"); - addProperty(returnObj, "message", "Characteristic not found"); - callbackContext.error(returnObj); + //Start the scan with or without service UUIDs + bluetoothAdapter.getBluetoothLeScanner().startScan(scanFilter, scanSettings.build(), scanCallback); + } + + { + JSONObject returnObj = new JSONObject(); + + //Notify user of started scan and save callback + addProperty(returnObj, keyStatus, statusScanStarted); + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + } + } + + private synchronized void stopScanAction(CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } + + JSONObject returnObj = new JSONObject(); + + //Check if already scanning + if (scanCallbackContext == null) { + addProperty(returnObj, keyError, errorStopScan); + addProperty(returnObj, keyMessage, logNotScanning); + callbackContext.error(returnObj); + return; + } + + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + bluetoothAdapter.stopLeScan(scanCallbackKitKat); + } else { + bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback); + } + + //Set scanning state + scanCallbackContext = null; + + //Inform user + addProperty(returnObj, keyStatus, statusScanStopped); + callbackContext.success(returnObj); + } + + private void retrieveConnectedAction(JSONArray args, CallbackContext callbackContext) { + //Filtering by service UUID only works if the service UUIDs have already been discovered/cached previously + if (isNotInitialized(callbackContext, true)) { + return; + } + + /*JSONObject obj = getArgsObject(args); + + UUID[] serviceUuids = serviceUuids = getServiceUuids(obj);*/ + + JSONArray returnArray = new JSONArray(); + + Set devices = bluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : devices) { + if (device.getType() != BluetoothDevice.DEVICE_TYPE_LE && device.getType() != BluetoothDevice.DEVICE_TYPE_DUAL) { + continue; + } + + /*if (serviceUuids != null) + { + ParcelUuid[] uuids = device.getUuids(); + + if (uuids == null) + { + continue; } - byte[] value = getPropertyBytes(obj, "value"); - boolean setResult = characteristic.setValue(value); - if (!setResult) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "respond"); - addProperty(returnObj, "message", "Failed to set value"); - callbackContext.error(returnObj); + Set set = new HashSet(); + + for (ParcelUuid uuid : uuids) + { + set.add(uuid.getUuid()); } - BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); - byte[] descriptorValue = descriptor.getValue(); + boolean flag = false; - boolean isIndicate = false; - if (Arrays.equals(descriptorValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) { - isIndicate = true; + for (UUID uuid : serviceUuids) + { + if (!set.contains(uuid)) + { + flag = true; + break; + } } - //Wait for onNotificationSent event - boolean result = gattServer.notifyCharacteristicChanged(device, characteristic, isIndicate); - if (!result) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "error", "notify"); - addProperty(returnObj, "message", "Failed to notify"); - callbackContext.error(returnObj); + if (flag) + { + continue; } + }*/ + + JSONObject returnObj = new JSONObject(); + + addDevice(returnObj, device); + + returnArray.put(returnObj); } - public void hasPermissionAction(CallbackContext callbackContext) { - JSONObject returnObj = new JSONObject(); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnArray); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + } - addProperty(returnObj, "hasPermission", cordova.hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)); + private void bondAction(JSONArray args, CallbackContext callbackContext) { + if (!isBondReceiverRegistered) { + cordova.getActivity().registerReceiver(mBondReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)); + isBondReceiverRegistered = true; + } - callbackContext.success(returnObj); + if (isNotInitialized(callbackContext, true)) { + return; } - public void requestPermissionAction(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; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - permissionsCallback = callbackContext; - cordova.requestPermission(this, REQUEST_ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { - if (permissionsCallback == null) { - return; - } + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + if (device == null) { + JSONObject returnObj = new JSONObject(); - //Just call hasPermission again to verify - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logNoDevice); + addProperty(returnObj, keyAddress, address); + + callbackContext.error(returnObj); + return; + } + + CallbackContext checkCallback = (CallbackContext) bonds.get(address); + if (checkCallback != null) { + JSONObject returnObj = new JSONObject(); + + addDevice(returnObj, device); - addProperty(returnObj, "requestPermission", cordova.hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)); + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logBonding); - permissionsCallback.success(returnObj); + callbackContext.error(returnObj); + return; } - private void isLocationEnabledAction(CallbackContext callbackContext) { - JSONObject returnObj = new JSONObject(); + int bondState = device.getBondState(); + if (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_BONDING) { + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "isLocationEnabled", isLocationEnabled()); + addDevice(returnObj, device); - callbackContext.success(returnObj); + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, bondState == BluetoothDevice.BOND_BONDED ? logBonded : logBonding); + + callbackContext.error(returnObj); + return; } - private boolean isLocationEnabled() { - boolean result = true; + bonds.put(address, callbackContext); - //Only applies to Android 6.0, which requires the users to have location services enabled to scan for devices - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - try { - result = (Settings.Secure.getInt(cordova.getActivity().getContentResolver(), Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF); - } catch (Settings.SettingNotFoundException e) { - result = true; //Probably better to default to true - } - } + boolean result = device.createBond(); + + if (!result) { + JSONObject returnObj = new JSONObject(); + + addDevice(returnObj, device); - return result; + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logBondFail); + + callbackContext.error(returnObj); + bonds.remove(address); } + } - private void requestLocationAction(CallbackContext callbackContext) { - locationCallback = callbackContext; + private void unbondAction(JSONArray args, CallbackContext callbackContext) { + if (!isBondReceiverRegistered) { + cordova.getActivity().registerReceiver(mBondReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)); + isBondReceiverRegistered = true; + } - Intent intent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); - cordova.startActivityForResult(this, intent, REQUEST_LOCATION_SOURCE_SETTINGS); + if (isNotInitialized(callbackContext, true)) { + return; } - private void initializeAction(JSONArray args, CallbackContext callbackContext) { - //Save init callback - initCallbackContext = callbackContext; + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - if (bluetoothAdapter != null) { - JSONObject returnObj = new JSONObject(); - PluginResult pluginResult; + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - if (bluetoothAdapter.isEnabled()) { - addProperty(returnObj, keyStatus, statusEnabled); + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + if (device == null) { + JSONObject returnObj = new JSONObject(); - pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); - } else { - addProperty(returnObj, keyStatus, statusDisabled); - addProperty(returnObj, keyMessage, logNotEnabled); + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logNoDevice); + addProperty(returnObj, keyAddress, address); - pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); - } + callbackContext.error(returnObj); + return; + } - return; - } + CallbackContext checkCallback = (CallbackContext) bonds.get(address); + if (checkCallback != null) { + JSONObject returnObj = new JSONObject(); - Activity activity = cordova.getActivity(); + addDevice(returnObj, device); - JSONObject obj = getArgsObject(args); + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logBonding); - if (obj != null && getStatusReceiver(obj)) { - //Add a receiver to pick up when Bluetooth state changes - activity.registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); - isReceiverRegistered = true; - } + callbackContext.error(returnObj); + return; + } - //Get Bluetooth adapter via Bluetooth Manager - BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); - bluetoothAdapter = bluetoothManager.getAdapter(); + int bondState = device.getBondState(); + if (bondState == BluetoothDevice.BOND_NONE || bondState == BluetoothDevice.BOND_BONDING) { + JSONObject returnObj = new JSONObject(); - connections = new HashMap>(); + addDevice(returnObj, device); - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorUnbond); + addProperty(returnObj, keyMessage, bondState == BluetoothDevice.BOND_NONE ? logUnbonded : logBonding); - //If it's already enabled, - if (bluetoothAdapter.isEnabled()) { - addProperty(returnObj, keyStatus, statusEnabled); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); - return; - } + callbackContext.error(returnObj); + return; + } - boolean request = false; - if (obj != null) { - request = getRequest(obj); - } + bonds.put(address, callbackContext); - //Request user to enable Bluetooth - if (request) { - //Request Bluetooth to be enabled - Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - cordova.startActivityForResult(this, enableBtIntent, REQUEST_BT_ENABLE); - } else { - //No request, so send back not enabled - addProperty(returnObj, keyStatus, statusDisabled); - addProperty(returnObj, keyMessage, logNotEnabled); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); - } + boolean result = false; + try { + java.lang.reflect.Method mi = device.getClass().getMethod("removeBond"); + Boolean returnValue = (Boolean) mi.invoke(device); + result = returnValue.booleanValue(); + } catch (Exception e) { + Log.d("BLE", e.getMessage()); } - private void enableAction(CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, false)) { - return; - } + if (!result) { + JSONObject returnObj = new JSONObject(); - if (isNotDisabled(callbackContext)) { - return; - } + addDevice(returnObj, device); - boolean result = bluetoothAdapter.enable(); + addProperty(returnObj, keyError, errorUnbond); + addProperty(returnObj, keyMessage, logUnbondFail); - if (!result) { - //Throw an enabling error - JSONObject returnObj = new JSONObject(); + callbackContext.error(returnObj); + bonds.remove(address); + } + } - addProperty(returnObj, keyError, errorEnable); - addProperty(returnObj, keyMessage, logNotEnabled); + private void connectAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - callbackContext.error(returnObj); - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - //Else listen to initialize callback for enabling + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - private void disableAction(CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + if (wasConnected(address, callbackContext)) { + return; + } - boolean result = bluetoothAdapter.disable(); + JSONObject returnObj = new JSONObject(); - if (!result) { - //Throw a disabling error - JSONObject returnObj = new JSONObject(); + //Ensure device exists + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + if (device == null) { + addProperty(returnObj, keyError, errorConnect); + addProperty(returnObj, keyMessage, logNoDevice); + addProperty(returnObj, keyAddress, address); + callbackContext.error(returnObj); + return; + } - addProperty(returnObj, keyError, errorDisable); - addProperty(returnObj, keyMessage, logNotDisabled); + HashMap connection = new HashMap(); - callbackContext.error(returnObj); - } + connection.put(keyState, BluetoothProfile.STATE_CONNECTING); + connection.put(keyDiscoveredState, STATE_UNDISCOVERED); + connection.put(operationConnect, callbackContext); - //Else listen to initialize callback for disabling + boolean autoConnect = false; + if (obj != null) { + autoConnect = obj.optBoolean("autoConnect", false); + } + connections.put(device.getAddress(), connection); + + BluetoothGatt bluetoothGatt = null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + int transportMode = getTransportMode(obj); + bluetoothGatt = device.connectGatt(cordova.getActivity().getApplicationContext(), autoConnect, bluetoothGattCallback, transportMode); + } else { + bluetoothGatt = device.connectGatt(cordova.getActivity().getApplicationContext(), autoConnect, bluetoothGattCallback); } - private synchronized void startScanAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + connection.put(keyPeripheral, bluetoothGatt); - //If the adapter is already scanning, don't call another scan. - if (scanCallbackContext != null) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorStartScan); - addProperty(returnObj, keyMessage, logAlreadyScanning); - callbackContext.error(returnObj); - return; - } + LinkedList queue = new LinkedList(); - //Get the service UUIDs from the arguments - JSONObject obj = getArgsObject(args); - UUID[] uuids = getServiceUuids(obj); + connection.put(keyQueue, queue); + } - //Save the callback context for reporting back found connections. Also the isScanning flag - scanCallbackContext = callbackContext; + private void reconnectAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - boolean result = uuids.length == 0 ? bluetoothAdapter.startLeScan(scanCallbackKitKat) : bluetoothAdapter.startLeScan(uuids, scanCallbackKitKat); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - if (!result) { // scan did not start - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorStartScan); - addProperty(returnObj, keyMessage, logScanStartFail); - callbackContext.error(returnObj); - scanCallbackContext = null; - return; - } - } else { - /* build the ScanFilters */ - ArrayList scanFilter = new ArrayList(); - for (UUID uuid : getServiceUuids(obj)) { - ScanFilter.Builder builder = new ScanFilter.Builder(); - builder.setServiceUuid(new ParcelUuid(uuid)); - scanFilter.add(builder.build()); - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - /* build the ScanSetting */ - ScanSettings.Builder scanSettings = new ScanSettings.Builder(); - scanSettings.setReportDelay(0); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - int scanMode = obj.optInt(keyScanMode, ScanSettings.SCAN_MODE_LOW_LATENCY); - try { - scanSettings.setScanMode(scanMode); - } catch (java.lang.IllegalArgumentException e) { - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - int matchMode = obj.optInt(keyMatchMode, ScanSettings.MATCH_MODE_AGGRESSIVE); - try { - scanSettings.setMatchMode(matchMode); - } catch (java.lang.IllegalArgumentException e) { - } + if (isNotDisconnected(connection, device, callbackContext)) { + return; + } - int matchNum = obj.optInt(keyMatchNum, ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT); - try { - scanSettings.setNumOfMatches(matchNum); - } catch (java.lang.IllegalArgumentException e) { - } + JSONObject returnObj = new JSONObject(); - int callbackType = obj.optInt(keyCallbackType, ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - try { - scanSettings.setCallbackType(callbackType); - } catch (java.lang.IllegalArgumentException e) { - } - } - - Log.d("CN1BT", "startScan " + scanSettings.build()); + addDevice(returnObj, device); - //Start the scan with or without service UUIDs - bluetoothAdapter.getBluetoothLeScanner().startScan(scanFilter, scanSettings.build(), scanCallback); - } + boolean result = bluetoothGatt.connect(); - { - JSONObject returnObj = new JSONObject(); + if (!result) { + addProperty(returnObj, keyError, errorReconnect); + addProperty(returnObj, keyMessage, logReconnectFail); + callbackContext.error(returnObj); + return; + } - //Notify user of started scan and save callback - addProperty(returnObj, keyStatus, statusScanStarted); + connection.put(keyState, BluetoothProfile.STATE_CONNECTING); + //connection.put(keyDiscoveredState, STATE_UNDISCOVERED); //Devices stays discovered even if disconnected (but not closed) + connection.put(operationConnect, callbackContext); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); - } + private void disconnectAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; } - private synchronized void stopScanAction(CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - JSONObject returnObj = new JSONObject(); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - //Check if already scanning - if (scanCallbackContext == null) { - addProperty(returnObj, keyError, errorStopScan); - addProperty(returnObj, keyMessage, logNotScanning); - callbackContext.error(returnObj); - return; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - bluetoothAdapter.stopLeScan(scanCallbackKitKat); - } else { - bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback); - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - //Set scanning state - scanCallbackContext = null; + if (isDisconnected(connection, device, callbackContext)) { + return; + } - //Inform user - addProperty(returnObj, keyStatus, statusScanStopped); - callbackContext.success(returnObj); + int state = Integer.valueOf(connection.get(keyState).toString()); + + JSONObject returnObj = new JSONObject(); + + //Return disconnecting status and keep callback + addDevice(returnObj, device); + + //If it's connecting, cancel attempt and return disconnect + if (state == BluetoothProfile.STATE_CONNECTING) { + addProperty(returnObj, keyStatus, statusDisconnected); + connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(false); + callbackContext.sendPluginResult(pluginResult); + + connection.remove(operationConnect); + } else { + //Very unlikely that this is DISCONNECTING + connection.put(operationConnect, callbackContext); } - private void retrieveConnectedAction(JSONArray args, CallbackContext callbackContext) { - //Filtering by service UUID only works if the service UUIDs have already been discovered/cached previously - if (isNotInitialized(callbackContext, true)) { - return; - } + bluetoothGatt.disconnect(); + } - /*JSONObject obj = getArgsObject(args); + private void closeAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - UUID[] serviceUuids = serviceUuids = getServiceUuids(obj);*/ - JSONArray returnArray = new JSONArray(); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - Set devices = bluetoothAdapter.getBondedDevices(); - for (BluetoothDevice device : devices) { - if (device.getType() != BluetoothDevice.DEVICE_TYPE_LE) { - continue; - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - /*if (serviceUuids != null) - { - ParcelUuid[] uuids = device.getUuids(); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - if (uuids == null) - { - continue; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - Set set = new HashSet(); + /* Make disconnect/close less annoying + if (isNotDisconnected(connection, device, callbackContext)) + { + return; + } + */ - for (ParcelUuid uuid : uuids) - { - set.add(uuid.getUuid()); - } + JSONObject returnObj = new JSONObject(); - boolean flag = false; + addProperty(returnObj, keyStatus, statusClosed); - for (UUID uuid : serviceUuids) - { - if (!set.contains(uuid)) - { - flag = true; - break; - } - } + addDevice(returnObj, device); - if (flag) - { - continue; - } - }*/ - JSONObject returnObj = new JSONObject(); + bluetoothGatt.close(); - addDevice(returnObj, device); + connections.remove(device.getAddress()); - returnArray.put(returnObj); - } + callbackContext.success(returnObj); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnArray); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); + private void discoverAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; } - private void connectAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - if (wasConnected(address, callbackContext)) { - return; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - JSONObject returnObj = new JSONObject(); + if (isNotConnected(connection, device, callbackContext)) { + return; + } - //Ensure device exists - BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); - if (device == null) { - addProperty(returnObj, keyError, errorConnect); - addProperty(returnObj, keyMessage, logNoDevice); - addProperty(returnObj, keyAddress, address); - callbackContext.error(returnObj); - return; - } + JSONObject returnObj = new JSONObject(); - HashMap connection = new HashMap(); + addDevice(returnObj, device); - connection.put(keyState, BluetoothProfile.STATE_CONNECTING); - connection.put(keyDiscoveredState, STATE_UNDISCOVERED); - connection.put(operationConnect, callbackContext); + if (obj == null || !obj.optBoolean("clearCache", false)) { + int discoveredState = Integer.valueOf(connection.get(keyDiscoveredState).toString()); + //Already initiated discovery + if (discoveredState == STATE_DISCOVERING) { + addProperty(returnObj, keyError, errorDiscover); + addProperty(returnObj, keyMessage, logAlreadyDiscovering); + callbackContext.error(returnObj); + return; + } else if (discoveredState == STATE_DISCOVERED) { + //Already discovered + returnObj = getDiscovery(bluetoothGatt); + callbackContext.success(returnObj); + return; + } + } - BluetoothGatt bluetoothGatt = device.connectGatt(cordova.getActivity().getApplicationContext(), false, bluetoothGattCallback); + //Else undiscovered, so start discovery + connection.put(keyDiscoveredState, STATE_DISCOVERING); + connection.put(operationDiscover, callbackContext); - connection.put(keyPeripheral, bluetoothGatt); + if (obj != null && obj.optBoolean("clearCache", false)) { + refreshDeviceCache(bluetoothGatt); + } - connections.put(device.getAddress(), connection); + bluetoothGatt.discoverServices(); + } + + private boolean refreshDeviceCache(BluetoothGatt gatt) { + try { + BluetoothGatt localBluetoothGatt = gatt; + java.lang.reflect.Method localMethod = localBluetoothGatt.getClass().getMethod("refresh", new Class[0]); + if (localMethod != null) { + boolean bool = ((Boolean) localMethod.invoke(localBluetoothGatt, new Object[0])).booleanValue(); + return bool; + } + } + catch (Exception localException) { + Log.e("BLE", "An exception occured while refreshing device cache"); } + return false; + } - private void reconnectAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + private boolean readAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + if (isNotInitialized(callbackContext, true)) { + return false; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + JSONObject obj = getArgsObject(args); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; + } - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } - if (isNotDisconnected(connection, device, callbackContext)) { - return; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - JSONObject returnObj = new JSONObject(); + if (isNotConnected(connection, device, callbackContext)) { + return false; + } - addDevice(returnObj, device); + BluetoothGattService service = getService(bluetoothGatt, obj); - boolean result = bluetoothGatt.connect(); + if (isNotService(service, device, callbackContext)) { + return false; + } - if (!result) { - addProperty(returnObj, keyError, errorReconnect); - addProperty(returnObj, keyMessage, logReconnectFail); - callbackContext.error(returnObj); - return; - } + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - connection.put(keyState, BluetoothProfile.STATE_CONNECTING); - //connection.put(keyDiscoveredState, STATE_UNDISCOVERED); //Devices stays discovered even if disconnected (but not closed) - connection.put(operationConnect, callbackContext); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; } - private void disconnectAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + UUID characteristicUuid = characteristic.getUuid(); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + AddCallback(characteristicUuid, connection, operationRead, callbackContext); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + boolean result = bluetoothGatt.readCharacteristic(characteristic); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + if (!result) { + JSONObject returnObj = new JSONObject(); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + addDevice(returnObj, device); - if (isDisconnected(connection, device, callbackContext)) { - return; - } + addCharacteristic(returnObj, characteristic); - int state = Integer.valueOf(connection.get(keyState).toString()); + addProperty(returnObj, keyError, errorRead); + addProperty(returnObj, keyMessage, logReadFail); - JSONObject returnObj = new JSONObject(); + callbackContext.error(returnObj); - //Return disconnecting status and keep callback - addDevice(returnObj, device); + RemoveCallback(characteristicUuid, connection, operationRead); + + return false; + } + + return true; + } + + private boolean subscribeAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; + + if (isNotInitialized(callbackContext, true)) { + return false; + } + + JSONObject obj = getArgsObject(args); + + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; + } + + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } + + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); + + if (isNotConnected(connection, device, callbackContext)) { + return false; + } + + BluetoothGattService service = getService(bluetoothGatt, obj); + + if (isNotService(service, device, callbackContext)) { + return false; + } - //If it's connecting, cancel attempt and return disconnect - if (state == BluetoothProfile.STATE_CONNECTING) { - addProperty(returnObj, keyStatus, statusDisconnected); - connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(false); - callbackContext.sendPluginResult(pluginResult); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; + } - connection.remove(operationConnect); - } else { - //Very unlikely that this is DISCONNECTING - connection.put(operationConnect, callbackContext); - } + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); - bluetoothGatt.disconnect(); + if (isNotDescriptor(descriptor, device, callbackContext)) { + return false; } - private void closeAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } - - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + UUID characteristicUuid = characteristic.getUuid(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + JSONObject returnObj = new JSONObject(); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + addDevice(returnObj, device); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + addCharacteristic(returnObj, characteristic); - /* Make disconnect/close less annoying - if (isNotDisconnected(connection, device, callbackContext)) - { - return; - } - */ - JSONObject returnObj = new JSONObject(); + SequentialCallbackContext checkExisting = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe); + if (checkExisting != null) { + addProperty(returnObj, keyError, errorSubscription); + addProperty(returnObj, keyMessage, logSubscribeAlready); + callbackContext.error(returnObj); + return false; + } - addProperty(returnObj, keyStatus, statusClosed); + //Subscribe to the characteristic + boolean result = bluetoothGatt.setCharacteristicNotification(characteristic, true); - addDevice(returnObj, device); + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); + callbackContext.error(returnObj); + return false; + } - bluetoothGatt.close(); + result = false; - connections.remove(device.getAddress()); + //Use properties to determine whether notification or indication should be used + if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + } else { + result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + } - callbackContext.success(returnObj); + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); + callbackContext.error(returnObj); + return false; } - private void discoverAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + AddSequentialCallbackContext(characteristicUuid, connection, operationSubscribe, callbackContext); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + //Write the descriptor value + result = bluetoothGatt.writeDescriptor(descriptor); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorFail); + callbackContext.error(returnObj); + RemoveCallback(characteristicUuid, connection, operationSubscribe); + return false; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + return true; + } - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + private boolean unsubscribeAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - if (isNotConnected(connection, device, callbackContext)) { - return; - } + if (isNotInitialized(callbackContext, true)) { + return false; + } - JSONObject returnObj = new JSONObject(); + JSONObject obj = getArgsObject(args); - addDevice(returnObj, device); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; + } - int discoveredState = Integer.valueOf(connection.get(keyDiscoveredState).toString()); - //Already initiated discovery - if (discoveredState == STATE_DISCOVERING) { - addProperty(returnObj, keyError, errorDiscover); - addProperty(returnObj, keyMessage, logAlreadyDiscovering); - callbackContext.error(returnObj); - return; - } else if (discoveredState == STATE_DISCOVERED) { - //Already discovered - returnObj = getDiscovery(bluetoothGatt); - callbackContext.success(returnObj); - return; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } - //Else undiscovered, so start discovery - connection.put(keyDiscoveredState, STATE_DISCOVERING); - connection.put(operationDiscover, callbackContext); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - bluetoothGatt.discoverServices(); + if (isNotConnected(connection, device, callbackContext)) { + return false; } - private boolean readAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } + BluetoothGattService service = getService(bluetoothGatt, obj); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + if (isNotService(service, device, callbackContext)) { + return false; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; + } - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + if (isNotDescriptor(descriptor, device, callbackContext)) { + return false; + } - BluetoothGattService service = getService(bluetoothGatt, obj); + UUID characteristicUuid = characteristic.getUuid(); - if (isNotService(service, device, callbackContext)) { - return false; - } + JSONObject returnObj = new JSONObject(); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + addDevice(returnObj, device); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + addCharacteristic(returnObj, characteristic); - UUID characteristicUuid = characteristic.getUuid(); + SequentialCallbackContext checkExisting = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe); + if (checkExisting == null) { + addProperty(returnObj, keyError, errorSubscription); + addProperty(returnObj, keyMessage, logUnsubscribeAlready); + callbackContext.error(returnObj); + return false; + } + RemoveCallback(characteristicUuid, connection, operationSubscribe); - AddCallback(characteristicUuid, connection, operationRead, callbackContext); + //Set the descriptor for disabling notification/indication + boolean result = descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); - boolean result = bluetoothGatt.readCharacteristic(characteristic); + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); + callbackContext.error(returnObj); + return false; + } - if (!result) { - JSONObject returnObj = new JSONObject(); + AddCallback(characteristicUuid, connection, operationUnsubscribe, callbackContext); - addDevice(returnObj, device); + //Write the actual descriptor value + result = bluetoothGatt.writeDescriptor(descriptor); - addCharacteristic(returnObj, characteristic); + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorFail); + callbackContext.error(returnObj); + RemoveCallback(characteristicUuid, connection, operationUnsubscribe); + return false; + } - addProperty(returnObj, keyError, errorRead); - addProperty(returnObj, keyMessage, logReadFail); + return true; + } - callbackContext.error(returnObj); + private boolean writeAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - RemoveCallback(characteristicUuid, connection, operationRead); + if (isNotInitialized(callbackContext, true)) { + return false; + } - return false; - } + JSONObject obj = getArgsObject(args); - return true; + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; } - private boolean subscribeAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + if (isNotConnected(connection, device, callbackContext)) { + return false; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + BluetoothGattService service = getService(bluetoothGatt, obj); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + if (isNotService(service, device, callbackContext)) { + return false; + } - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - BluetoothGattService service = getService(bluetoothGatt, obj); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; + } - if (isNotService(service, device, callbackContext)) { - return false; - } + UUID characteristicUuid = characteristic.getUuid(); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + JSONObject returnObj = new JSONObject(); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + addDevice(returnObj, device); - BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); + addCharacteristic(returnObj, characteristic); - if (isNotDescriptor(descriptor, device, callbackContext)) { - return false; - } + byte[] value = getPropertyBytes(obj, keyValue); - UUID characteristicUuid = characteristic.getUuid(); + if (value == null) { + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteValueNotFound); + callbackContext.error(returnObj); + return false; + } - JSONObject returnObj = new JSONObject(); + int writeType = this.getWriteType(obj); + characteristic.setWriteType(writeType); - addDevice(returnObj, device); + boolean result = characteristic.setValue(value); - addCharacteristic(returnObj, characteristic); + if (!result) { + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteValueNotSet); + callbackContext.error(returnObj); + return false; + } - CallbackContext checkExisting = GetCallback(characteristicUuid, connection, operationSubscribe); - if (checkExisting != null) { - addProperty(returnObj, keyError, errorSubscription); - addProperty(returnObj, keyMessage, logSubscribeAlready); - callbackContext.error(returnObj); - return false; - } + AddCallback(characteristicUuid, connection, operationWrite, callbackContext); - boolean result = false; + result = bluetoothGatt.writeCharacteristic(characteristic); - //Use properties to determine whether notification or indication should be used - if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { - result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - } else { - result = descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); - } + if (!result) { + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteFail); + callbackContext.error(returnObj); + RemoveCallback(characteristicUuid, connection, operationWrite); - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); - callbackContext.error(returnObj); - return false; - } + return false; + } - AddCallback(characteristicUuid, connection, operationSubscribe, callbackContext); + return true; + } - //Write the descriptor value - result = bluetoothGatt.writeDescriptor(descriptor); + private void writeQAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorFail); - callbackContext.error(returnObj); - RemoveCallback(characteristicUuid, connection, operationSubscribe); - return false; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - return true; + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - private boolean unsubscribeAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + if (isNotConnected(connection, device, callbackContext)) { + return; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + BluetoothGattService service = getService(bluetoothGatt, obj); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + if (isNotService(service, device, callbackContext)) { + return; + } - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - BluetoothGattService service = getService(bluetoothGatt, obj); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return; + } - if (isNotService(service, device, callbackContext)) { - return false; - } + UUID characteristicUuid = characteristic.getUuid(); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + JSONObject returnObj = new JSONObject(); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + addDevice(returnObj, device); - BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid); + addCharacteristic(returnObj, characteristic); - if (isNotDescriptor(descriptor, device, callbackContext)) { - return false; - } + byte[] value = getPropertyBytes(obj, keyValue); - UUID characteristicUuid = characteristic.getUuid(); + if (value == null) { + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteValueNotFound); + callbackContext.error(returnObj); + return; + } - JSONObject returnObj = new JSONObject(); + int writeType = this.getWriteType(obj); + characteristic.setWriteType(writeType); - addDevice(returnObj, device); + AddCallback(characteristicUuid, connection, operationWrite, callbackContext); - addCharacteristic(returnObj, characteristic); + queueQuick.clear(); - CallbackContext checkExisting = GetCallback(characteristicUuid, connection, operationSubscribe); - if (checkExisting == null) { - addProperty(returnObj, keyError, errorSubscription); - addProperty(returnObj, keyMessage, logUnsubscribeAlready); - callbackContext.error(returnObj); - return false; - } - RemoveCallback(characteristicUuid, connection, operationSubscribe); + int length = value.length; + int chunkSize = obj.optInt("chunkSize", 20); + int offset = 0; - //Set the descriptor for disabling notification/indication - boolean result = descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); + do { + int thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset; - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); - callbackContext.error(returnObj); - return false; - } + byte[] chunk = Arrays.copyOfRange(value, offset, offset + thisChunkSize); - AddCallback(characteristicUuid, connection, operationUnsubscribe, callbackContext); + offset += thisChunkSize; - //Write the actual descriptor value - result = bluetoothGatt.writeDescriptor(descriptor); + queueQuick.add(chunk); - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorFail); - callbackContext.error(returnObj); - RemoveCallback(characteristicUuid, connection, operationUnsubscribe); - return false; - } + } while (offset < length); - return true; - } + writeQ(connection, characteristic, bluetoothGatt); + } - private boolean writeAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } + private void writeQ(HashMap connection, BluetoothGattCharacteristic characteristic, BluetoothGatt bluetoothGatt) { + byte[] value = queueQuick.poll(); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + if (value == null) { + JSONObject returnObj = new JSONObject(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + addDevice(returnObj, bluetoothGatt.getDevice()); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + addCharacteristic(returnObj, characteristic); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, "Queue was empty"); - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); + RemoveCallback(characteristic.getUuid(), connection, operationWrite); - BluetoothGattService service = getService(bluetoothGatt, obj); + callbackContext.error(returnObj); - if (isNotService(service, device, callbackContext)) { - return false; - } + return; + } - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + boolean result = characteristic.setValue(value); + if (!result) { + queueQuick.clear(); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + JSONObject returnObj = new JSONObject(); - UUID characteristicUuid = characteristic.getUuid(); + addDevice(returnObj, bluetoothGatt.getDevice()); - JSONObject returnObj = new JSONObject(); + addCharacteristic(returnObj, characteristic); - addDevice(returnObj, device); + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteValueNotSet); - addCharacteristic(returnObj, characteristic); + CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); + RemoveCallback(characteristic.getUuid(), connection, operationWrite); - byte[] value = getPropertyBytes(obj, keyValue); + callbackContext.error(returnObj); + return; + } - if (value == null) { - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteValueNotFound); - callbackContext.error(returnObj); - return false; - } + result = bluetoothGatt.writeCharacteristic(characteristic); + if (!result) { + queueQuick.clear(); - int writeType = this.getWriteType(obj); - characteristic.setWriteType(writeType); + JSONObject returnObj = new JSONObject(); - boolean result = characteristic.setValue(value); + addDevice(returnObj, bluetoothGatt.getDevice()); - if (!result) { - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteValueNotSet); - callbackContext.error(returnObj); - return false; - } + addCharacteristic(returnObj, characteristic); - AddCallback(characteristicUuid, connection, operationWrite, callbackContext); + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteFail); - result = bluetoothGatt.writeCharacteristic(characteristic); + CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); + RemoveCallback(characteristic.getUuid(), connection, operationWrite); - if (!result) { - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteFail); - callbackContext.error(returnObj); - RemoveCallback(characteristicUuid, connection, operationWrite); + callbackContext.error(returnObj); + } + } - return false; - } + private boolean readDescriptorAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - return true; + if (isNotInitialized(callbackContext, true)) { + return false; } - private void writeQAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + JSONObject obj = getArgsObject(args); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + if (isNotConnected(connection, device, callbackContext)) { + return false; + } - if (isNotConnected(connection, device, callbackContext)) { - return; - } + BluetoothGattService service = getService(bluetoothGatt, obj); - BluetoothGattService service = getService(bluetoothGatt, obj); + if (isNotService(service, device, callbackContext)) { + return false; + } - if (isNotService(service, device, callbackContext)) { - return; - } + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; + } - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return; - } + BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); - UUID characteristicUuid = characteristic.getUuid(); + if (isNotDescriptor(descriptor, device, callbackContext)) { + return false; + } - JSONObject returnObj = new JSONObject(); + UUID descriptorUuid = descriptor.getUuid(); + UUID characteristicUuid = characteristic.getUuid(); - addDevice(returnObj, device); + AddDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead, callbackContext); - addCharacteristic(returnObj, characteristic); + boolean result = bluetoothGatt.readDescriptor(descriptor); - byte[] value = getPropertyBytes(obj, keyValue); + if (!result) { + JSONObject returnObj = new JSONObject(); - if (value == null) { - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteValueNotFound); - callbackContext.error(returnObj); - return; - } + addDevice(returnObj, device); - int writeType = this.getWriteType(obj); - characteristic.setWriteType(writeType); + addDescriptor(returnObj, descriptor); - AddCallback(characteristicUuid, connection, operationWrite, callbackContext); + addProperty(returnObj, keyError, errorReadDescriptor); + addProperty(returnObj, keyMessage, logReadDescriptorFail); - queueQuick.clear(); + callbackContext.error(returnObj); - int length = value.length; - int chunkSize = 20; - int offset = 0; + RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); - do { - int thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset; + return false; + } - byte[] chunk = Arrays.copyOfRange(value, offset, offset + thisChunkSize); + return true; + } - offset += thisChunkSize; + private boolean writeDescriptorAction(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - queueQuick.add(chunk); + if (isNotInitialized(callbackContext, true)) { + return false; + } - } while (offset < length); + JSONObject obj = getArgsObject(args); - writeQ(connection, characteristic, bluetoothGatt); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return false; } - private void writeQ(HashMap connection, BluetoothGattCharacteristic characteristic, BluetoothGatt bluetoothGatt) { - byte[] value = queueQuick.poll(); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return false; + } - if (value == null) { - JSONObject returnObj = new JSONObject(); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - addDevice(returnObj, bluetoothGatt.getDevice()); + if (isNotConnected(connection, device, callbackContext)) { + return false; + } - addCharacteristic(returnObj, characteristic); + BluetoothGattService service = getService(bluetoothGatt, obj); - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, "Queue was empty"); + if (isNotService(service, device, callbackContext)) { + return false; + } - CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); - RemoveCallback(characteristic.getUuid(), connection, operationWrite); + BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); - callbackContext.error(returnObj); + if (isNotCharacteristic(characteristic, device, callbackContext)) { + return false; + } - return; - } + BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); - boolean result = characteristic.setValue(value); - if (!result) { - queueQuick.clear(); + if (isNotDescriptor(descriptor, device, callbackContext)) { + return false; + } - JSONObject returnObj = new JSONObject(); + UUID descriptorUuid = descriptor.getUuid(); + UUID characteristicUuid = characteristic.getUuid(); - addDevice(returnObj, bluetoothGatt.getDevice()); + JSONObject returnObj = new JSONObject(); - addCharacteristic(returnObj, characteristic); + addDevice(returnObj, device); - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteValueNotSet); + addDescriptor(returnObj, descriptor); - CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); - RemoveCallback(characteristic.getUuid(), connection, operationWrite); + //Let subscribe/unsubscribe take care of it + if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorNotAllowed); + callbackContext.error(returnObj); + return false; + } - callbackContext.error(returnObj); - return; - } + //TODO get property type - result = bluetoothGatt.writeCharacteristic(characteristic); - if (!result) { - queueQuick.clear(); + byte[] value = getPropertyBytes(obj, keyValue); - JSONObject returnObj = new JSONObject(); + if (value == null) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorValueNotFound); + callbackContext.error(returnObj); + return false; + } - addDevice(returnObj, bluetoothGatt.getDevice()); + boolean result = descriptor.setValue(value); - addCharacteristic(returnObj, characteristic); + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); + callbackContext.error(returnObj); + return false; + } - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteFail); + AddDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite, callbackContext); - CallbackContext callbackContext = GetCallback(characteristic.getUuid(), connection, operationWrite); - RemoveCallback(characteristic.getUuid(), connection, operationWrite); + result = bluetoothGatt.writeDescriptor(descriptor); - callbackContext.error(returnObj); - } + if (!result) { + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorFail); + callbackContext.error(returnObj); + RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); + return false; } - private boolean readDescriptorAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } + return true; + } - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + private void rssiAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - BluetoothGattService service = getService(bluetoothGatt, obj); + if (isNotConnected(connection, device, callbackContext)) { + return; + } - if (isNotService(service, device, callbackContext)) { - return false; - } + connection.put(operationRssi, callbackContext); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + boolean result = bluetoothGatt.readRemoteRssi(); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + if (!result) { + JSONObject returnObj = new JSONObject(); - BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); + addDevice(returnObj, device); - if (isNotDescriptor(descriptor, device, callbackContext)) { - return false; - } + addProperty(returnObj, keyError, errorRssi); + addProperty(returnObj, keyMessage, logRssiFail); - UUID descriptorUuid = descriptor.getUuid(); - UUID characteristicUuid = characteristic.getUuid(); + callbackContext.error(returnObj); - AddDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead, callbackContext); + connection.remove(operationRssi); + return; + } + } - boolean result = bluetoothGatt.readDescriptor(descriptor); + private void mtuAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - if (!result) { - JSONObject returnObj = new JSONObject(); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - addDevice(returnObj, device); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - addDescriptor(returnObj, descriptor); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - addProperty(returnObj, keyError, errorReadDescriptor); - addProperty(returnObj, keyMessage, logReadDescriptorFail); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - callbackContext.error(returnObj); + if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + JSONObject returnObj = new JSONObject(); - RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); + addDevice(returnObj, device); - return false; - } + addProperty(returnObj, keyError, errorMtu); + addProperty(returnObj, keyMessage, logRequiresAPI21); - return true; + callbackContext.error(returnObj); + return; } - private boolean writeDescriptorAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return false; - } - - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return false; - } + if (isNotConnected(connection, device, callbackContext)) { + return; + } - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return false; - } + connection.put(operationMtu, callbackContext); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return false; - } + int mtu = getMtu(obj); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + boolean result = bluetoothGatt.requestMtu(mtu); - if (isNotConnected(connection, device, callbackContext)) { - return false; - } + if (!result) { + JSONObject returnObj = new JSONObject(); - BluetoothGattService service = getService(bluetoothGatt, obj); + addDevice(returnObj, device); - if (isNotService(service, device, callbackContext)) { - return false; - } + addProperty(returnObj, keyError, errorMtu); + addProperty(returnObj, keyMessage, logMtuFail); - BluetoothGattCharacteristic characteristic = getCharacteristic(obj, service); + callbackContext.error(returnObj); - if (isNotCharacteristic(characteristic, device, callbackContext)) { - return false; - } + connection.remove(operationMtu); + } + } - BluetoothGattDescriptor descriptor = getDescriptor(obj, characteristic); + private void isInitializedAction(CallbackContext callbackContext) { + boolean result = (bluetoothAdapter != null); - if (isNotDescriptor(descriptor, device, callbackContext)) { - return false; - } + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyIsInitialized, result); - UUID descriptorUuid = descriptor.getUuid(); - UUID characteristicUuid = characteristic.getUuid(); + callbackContext.success(returnObj); + } - JSONObject returnObj = new JSONObject(); + private void isEnabledAction(CallbackContext callbackContext) { + boolean result = (bluetoothAdapter != null && bluetoothAdapter.isEnabled()); - addDevice(returnObj, device); + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyIsEnabled, result); - addDescriptor(returnObj, descriptor); + callbackContext.success(returnObj); + } - //Let subscribe/unsubscribe take care of it - if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorNotAllowed); - callbackContext.error(returnObj); - return false; - } + private void isScanningAction(CallbackContext callbackContext) { + boolean result = (scanCallbackContext != null); - //TODO get property type - byte[] value = getPropertyBytes(obj, keyValue); + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyIsScanning, result); - if (value == null) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorValueNotFound); - callbackContext.error(returnObj); - return false; - } + callbackContext.success(returnObj); + } - boolean result = descriptor.setValue(value); + private void isBondedAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorValueNotSet); - callbackContext.error(returnObj); - return false; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - AddDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite, callbackContext); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - result = bluetoothGatt.writeDescriptor(descriptor); + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); + if (device == null) { + JSONObject returnObj = new JSONObject(); - if (!result) { - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorFail); - callbackContext.error(returnObj); - RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); - return false; - } + addProperty(returnObj, keyError, errorBond); + addProperty(returnObj, keyMessage, logNoDevice); + addProperty(returnObj, keyAddress, address); - return true; + callbackContext.error(returnObj); + return; } - private void rssiAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + boolean result = (device.getBondState() == BluetoothDevice.BOND_BONDED); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + JSONObject returnObj = new JSONObject(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + addProperty(returnObj, keyIsBonded, result); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + addDevice(returnObj, device); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + callbackContext.success(returnObj); + } - if (isNotConnected(connection, device, callbackContext)) { - return; - } + private void wasConnectedAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - connection.put(operationRssi, callbackContext); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - boolean result = bluetoothGatt.readRemoteRssi(); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - if (!result) { - JSONObject returnObj = new JSONObject(); + HashMap connection = connections.get(address); + if (connection == null) { + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); + addProperty(returnObj, "wasConnected", false); - addProperty(returnObj, keyError, errorRssi); - addProperty(returnObj, keyMessage, logRssiFail); + addProperty(returnObj, keyAddress, address); - callbackContext.error(returnObj); + callbackContext.success(returnObj); - connection.remove(operationRssi); - return; - } + return; } - private void mtuAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } - - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } - - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + BluetoothDevice device = bluetoothGatt.getDevice(); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + JSONObject returnObj = new JSONObject(); - if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, "wasConnected", true); - addDevice(returnObj, device); + addDevice(returnObj, device); - addProperty(returnObj, keyError, errorMtu); - addProperty(returnObj, keyMessage, logRequiresAPI21); + callbackContext.success(returnObj); + } - callbackContext.error(returnObj); - return; - } + private void isConnectedAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - if (isNotConnected(connection, device, callbackContext)) { - return; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - connection.put(operationMtu, callbackContext); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; + } - int mtu = getMtu(obj); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - boolean result = bluetoothGatt.requestMtu(mtu); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - if (!result) { - JSONObject returnObj = new JSONObject(); + int state = Integer.valueOf(connection.get(keyState).toString()); - addDevice(returnObj, device); + boolean result = (state == BluetoothAdapter.STATE_CONNECTED); - addProperty(returnObj, keyError, errorMtu); - addProperty(returnObj, keyMessage, logMtuFail); + BluetoothDevice device = bluetoothGatt.getDevice(); - callbackContext.error(returnObj); + JSONObject returnObj = new JSONObject(); - connection.remove(operationMtu); - } - } + addProperty(returnObj, keyIsConnected, result); - private void isInitializedAction(CallbackContext callbackContext) { - boolean result = (bluetoothAdapter != null); + addDevice(returnObj, device); - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyIsInitialized, result); + callbackContext.success(returnObj); + } - callbackContext.success(returnObj); + private void isDiscoveredAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; } - private void isEnabledAction(CallbackContext callbackContext) { - boolean result = (bluetoothAdapter != null && bluetoothAdapter.isEnabled()); - - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyIsEnabled, result); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - callbackContext.success(returnObj); + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - private void isScanningAction(CallbackContext callbackContext) { - boolean result = (scanCallbackContext != null); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyIsScanning, result); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = bluetoothGatt.getDevice(); - callbackContext.success(returnObj); + if (isNotConnected(connection, device, callbackContext)) { + return; } - private void wasConnectedAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + int state = Integer.valueOf(connection.get(keyDiscoveredState).toString()); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + boolean result = (state == STATE_DISCOVERED); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + JSONObject returnObj = new JSONObject(); - HashMap connection = connections.get(address); - if (connection == null) { - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyIsDiscovered, result); - addProperty(returnObj, "wasConnected", false); + addDevice(returnObj, device); - addProperty(returnObj, keyAddress, address); + callbackContext.success(returnObj); + } - callbackContext.success(returnObj); + private void requestConnectionPriorityAction(JSONArray args, CallbackContext callbackContext) { + if (isNotInitialized(callbackContext, true)) { + return; + } - return; - } + JSONObject obj = getArgsObject(args); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - BluetoothDevice device = bluetoothGatt.getDevice(); + String address = getAddress(obj); - JSONObject returnObj = new JSONObject(); + if (isNotAddress(address, callbackContext)) { + return; + } - addProperty(returnObj, "wasConnected", true); + HashMap connection = wasNeverConnected(address, callbackContext); + if (connection == null) { + return; + } - addDevice(returnObj, device); + BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - callbackContext.success(returnObj); - } + if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { + JSONObject returnObj = new JSONObject(); - private void isConnectedAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + addDevice(returnObj, bluetoothGatt.getDevice()); - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + addProperty(returnObj, keyError, errorRequestConnectionPriority); + addProperty(returnObj, keyMessage, logRequiresAPI21); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + callbackContext.error(returnObj); + return; + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + String priority = obj.optString(keyConnectionPriority, null); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + int androidPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED; - int state = Integer.valueOf(connection.get(keyState).toString()); + if (priority == null) { + JSONObject returnObj = new JSONObject(); - boolean result = (state == BluetoothAdapter.STATE_CONNECTED); + addDevice(returnObj, bluetoothGatt.getDevice()); - BluetoothDevice device = bluetoothGatt.getDevice(); + addProperty(returnObj, keyError, errorRequestConnectionPriority); + addProperty(returnObj, keyMessage, logRequestConnectionPriorityNull); - JSONObject returnObj = new JSONObject(); + callbackContext.error(returnObj); + return; + } else if (priority.equals(propertyConnectionPriorityLow)) { + androidPriority = BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER; + } else if (priority.equals(propertyConnectionPriorityBalanced)) { + androidPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED; + } else if (priority.equals(propertyConnectionPriorityHigh)) { + androidPriority = BluetoothGatt.CONNECTION_PRIORITY_HIGH; + } else { + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyIsConnected, result); + addDevice(returnObj, bluetoothGatt.getDevice()); - addDevice(returnObj, device); + addProperty(returnObj, keyError, errorRequestConnectionPriority); + addProperty(returnObj, keyMessage, logRequestConnectionPriorityInvalid); - callbackContext.success(returnObj); + callbackContext.error(returnObj); + return; } - private void isDiscoveredAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } - - JSONObject obj = getArgsObject(args); - if (isNotArgsObject(obj, callbackContext)) { - return; - } + boolean result = bluetoothGatt.requestConnectionPriority(androidPriority); + if (!result) { + JSONObject returnObj = new JSONObject(); - String address = getAddress(obj); - if (isNotAddress(address, callbackContext)) { - return; - } + addDevice(returnObj, bluetoothGatt.getDevice()); - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + addProperty(returnObj, keyError, errorRequestConnectionPriority); + addProperty(returnObj, keyMessage, logRequestConnectionPriorityFailed); - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = bluetoothGatt.getDevice(); + callbackContext.error(returnObj); + } else { + JSONObject returnObj = new JSONObject(); - if (isNotConnected(connection, device, callbackContext)) { - return; - } + addProperty(returnObj, keyStatus, statusConnectionPriorityRequested); - int state = Integer.valueOf(connection.get(keyDiscoveredState).toString()); + addDevice(returnObj, bluetoothGatt.getDevice()); - boolean result = (state == STATE_DISCOVERED); + callbackContext.success(returnObj); + } + } - JSONObject returnObj = new JSONObject(); + private void setPinAction(JSONArray args, CallbackContext callbackContext) { + Log.d("BLE","set pin"); + if (mPairingRequestReceiver!=null) { + cordova.getActivity().unregisterReceiver(mPairingRequestReceiver); + } - addProperty(returnObj, keyIsDiscovered, result); + if (isNotInitialized(callbackContext, true)) { + return; + } - addDevice(returnObj, device); + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - callbackContext.success(returnObj); + final String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - private void requestConnectionPriorityAction(JSONArray args, CallbackContext callbackContext) { - if (isNotInitialized(callbackContext, true)) { - return; - } + final String pin = getPin(obj); + if (pin==null) { + return; + } - JSONObject obj = getArgsObject(args); + Log.d("BLE","set pin " + address + " " + pin); + JSONObject returnObj = new JSONObject(); + try { + mPairingRequestReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d("BLE", "on receive"); + String action = intent.getAction(); + if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) { + BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if(bluetoothDevice.getAddress().equalsIgnoreCase(address)){ + int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); + if (type == BluetoothDevice.PAIRING_VARIANT_PIN) { + bluetoothDevice.setPin(pin.getBytes()); + abortBroadcast(); + } + } + } + } + }; + IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); + intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + cordova.getActivity().registerReceiver(mPairingRequestReceiver, intentFilter); + addProperty(returnObj, keyStatus, "pinSet"); + callbackContext.success(returnObj); + } catch (Exception e) { + Log.d("BLE","exception " + e.getMessage()); + addProperty(returnObj, keyError, "setPin"); + addProperty(returnObj, keyMessage, "Failed to set pin"); + callbackContext.error(returnObj); + } + return; - if (isNotArgsObject(obj, callbackContext)) { - return; - } + } - String address = getAddress(obj); + @Override + public void onDestroy() { + super.onDestroy(); - if (isNotAddress(address, callbackContext)) { - return; - } + if (isReceiverRegistered) { + cordova.getActivity().unregisterReceiver(mReceiver); + } + if (isBondReceiverRegistered) { + cordova.getActivity().unregisterReceiver(mBondReceiver); + } + if(mPairingRequestReceiver!=null){ + cordova.getActivity().unregisterReceiver(mPairingRequestReceiver); + } + } - HashMap connection = wasNeverConnected(address, callbackContext); - if (connection == null) { - return; - } + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (initCallbackContext == null) { + return; + } - BluetoothGatt bluetoothGatt = (BluetoothGatt) connection.get(keyPeripheral); + if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + JSONObject returnObj = new JSONObject(); + PluginResult pluginResult; - if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - JSONObject returnObj = new JSONObject(); + switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { + case BluetoothAdapter.STATE_OFF: + addProperty(returnObj, keyStatus, statusDisabled); + addProperty(returnObj, keyMessage, logNotEnabled); - addDevice(returnObj, bluetoothGatt.getDevice()); + connections = new HashMap>(); + if (scanCallbackContext != null) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); + // Unsure why some devices return null + if (scanner != null) { + scanner.stopScan(scanCallback); + } + } + } + scanCallbackContext = null; - addProperty(returnObj, keyError, errorRequestConnectionPriority); - addProperty(returnObj, keyMessage, logRequiresAPI21); + // Reset isAdvertising when adapter is off (if STATE_TURNING_OFF doesn't trigger) + if (isAdvertising) isAdvertising = false; - callbackContext.error(returnObj); - return; - } + gattServer = null; - String priority = obj.optString(keyConnectionPriority, null); + pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); - int androidPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED; + if (initPeripheralCallback != null) initPeripheralCallback.sendPluginResult(pluginResult); - if (priority == null) { - JSONObject returnObj = new JSONObject(); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + // Reset isAdvertising when adapter is turning off + if (isAdvertising) isAdvertising = false; - addDevice(returnObj, bluetoothGatt.getDevice()); + // Make sure gattServer is not null (in case this triggers when it is null) + if (gattServer != null) gattServer.close(); + break; + case BluetoothAdapter.STATE_ON: - addProperty(returnObj, keyError, errorRequestConnectionPriority); - addProperty(returnObj, keyMessage, logRequestConnectionPriorityNull); + addProperty(returnObj, keyStatus, statusEnabled); - callbackContext.error(returnObj); - return; - } else if (priority.equals(propertyConnectionPriorityLow)) { - androidPriority = BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER; - } else if (priority.equals(propertyConnectionPriorityBalanced)) { - androidPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED; - } else if (priority.equals(propertyConnectionPriorityHigh)) { - androidPriority = BluetoothGatt.CONNECTION_PRIORITY_HIGH; - } else { - JSONObject returnObj = new JSONObject(); + initGattServer(); - addDevice(returnObj, bluetoothGatt.getDevice()); + pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); - addProperty(returnObj, keyError, errorRequestConnectionPriority); - addProperty(returnObj, keyMessage, logRequestConnectionPriorityInvalid); + if (initPeripheralCallback != null) initPeripheralCallback.sendPluginResult(pluginResult); - callbackContext.error(returnObj); - return; + break; } + } + } + }; - boolean result = bluetoothGatt.requestConnectionPriority(androidPriority); - if (!result) { - JSONObject returnObj = new JSONObject(); + private final BroadcastReceiver mBondReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); + int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - addDevice(returnObj, bluetoothGatt.getDevice()); + String address = device.getAddress(); - addProperty(returnObj, keyError, errorRequestConnectionPriority); - addProperty(returnObj, keyMessage, logRequestConnectionPriorityFailed); + CallbackContext callback = (CallbackContext) bonds.get(address); + if (callback == null) { + return; + } - callbackContext.error(returnObj); - } else { - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyStatus, statusConnectionPriorityRequested); + addDevice(returnObj, device); - addDevice(returnObj, bluetoothGatt.getDevice()); + boolean keepCallback = false; - callbackContext.success(returnObj); + switch (bondState) { + case BluetoothDevice.BOND_BONDED: + addProperty(returnObj, keyStatus, statusBonded); + break; + case BluetoothDevice.BOND_BONDING: + addProperty(returnObj, keyStatus, statusBonding); + keepCallback = true; + break; + case BluetoothDevice.BOND_NONE: + addProperty(returnObj, keyStatus, statusUnbonded); + break; } - } - @Override - public void onDestroy() { - super.onDestroy(); - - if (isReceiverRegistered) { - cordova.getActivity().unregisterReceiver(mReceiver); + if (!keepCallback) { + bonds.remove(address); } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (initCallbackContext == null) { - return; - } - if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - JSONObject returnObj = new JSONObject(); - PluginResult pluginResult; + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(keepCallback); + callback.sendPluginResult(pluginResult); + } + } + }; - switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { - case BluetoothAdapter.STATE_OFF: - addProperty(returnObj, keyStatus, statusDisabled); - addProperty(returnObj, keyMessage, logNotEnabled); + private BroadcastReceiver mPairingRequestReceiver; - connections = new HashMap>(); - scanCallbackContext = null; + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + //If this was a Bluetooth enablement request... + if (requestCode == REQUEST_BT_ENABLE) { + //If callback doesnt exist, no reason to proceed + if (initCallbackContext == null) { + return; + } - pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); + //Whether the result code was successful or not, just check whether Bluetooth is enabled + if (!bluetoothAdapter.isEnabled()) { + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyStatus, statusDisabled); + addProperty(returnObj, keyMessage, logNotEnabled); - break; - case BluetoothAdapter.STATE_ON: + PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, returnObj); + pluginResult.setKeepCallback(true); + initCallbackContext.sendPluginResult(pluginResult); + } + } else if (requestCode == REQUEST_LOCATION_SOURCE_SETTINGS) { + if (locationCallback != null) { + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyStatus, statusEnabled); + addProperty(returnObj, "requestLocation", isLocationEnabled()); - pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); + locationCallback.success(returnObj); - break; - } - } - } - }; + locationCallback = null; + } + } + } + //Scan Callback for KitKat + private LeScanCallback scanCallbackKitKat = new LeScanCallback() { @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - //If this was a Bluetooth enablement request... - if (requestCode == REQUEST_BT_ENABLE) { - //If callback doesnt exist, no reason to proceed - if (initCallbackContext == null) { - return; - } + public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { + synchronized (BluetoothLePlugin.this) { - //Whether the result code was successful or not, just check whether Bluetooth is enabled - if (!bluetoothAdapter.isEnabled()) { - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyStatus, statusDisabled); - addProperty(returnObj, keyMessage, logNotEnabled); + if (scanCallbackContext == null) { + return; + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, returnObj); - pluginResult.setKeepCallback(true); - initCallbackContext.sendPluginResult(pluginResult); - } - } else if (requestCode == REQUEST_LOCATION_SOURCE_SETTINGS) { - if (locationCallback != null) { - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, "requestLocation", isLocationEnabled()); + addDevice(returnObj, device); - locationCallback.success(returnObj); + addProperty(returnObj, keyRssi, rssi); + addPropertyBytes(returnObj, keyAdvertisement, scanRecord); + addProperty(returnObj, keyStatus, statusScanResult); - locationCallback = null; - } - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + scanCallbackContext.sendPluginResult(pluginResult); + } } + }; + + //API 21+ Scan and Advertise Callbacks + private ScanCallback scanCallback = null; + private AdvertiseCallback advertiseCallback = null; + + private void createScanCallback() { + scanCallback = new ScanCallback() { + @Override + public void onBatchScanResults(List results) { + if (scanCallbackContext == null) + return; + } + + @Override + public void onScanFailed(int errorCode) { + synchronized (BluetoothLePlugin.this) { + if (scanCallbackContext == null) + return; - //Scan Callback for KitKat - private LeScanCallback scanCallbackKitKat = new LeScanCallback() { - @Override - public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { - synchronized (BluetoothLePlugin.this) { - - if (scanCallbackContext == null) { - return; - } - - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorStartScan); + + if (errorCode == ScanCallback.SCAN_FAILED_ALREADY_STARTED) { + addProperty(returnObj, keyMessage, "Scan already started"); + } else if (errorCode == ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED) { + addProperty(returnObj, keyMessage, "Application registration failed"); + } else if (errorCode == ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED) { + addProperty(returnObj, keyMessage, "Feature unsupported"); + } else if (errorCode == ScanCallback.SCAN_FAILED_INTERNAL_ERROR) { + addProperty(returnObj, keyMessage, "Internal error"); + } else { + addProperty(returnObj, keyMessage, logScanStartFail); + } + + scanCallbackContext.error(returnObj); + scanCallbackContext = null; + } + } + + @Override + public void onScanResult(int callbackType, ScanResult result) { + synchronized (BluetoothLePlugin.this) { + if (scanCallbackContext == null) + return; - addDevice(returnObj, device); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyRssi, rssi); - addPropertyBytes(returnObj, keyAdvertisement, scanRecord); - addProperty(returnObj, keyStatus, statusScanResult); + addDevice(returnObj, result.getDevice()); + if(result.getScanRecord().getDeviceName() != null){ + addProperty(returnObj, keyName, result.getScanRecord().getDeviceName().replace("\0", "")); + } + addProperty(returnObj, keyRssi, result.getRssi()); + addPropertyBytes(returnObj, keyAdvertisement, result.getScanRecord().getBytes()); + addProperty(returnObj, keyStatus, statusScanResult); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - scanCallbackContext.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + scanCallbackContext.sendPluginResult(pluginResult); } + } }; + } - //API 21+ Scan and Advertise Callbacks - private ScanCallback scanCallback = null; - private AdvertiseCallback advertiseCallback = null; - - private void createScanCallback() { - Log.d("CN1BT", "createScanCallback"); - scanCallback = new ScanCallback() { - @Override - public void onBatchScanResults(List results) { - Log.d("CN1BT", "onBatchScanResults " + results.toString()); - if (scanCallbackContext == null) { - return; - } - } - - @Override - public void onScanFailed(int errorCode) { - Log.d("CN1BT", "onScanFailed " + errorCode); - synchronized (BluetoothLePlugin.this) { - if (scanCallbackContext == null) { - return; - } - - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorStartScan); - - if (errorCode == ScanCallback.SCAN_FAILED_ALREADY_STARTED) { - addProperty(returnObj, keyMessage, "Scan already started"); - } else if (errorCode == ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED) { - addProperty(returnObj, keyMessage, "Application registration failed"); - } else if (errorCode == ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED) { - addProperty(returnObj, keyMessage, "Feature unsupported"); - } else if (errorCode == ScanCallback.SCAN_FAILED_INTERNAL_ERROR) { - addProperty(returnObj, keyMessage, "Internal error"); - } else { - addProperty(returnObj, keyMessage, logScanStartFail); - } - - scanCallbackContext.error(returnObj); - scanCallbackContext = null; - } - } - - @Override - public void onScanResult(int callbackType, ScanResult result) { - Log.d("CN1BT", "onScanResult " + callbackType + " " + result); - synchronized (BluetoothLePlugin.this) { - if (scanCallbackContext == null) { - return; - } + private void createAdvertiseCallback() { + advertiseCallback = new AdvertiseCallback() { + @Override + public void onStartFailure(int errorCode) { + isAdvertising = false; - JSONObject returnObj = new JSONObject(); + if (advertiseCallbackContext == null) + return; - addDevice(returnObj, result.getDevice()); - addProperty(returnObj, keyRssi, result.getRssi()); - addPropertyBytes(returnObj, keyAdvertisement, result.getScanRecord().getBytes()); - addProperty(returnObj, keyStatus, statusScanResult); + JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, "startAdvertising"); + + if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED) { + addProperty(returnObj, keyMessage, "Already started"); + } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE) { + addProperty(returnObj, keyMessage, "Too large data"); + } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED) { + addProperty(returnObj, keyMessage, "Feature unsupported"); + } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR) { + addProperty(returnObj, keyMessage, "Internal error"); + } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) { + addProperty(returnObj, keyMessage, "Too many advertisers"); + } else { + addProperty(returnObj, keyMessage, "Advertising error"); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - scanCallbackContext.sendPluginResult(pluginResult); - } - } - }; - } + advertiseCallbackContext.error(returnObj); + advertiseCallbackContext = null; + } - private void createAdvertiseCallback() { - advertiseCallback = new AdvertiseCallback() { - @Override - public void onStartFailure(int errorCode) { - isAdvertising = false; + @Override + public void onStartSuccess(AdvertiseSettings settingsInEffect) { + isAdvertising = true; - if (advertiseCallbackContext == null) { - return; - } + if (advertiseCallbackContext == null) + return; - JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, "startAdvertising"); - - if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED) { - addProperty(returnObj, keyMessage, "Already started"); - } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE) { - addProperty(returnObj, keyMessage, "Too large data"); - } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED) { - addProperty(returnObj, keyMessage, "Feature unsupported"); - } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR) { - addProperty(returnObj, keyMessage, "Internal error"); - } else if (errorCode == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) { - addProperty(returnObj, keyMessage, "Too many advertisers"); - } else { - addProperty(returnObj, keyMessage, "Advertising error"); - } + JSONObject returnObj = new JSONObject(); - advertiseCallbackContext.error(returnObj); - advertiseCallbackContext = null; - } + addProperty(returnObj, "mode", settingsInEffect.getMode()); + addProperty(returnObj, "timeout", settingsInEffect.getTimeout()); + addProperty(returnObj, "txPowerLevel", settingsInEffect.getTxPowerLevel()); + addProperty(returnObj, "isConnectable", settingsInEffect.isConnectable()); - @Override - public void onStartSuccess(AdvertiseSettings settingsInEffect) { - isAdvertising = true; + addProperty(returnObj, keyStatus, "advertisingStarted"); - if (advertiseCallbackContext == null) { - return; - } + advertiseCallbackContext.success(returnObj); + advertiseCallbackContext = null; + } + }; + } - JSONObject returnObj = new JSONObject(); + private String formatUuid(UUID uuid) { + String uuidString = uuid.toString().toUpperCase(); - addProperty(returnObj, "mode", settingsInEffect.getMode()); - addProperty(returnObj, "timeout", settingsInEffect.getTimeout()); - addProperty(returnObj, "txPowerLevel", settingsInEffect.getTxPowerLevel()); - addProperty(returnObj, "isConnectable", settingsInEffect.isConnectable()); + if (uuidString.startsWith(baseUuidStart) && uuidString.endsWith(baseUuidEnd)) { + return uuidString.substring(4, 8); + } - addProperty(returnObj, keyStatus, "advertisingStarted"); + return uuidString; + } - advertiseCallbackContext.success(returnObj); - advertiseCallbackContext = null; - } - }; + //Helpers for BluetoothGatt classes + private UUID getUUID(String value) { + if (value == null) { + return null; } - private String formatUuid(UUID uuid) { - String uuidString = uuid.toString(); + if (value.length() == 4) { + value = baseUuidStart + value + baseUuidEnd; + } - if (uuidString.startsWith(baseUuidStart) && uuidString.endsWith(baseUuidEnd)) { - return uuidString.substring(4, 8); - } + UUID uuid = null; - return uuidString; + try { + uuid = UUID.fromString(value); + } catch (Exception ex) { + return null; } - //Helpers for BluetoothGatt classes - private UUID getUUID(String value) { - if (value == null) { - return null; - } + return uuid; + } - if (value.length() == 4) { - value = baseUuidStart + value + baseUuidEnd; - } + private BluetoothGattService getService(BluetoothGatt bluetoothGatt, JSONObject obj) { + UUID uuid = getUUID(obj.optString("service", null)); + + int serviceIndex = obj.optInt("serviceIndex", 0); + int found = 0; + List services = bluetoothGatt.getServices(); + for (BluetoothGattService service : services) { + if (service.getUuid().equals(uuid) && serviceIndex == found) { + return service; + } else if (service.getUuid().equals(uuid) && serviceIndex != found) { + found++; + } + } - UUID uuid = null; + return null; + } - try { - uuid = UUID.fromString(value); - } catch (Exception ex) { - return null; - } + private BluetoothGattCharacteristic getCharacteristic(JSONObject obj, BluetoothGattService service) { + UUID uuid = getUUID(obj.optString("characteristic", null)); - return uuid; + int characteristicIndex = obj.optInt("characteristicIndex", 0); + int found = 0; + List characteristics = service.getCharacteristics(); + for (BluetoothGattCharacteristic characteristic : characteristics) { + if (characteristic.getUuid().equals(uuid) && characteristicIndex == found) { + return characteristic; + } else if (characteristic.getUuid().equals(uuid) && characteristicIndex != found) { + found++; + } } - private BluetoothGattService getService(BluetoothGatt bluetoothGatt, JSONObject obj) { - UUID uuid = getUUID(obj.optString("service", null)); + return null; + } - BluetoothGattService service = bluetoothGatt.getService(uuid); + private BluetoothGattDescriptor getDescriptor(JSONObject obj, BluetoothGattCharacteristic characteristic) { + UUID uuid = getUUID(obj.optString("descriptor", null)); - if (service == null) { - return null; - } + BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid); - return service; + if (descriptor == null) { + return null; } - private BluetoothGattCharacteristic getCharacteristic(JSONObject obj, BluetoothGattService service) { - UUID uuid = getUUID(obj.optString("characteristic", null)); + return descriptor; + } - BluetoothGattCharacteristic characteristic = service.getCharacteristic(uuid); + //Helpers for Callbacks + private void queueAdd(Operation operation) { + JSONArray args = operation.args; + CallbackContext callbackContext = operation.callbackContext; - if (characteristic == null) { - return null; - } + JSONObject obj = getArgsObject(args); + if (isNotArgsObject(obj, callbackContext)) { + return; + } - return characteristic; + String address = getAddress(obj); + if (isNotAddress(address, callbackContext)) { + return; } - private BluetoothGattDescriptor getDescriptor(JSONObject obj, BluetoothGattCharacteristic characteristic) { - UUID uuid = getUUID(obj.optString("descriptor", null)); + HashMap connection = connections.get(address); - BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid); + // When Connection is Valid + if (connection != null) { + // Get operation Queue from connection + LinkedList queue = (LinkedList) connection.get(keyQueue); - if (descriptor == null) { - return null; - } + // When queue is not valid + if(queue == null) { + // Create dummy queue + queue = new LinkedList<>(); + + // Add dummy queue to connection + connection.put(keyQueue, queue); + } + + // Add operation to queue + queue.add(operation); - return descriptor; + // Start Queue + queueStart(connection); + } + } + + private void queueStart(HashMap connection) { + LinkedList queue = (LinkedList) connection.get(keyQueue); + //Attempt to start the queue whenever a new operation is added + if (queue.size() > 1) { + //There was already something in the queue so wait for queueNext to be called + return; } - //Helpers for Callbacks - private void queueStart() { - //Attempt to start the queue whenever a new operation is added - if (queue.size() > 1) { - //There was already something in the queue so wait for queueNext to be called - return; - } + //Added to queue and immediately ready for processing + queueNext(connection); + } + + private void queueNext(HashMap connection) { + LinkedList queue = (LinkedList) connection.get(keyQueue); + //Start to process the next command + Operation operation = queue.peek(); + //If the operation was unsuccessful, remove immediately and start next + boolean result = true; + if (operation.type.equals("read")) { + result = readAction(operation); + } else if (operation.type.equals("write")) { + result = writeAction(operation); + } else if (operation.type.equals("readDescriptor")) { + result = readDescriptorAction(operation); + } else if (operation.type.equals("writeDescriptor")) { + result = writeDescriptorAction(operation); + } else if (operation.type.equals("subscribe")) { + result = subscribeAction(operation); + } else { + result = unsubscribeAction(operation); + } + if (!result) { + queueRemove(connection); + } + } - //Added to queue and immediately ready for processing - queueNext(); + private void queueRemove(HashMap connection) { + LinkedList queue = (LinkedList) connection.get(keyQueue); + //Ensure the queue has something in it, this should never be empty + if (queue.size() == 0) { + return; } + //Remove front of the queue + queue.poll(); - private void queueNext() { - //Start to process the next command - Operation operation = queue.peek(); - //If the operation was unsuccessful, remove immediately and start next - boolean result = true; - if (operation.type.equals("read")) { - result = readAction(operation.args, operation.callbackContext); - } else if (operation.type.equals("write")) { - result = writeAction(operation.args, operation.callbackContext); - } else if (operation.type.equals("readDescriptor")) { - result = readDescriptorAction(operation.args, operation.callbackContext); - } else if (operation.type.equals("writeDescriptor")) { - result = writeDescriptorAction(operation.args, operation.callbackContext); - } else if (operation.type.equals("subscribe")) { - result = subscribeAction(operation.args, operation.callbackContext); - } else { - result = unsubscribeAction(operation.args, operation.callbackContext); - } - if (!result) { - queueRemove(); - } + //See if there's anything left to process + if (queue.size() == 0) { + return; } - private void queueRemove() { - //Ensure the queue has something in it, this should never be empty - if (queue.size() == 0) { - return; - } - //Remove front of the queue - queue.poll(); + //Start the next item + queueNext(connection); + } - //See if there's anything left to process - if (queue.size() == 0) { - return; - } + private HashMap EnsureCallback(UUID characteristicUuid, HashMap connection) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); - //Start the next item - queueNext(); + if (characteristicCallbacks != null) { + return characteristicCallbacks; } - private HashMap EnsureCallback(UUID characteristicUuid, HashMap connection) { - HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + characteristicCallbacks = new HashMap(); + connection.put(characteristicUuid, characteristicCallbacks); - if (characteristicCallbacks != null) { - return characteristicCallbacks; - } + return characteristicCallbacks; + } - characteristicCallbacks = new HashMap(); - connection.put(characteristicUuid, characteristicCallbacks); + private void AddCallback(UUID characteristicUuid, HashMap connection, String operationType, CallbackContext callbackContext) { + HashMap characteristicCallbacks = EnsureCallback(characteristicUuid, connection); - return characteristicCallbacks; - } + characteristicCallbacks.put(operationType, callbackContext); + } - private void AddCallback(UUID characteristicUuid, HashMap connection, String operationType, CallbackContext callbackContext) { - HashMap characteristicCallbacks = EnsureCallback(characteristicUuid, connection); + private CallbackContext GetCallback(UUID characteristicUuid, HashMap connection, String operationType) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); - characteristicCallbacks.put(operationType, callbackContext); + if (characteristicCallbacks == null) { + return null; } - private CallbackContext GetCallback(UUID characteristicUuid, HashMap connection, String operationType) { - HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + //This may return null + return (CallbackContext) characteristicCallbacks.get(operationType); + } - if (characteristicCallbacks == null) { - return null; - } + private void AddSequentialCallbackContext(UUID characteristicUuid, HashMap connection, String operationType, CallbackContext callbackContext) { + HashMap characteristicCallbacks = EnsureCallback(characteristicUuid, connection); - //This may return null - return (CallbackContext) characteristicCallbacks.get(operationType); - } + characteristicCallbacks.put(operationType, new SequentialCallbackContext(callbackContext)); + } - private CallbackContext[] GetCallbacks(HashMap connection) { - ArrayList callbacks = new ArrayList(); + private SequentialCallbackContext GetSequentialCallbackContext(UUID characteristicUuid, HashMap connection, String operationType) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); - for (Object key : connection.keySet()) { + if (characteristicCallbacks == null) { + return null; + } - if (key instanceof String) { - if (key.equals(operationDiscover) || key.equals(operationRssi) || key.equals(operationMtu)) { - CallbackContext callback = (CallbackContext) connection.get(key); - if (callback == null) { - continue; - } + //This may return null + return (SequentialCallbackContext) characteristicCallbacks.get(operationType); + } - callbacks.add(callback); - } + private CallbackContext[] GetCallbacks(HashMap connection) { + ArrayList callbacks = new ArrayList(); - continue; - } + for (Object key : connection.keySet()) { - if (!(key instanceof UUID)) { - continue; - } + if (key instanceof String) { + if (key.equals(operationDiscover) || key.equals(operationRssi) || key.equals(operationMtu)) { + CallbackContext callback = (CallbackContext) connection.get(key); + if (callback == null) { + continue; + } - HashMap characteristic = (HashMap) connection.get(key); - GetMoreCallbacks(characteristic, callbacks); + callbacks.add(callback); } - return callbacks.toArray(new CallbackContext[callbacks.size()]); - } + continue; + } - private void GetMoreCallbacks(HashMap lower, ArrayList callbacks) { - for (Object key : lower.keySet()) { - if (key instanceof UUID) { - HashMap next = (HashMap) lower.get(key); - GetMoreCallbacks(next, callbacks); - continue; - } + if (!(key instanceof UUID)) { + continue; + } - if (!(key instanceof String)) { - continue; - } + HashMap characteristic = (HashMap) connection.get(key); + GetMoreCallbacks(characteristic, callbacks); + } - CallbackContext callback = (CallbackContext) lower.get(key); + return callbacks.toArray(new CallbackContext[callbacks.size()]); + } + + private void GetMoreCallbacks(HashMap lower, ArrayList callbacks) { + for (Object key : lower.keySet()) { + if (key instanceof UUID) { + HashMap next = (HashMap) lower.get(key); + GetMoreCallbacks(next, callbacks); + continue; + } + + if (!(key instanceof String)) { + continue; + } + + CallbackContext callback; + if (key.equals(operationSubscribe)) { + callback = ((SequentialCallbackContext) lower.get(key)).getContext(); + } else { + callback = (CallbackContext) lower.get(key); + } + + if (callback == null) { + continue; + } + + callbacks.add(callback); + } + } - if (callback == null) { - continue; - } + private void RemoveCallback(UUID characteristicUuid, HashMap connection, String operationType) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); - callbacks.add(callback); - } + if (characteristicCallbacks == null) { + return; } - private void RemoveCallback(UUID characteristicUuid, HashMap connection, String operationType) { - HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + characteristicCallbacks.remove(operationType); + } - if (characteristicCallbacks == null) { - return; - } + private HashMap EnsureDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection) { + HashMap characteristicCallbacks = EnsureCallback(characteristicUuid, connection); - characteristicCallbacks.remove(operationType); + HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); + + if (descriptorCallbacks != null) { + return descriptorCallbacks; } - private HashMap EnsureDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection) { - HashMap characteristicCallbacks = EnsureCallback(characteristicUuid, connection); + descriptorCallbacks = new HashMap(); + characteristicCallbacks.put(descriptorUuid, descriptorCallbacks); - HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); + return descriptorCallbacks; + } - if (descriptorCallbacks != null) { - return descriptorCallbacks; - } + private void AddDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType, CallbackContext callbackContext) { + HashMap descriptorCallbacks = EnsureDescriptorCallback(descriptorUuid, characteristicUuid, connection); - descriptorCallbacks = new HashMap(); - characteristicCallbacks.put(descriptorUuid, descriptorCallbacks); + descriptorCallbacks.put(operationType, callbackContext); + } - return descriptorCallbacks; + private CallbackContext GetDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + + if (characteristicCallbacks == null) { + return null; } - private void AddDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType, CallbackContext callbackContext) { - HashMap descriptorCallbacks = EnsureDescriptorCallback(descriptorUuid, characteristicUuid, connection); + HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); - descriptorCallbacks.put(operationType, callbackContext); + if (descriptorCallbacks == null) { + return null; } - private CallbackContext GetDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType) { - HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + //This may return null + return (CallbackContext) descriptorCallbacks.get(operationType); + } - if (characteristicCallbacks == null) { - return null; - } + private void RemoveDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType) { + HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); - HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); + if (characteristicCallbacks == null) { + return; + } - if (descriptorCallbacks == null) { - return null; - } + HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); - //This may return null - return (CallbackContext) descriptorCallbacks.get(operationType); + if (descriptorCallbacks == null) { + return; } - private void RemoveDescriptorCallback(UUID descriptorUuid, UUID characteristicUuid, HashMap connection, String operationType) { - HashMap characteristicCallbacks = (HashMap) connection.get(characteristicUuid); + descriptorCallbacks.remove(descriptorUuid); + } - if (characteristicCallbacks == null) { - return; - } + //Helpers to Check Conditions + private boolean isNotInitialized(CallbackContext callbackContext, boolean checkIsNotEnabled) { + if (bluetoothAdapter == null) { + JSONObject returnObj = new JSONObject(); - HashMap descriptorCallbacks = (HashMap) characteristicCallbacks.get(descriptorUuid); + addProperty(returnObj, keyError, errorInitialize); + addProperty(returnObj, keyMessage, logNotInit); - if (descriptorCallbacks == null) { - return; - } + callbackContext.error(returnObj); - descriptorCallbacks.remove(descriptorUuid); + return true; } - //Helpers to Check Conditions - private boolean isNotInitialized(CallbackContext callbackContext, boolean checkIsNotEnabled) { - if (bluetoothAdapter == null) { - JSONObject returnObj = new JSONObject(); + if (checkIsNotEnabled) { + return isNotEnabled(callbackContext); + } else { + return false; + } + } - addProperty(returnObj, keyError, errorInitialize); - addProperty(returnObj, keyMessage, logNotInit); + private boolean isNotEnabled(CallbackContext callbackContext) { + if (!bluetoothAdapter.isEnabled()) { + JSONObject returnObj = new JSONObject(); - callbackContext.error(returnObj); + addProperty(returnObj, keyError, errorEnable); + addProperty(returnObj, keyMessage, logNotEnabled); - return true; - } + callbackContext.error(returnObj); - if (checkIsNotEnabled) { - return isNotEnabled(callbackContext); - } else { - return false; - } + return true; } - private boolean isNotEnabled(CallbackContext callbackContext) { - if (!bluetoothAdapter.isEnabled()) { - JSONObject returnObj = new JSONObject(); + return false; + } - addProperty(returnObj, keyError, errorEnable); - addProperty(returnObj, keyMessage, logNotEnabled); + private boolean isNotDisabled(CallbackContext callbackContext) { + if (bluetoothAdapter.isEnabled()) { + JSONObject returnObj = new JSONObject(); - callbackContext.error(returnObj); + addProperty(returnObj, keyError, errorDisable); + addProperty(returnObj, keyMessage, logNotDisabled); - return true; - } + callbackContext.error(returnObj); - return false; + return true; } - private boolean isNotDisabled(CallbackContext callbackContext) { - if (bluetoothAdapter.isEnabled()) { - JSONObject returnObj = new JSONObject(); + return false; + } - addProperty(returnObj, keyError, errorDisable); - addProperty(returnObj, keyMessage, logNotDisabled); + private boolean isNotArgsObject(JSONObject obj, CallbackContext callbackContext) { + if (obj != null) { + return false; + } - callbackContext.error(returnObj); + JSONObject returnObj = new JSONObject(); - return true; - } + addProperty(returnObj, keyError, errorArguments); + addProperty(returnObj, keyMessage, logNoArgObj); - return false; - } + callbackContext.error(returnObj); - private boolean isNotArgsObject(JSONObject obj, CallbackContext callbackContext) { - if (obj != null) { - return false; - } + return true; + } - JSONObject returnObj = new JSONObject(); + private boolean isNotAddress(String address, CallbackContext callbackContext) { + if (address == null) { + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorArguments); - addProperty(returnObj, keyMessage, logNoArgObj); + addProperty(returnObj, keyError, errorConnect); + addProperty(returnObj, keyMessage, logNoAddress); - callbackContext.error(returnObj); + callbackContext.error(returnObj); + return true; + } - return true; + return false; + } + + private boolean isNotService(BluetoothGattService service, BluetoothDevice device, CallbackContext callbackContext) { + if (service != null) { + return false; } - private boolean isNotAddress(String address, CallbackContext callbackContext) { - if (address == null) { - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorConnect); - addProperty(returnObj, keyMessage, logNoAddress); + addProperty(returnObj, keyError, errorService); + addProperty(returnObj, keyMessage, logNoService); - callbackContext.error(returnObj); - return true; - } + addDevice(returnObj, device); + + callbackContext.error(returnObj); - return false; + return true; + } + + private boolean isNotCharacteristic(BluetoothGattCharacteristic characteristic, BluetoothDevice device, CallbackContext callbackContext) { + if (characteristic != null) { + return false; } - private boolean isNotService(BluetoothGattService service, BluetoothDevice device, CallbackContext callbackContext) { - if (service != null) { - return false; - } + JSONObject returnObj = new JSONObject(); - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorCharacteristic); + addProperty(returnObj, keyMessage, logNoCharacteristic); - addProperty(returnObj, keyError, errorService); - addProperty(returnObj, keyMessage, logNoService); + addDevice(returnObj, device); - addDevice(returnObj, device); + callbackContext.error(returnObj); - callbackContext.error(returnObj); + return true; + } - return true; + private boolean isNotDescriptor(BluetoothGattDescriptor descriptor, BluetoothDevice device, CallbackContext callbackContext) { + if (descriptor != null) { + return false; } - private boolean isNotCharacteristic(BluetoothGattCharacteristic characteristic, BluetoothDevice device, CallbackContext callbackContext) { - if (characteristic != null) { - return false; - } + JSONObject returnObj = new JSONObject(); - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorDescriptor); + addProperty(returnObj, keyMessage, logNoDescriptor); - addProperty(returnObj, keyError, errorCharacteristic); - addProperty(returnObj, keyMessage, logNoCharacteristic); + addDevice(returnObj, device); - addDevice(returnObj, device); + callbackContext.error(returnObj); - callbackContext.error(returnObj); + return true; + } + + private boolean isNotDisconnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { + int state = Integer.valueOf(connection.get(keyState).toString()); - return true; + //Determine whether the device is currently connected including connecting and disconnecting + //Certain actions like connect and reconnect can only be done while completely disconnected + if (state == BluetoothProfile.STATE_DISCONNECTED) { + return false; } - private boolean isNotDescriptor(BluetoothGattDescriptor descriptor, BluetoothDevice device, CallbackContext callbackContext) { - if (descriptor != null) { - return false; - } + JSONObject returnObj = new JSONObject(); - JSONObject returnObj = new JSONObject(); + addProperty(returnObj, keyError, errorIsNotDisconnected); + addProperty(returnObj, keyMessage, logIsNotDisconnected); - addProperty(returnObj, keyError, errorDescriptor); - addProperty(returnObj, keyMessage, logNoDescriptor); + addDevice(returnObj, device); - addDevice(returnObj, device); + callbackContext.error(returnObj); - callbackContext.error(returnObj); + return true; + } + + private boolean isDisconnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { + int state = Integer.valueOf(connection.get(keyState).toString()); - return true; + //Determine whether the device is currently disconnected NOT including connecting and disconnecting + //Certain actions like disconnect can be done while connected, connecting, disconnecting + if (state != BluetoothProfile.STATE_DISCONNECTED) { + return false; } - private boolean isNotDisconnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { - int state = Integer.valueOf(connection.get(keyState).toString()); + JSONObject returnObj = new JSONObject(); - //Determine whether the device is currently connected including connecting and disconnecting - //Certain actions like connect and reconnect can only be done while completely disconnected - if (state == BluetoothProfile.STATE_DISCONNECTED) { - return false; - } + addProperty(returnObj, keyError, errorIsDisconnected); + addProperty(returnObj, keyMessage, logIsDisconnected); - JSONObject returnObj = new JSONObject(); + addDevice(returnObj, device); - addProperty(returnObj, keyError, errorIsNotDisconnected); - addProperty(returnObj, keyMessage, logIsNotDisconnected); + callbackContext.error(returnObj); - addDevice(returnObj, device); + return true; + } - callbackContext.error(returnObj); + private boolean isNotConnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { + int state = Integer.valueOf(connection.get(keyState).toString()); - return true; + //Determine whether the device is currently disconnected including connecting and disconnecting + //Certain actions like read/write operations can only be done while completely connected + if (state == BluetoothProfile.STATE_CONNECTED) { + return false; } - private boolean isDisconnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { - int state = Integer.valueOf(connection.get(keyState).toString()); + JSONObject returnObj = new JSONObject(); - //Determine whether the device is currently disconnected NOT including connecting and disconnecting - //Certain actions like disconnect can be done while connected, connecting, disconnecting - if (state != BluetoothProfile.STATE_DISCONNECTED) { - return false; - } + addProperty(returnObj, keyError, errorIsNotConnected); + addProperty(returnObj, keyMessage, logIsNotConnected); - JSONObject returnObj = new JSONObject(); + addDevice(returnObj, device); - addProperty(returnObj, keyError, errorIsDisconnected); - addProperty(returnObj, keyMessage, logIsDisconnected); + callbackContext.error(returnObj); + + return true; + } + + private boolean wasConnected(String address, CallbackContext callbackContext) { + HashMap connection = connections.get(address); + if (connection != null) { + BluetoothGatt peripheral = (BluetoothGatt) connection.get(keyPeripheral); + BluetoothDevice device = peripheral.getDevice(); + + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, keyError, errorConnect); + addProperty(returnObj, keyMessage, logPreviouslyConnected); - addDevice(returnObj, device); + addDevice(returnObj, device); - callbackContext.error(returnObj); + callbackContext.error(returnObj); - return true; + return true; } + return false; + } - private boolean isNotConnected(HashMap connection, BluetoothDevice device, CallbackContext callbackContext) { - int state = Integer.valueOf(connection.get(keyState).toString()); + private HashMap wasNeverConnected(String address, CallbackContext callbackContext) { + HashMap connection = connections.get(address); + if (connection != null) { + return connection; + } - //Determine whether the device is currently disconnected including connecting and disconnecting - //Certain actions like read/write operations can only be done while completely connected - if (state == BluetoothProfile.STATE_CONNECTED) { - return false; - } + JSONObject returnObj = new JSONObject(); + + addProperty(returnObj, keyError, errorNeverConnected); + addProperty(returnObj, keyMessage, logNeverConnected); + addProperty(returnObj, keyAddress, address); + + callbackContext.error(returnObj); + + return null; + } + + private void addDevice(JSONObject returnObj, BluetoothDevice device) { + addProperty(returnObj, keyAddress, device.getAddress()); + addProperty(returnObj, keyName, device.getName()); + } + + private void addService(JSONObject returnObj, BluetoothGattService service) { + addProperty(returnObj, keyService, formatUuid(service.getUuid())); + } + + private void addCharacteristic(JSONObject returnObj, BluetoothGattCharacteristic characteristic) { + addService(returnObj, characteristic.getService()); + addProperty(returnObj, keyCharacteristic, formatUuid(characteristic.getUuid())); + } + + private void addDescriptor(JSONObject returnObj, BluetoothGattDescriptor descriptor) { + addCharacteristic(returnObj, descriptor.getCharacteristic()); + addProperty(returnObj, keyDescriptor, formatUuid(descriptor.getUuid())); + } + + //General Helpers + private void addProperty(JSONObject obj, String key, Object value) { + //Believe exception only occurs when adding duplicate keys, so just ignore it + try { + if (value == null) { + obj.put(key, JSONObject.NULL); + } else { + obj.put(key, value); + } + } catch (JSONException e) { + } + } - JSONObject returnObj = new JSONObject(); + private void addPropertyBytes(JSONObject obj, String key, byte[] bytes) { + String string = Base64.encodeToString(bytes, Base64.NO_WRAP); - addProperty(returnObj, keyError, errorIsNotConnected); - addProperty(returnObj, keyMessage, logIsNotConnected); + addProperty(obj, key, string); + } - addDevice(returnObj, device); + private JSONObject getArgsObject(JSONArray args) { + if (args.length() == 1) { + try { + return args.getJSONObject(0); + } catch (JSONException ex) { + } + } - callbackContext.error(returnObj); + return null; + } - return true; + private byte[] getPropertyBytes(JSONObject obj, String key) { + String string = obj.optString(key, null); + + if (string == null) { + return null; } - private boolean wasConnected(String address, CallbackContext callbackContext) { - HashMap connection = connections.get(address); - if (connection != null) { - BluetoothGatt peripheral = (BluetoothGatt) connection.get(keyPeripheral); - BluetoothDevice device = peripheral.getDevice(); + byte[] bytes = Base64.decode(string, Base64.NO_WRAP); - JSONObject returnObj = new JSONObject(); + if (bytes == null || bytes.length == 0) { + return null; + } - addProperty(returnObj, keyError, errorConnect); - addProperty(returnObj, keyMessage, logPreviouslyConnected); + return bytes; + } - addDevice(returnObj, device); + private UUID[] getServiceUuids(JSONObject obj) { + if (obj == null) { + return new UUID[]{}; + } - callbackContext.error(returnObj); + JSONArray array = obj.optJSONArray(keyServices); - return true; - } - return false; + if (array == null) { + return new UUID[]{}; } - private HashMap wasNeverConnected(String address, CallbackContext callbackContext) { - HashMap connection = connections.get(address); - if (connection != null) { - return connection; - } + //Create temporary array list for building array of UUIDs + ArrayList arrayList = new ArrayList(); - JSONObject returnObj = new JSONObject(); + //Iterate through the UUID strings + for (int i = 0; i < array.length(); i++) { + String value = array.optString(i, null); - addProperty(returnObj, keyError, errorNeverConnected); - addProperty(returnObj, keyMessage, logNeverConnected); - addProperty(returnObj, keyAddress, address); + if (value == null) { + continue; + } - callbackContext.error(returnObj); + if (value.length() == 4) { + value = baseUuidStart + value + baseUuidEnd; + } - return null; + //Try converting string to UUID and add to list + try { + UUID uuid = UUID.fromString(value); + arrayList.add(uuid); + } catch (Exception ex) { + } } - private void addDevice(JSONObject returnObj, BluetoothDevice device) { - addProperty(returnObj, keyAddress, device.getAddress()); - addProperty(returnObj, keyName, device.getName()); - } + UUID[] uuids = new UUID[arrayList.size()]; + uuids = arrayList.toArray(uuids); + return uuids; + } - private void addService(JSONObject returnObj, BluetoothGattService service) { - addProperty(returnObj, keyService, formatUuid(service.getUuid())); - } - private void addCharacteristic(JSONObject returnObj, BluetoothGattCharacteristic characteristic) { - addService(returnObj, characteristic.getService()); - addProperty(returnObj, keyCharacteristic, formatUuid(characteristic.getUuid())); + private int getTransportMode(JSONObject obj) { + int transportMode = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + transportMode = BluetoothDevice.TRANSPORT_AUTO; } - private void addDescriptor(JSONObject returnObj, BluetoothGattDescriptor descriptor) { - addCharacteristic(returnObj, descriptor.getCharacteristic()); - addProperty(returnObj, keyDescriptor, formatUuid(descriptor.getUuid())); + if (obj != null && !obj.isNull("transport")) { + try { + transportMode = obj.getInt("transport"); + } catch (JSONException e) { + Log.e("BLE", "An exception occurred while transport connection parameter, fall back to: BluetoothDevice.TRANSPORT_AUTO"); + } } + return transportMode; + } - //General Helpers - private void addProperty(JSONObject obj, String key, Object value) { - //Believe exception only occurs when adding duplicate keys, so just ignore it - try { - if (value == null) { - obj.put(key, JSONObject.NULL); - } else { - obj.put(key, value); - } - } catch (JSONException e) { - } - } + private String getAddress(JSONObject obj) { + //Get the address string from arguments + String address = obj.optString(keyAddress, null); - private void addPropertyBytes(JSONObject obj, String key, byte[] bytes) { - String string = Base64.encodeToString(bytes, Base64.NO_WRAP); + if (address == null) { + return null; + } - addProperty(obj, key, string); + //Validate address format + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + return null; } - private JSONObject getArgsObject(JSONArray args) { - if (args.length() == 1) { - try { - return args.getJSONObject(0); - } catch (JSONException ex) { - } - } + return address; + } - return null; - } + private boolean getRequest(JSONObject obj) { + return obj.optBoolean(keyRequest, false); + } - private byte[] getPropertyBytes(JSONObject obj, String key) { - String string = obj.optString(key, null); + private boolean getStatusReceiver(JSONObject obj) { + return obj.optBoolean(keyStatusReceiver, true); + } - if (string == null) { - return null; - } + private int getWriteType(JSONObject obj) { + String writeType = obj.optString(keyType, null); - byte[] bytes = Base64.decode(string, Base64.NO_WRAP); + if (writeType == null || !writeType.equals(writeTypeNoResponse)) { + return BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; + } + return BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE; + } - if (bytes == null || bytes.length == 0) { - return null; - } + private int getMtu(JSONObject obj) { + int mtu = obj.optInt(keyMtu); - return bytes; + if (mtu == 0) { + return 23; } - private UUID[] getServiceUuids(JSONObject obj) { - if (obj == null) { - return new UUID[]{}; - } + return mtu; + } - JSONArray array = obj.optJSONArray(keyServices); + private JSONObject getDiscovery(BluetoothGatt bluetoothGatt) { + JSONObject deviceObject = new JSONObject(); - if (array == null) { - return new UUID[]{}; - } + BluetoothDevice device = bluetoothGatt.getDevice(); - //Create temporary array list for building array of UUIDs - ArrayList arrayList = new ArrayList(); + addProperty(deviceObject, keyStatus, statusDiscovered); - //Iterate through the UUID strings - for (int i = 0; i < array.length(); i++) { - String value = array.optString(i, null); + addDevice(deviceObject, device); - if (value == null) { - continue; - } + JSONArray servicesArray = new JSONArray(); - if (value.length() == 4) { - value = baseUuidStart + value + baseUuidEnd; - } + List services = bluetoothGatt.getServices(); - //Try converting string to UUID and add to list - try { - UUID uuid = UUID.fromString(value); - arrayList.add(uuid); - } catch (Exception ex) { - } - } + for (BluetoothGattService service : services) { + JSONObject serviceObject = new JSONObject(); - UUID[] uuids = new UUID[arrayList.size()]; - uuids = arrayList.toArray(uuids); - return uuids; - } + addProperty(serviceObject, keyUuid, formatUuid(service.getUuid())); - private String getAddress(JSONObject obj) { - //Get the address string from arguments - String address = obj.optString(keyAddress, null); + JSONArray characteristicsArray = new JSONArray(); - if (address == null) { - return null; - } + List characteristics = service.getCharacteristics(); - //Validate address format - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - return null; - } + for (BluetoothGattCharacteristic characteristic : characteristics) { + JSONObject characteristicObject = new JSONObject(); - return address; - } + addProperty(characteristicObject, keyUuid, formatUuid(characteristic.getUuid())); + addProperty(characteristicObject, keyProperties, getProperties(characteristic)); + addProperty(characteristicObject, keyPermissions, getPermissions(characteristic)); - private boolean getRequest(JSONObject obj) { - return obj.optBoolean(keyRequest, false); - } + JSONArray descriptorsArray = new JSONArray(); - private boolean getStatusReceiver(JSONObject obj) { - return obj.optBoolean(keyStatusReceiver, true); - } + List descriptors = characteristic.getDescriptors(); - private int getWriteType(JSONObject obj) { - String writeType = obj.optString(keyType, null); + for (BluetoothGattDescriptor descriptor : descriptors) { + JSONObject descriptorObject = new JSONObject(); - if (writeType == null || !writeType.equals(writeTypeNoResponse)) { - return BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; + addProperty(descriptorObject, keyUuid, formatUuid(descriptor.getUuid())); + addProperty(descriptorObject, keyPermissions, getPermissions(descriptor)); + + descriptorsArray.put(descriptorObject); } - return BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE; - } - private int getMtu(JSONObject obj) { - int mtu = obj.optInt(keyMtu); + addProperty(characteristicObject, keyDescriptors, descriptorsArray); - if (mtu == 0) { - return 23; - } + characteristicsArray.put(characteristicObject); + } + + addProperty(serviceObject, keyCharacteristics, characteristicsArray); - return mtu; + servicesArray.put(serviceObject); } - private JSONObject getDiscovery(BluetoothGatt bluetoothGatt) { - JSONObject deviceObject = new JSONObject(); + addProperty(deviceObject, keyServices, servicesArray); - BluetoothDevice device = bluetoothGatt.getDevice(); + return deviceObject; + } - addProperty(deviceObject, keyStatus, statusDiscovered); + private JSONObject getProperties(BluetoothGattCharacteristic characteristic) { + int properties = characteristic.getProperties(); - addDevice(deviceObject, device); + JSONObject propertiesObject = new JSONObject(); - JSONArray servicesArray = new JSONArray(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) == BluetoothGattCharacteristic.PROPERTY_BROADCAST) { + addProperty(propertiesObject, propertyBroadcast, true); + } - List services = bluetoothGatt.getServices(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ) { + addProperty(propertiesObject, propertyRead, true); + } - for (BluetoothGattService service : services) { - JSONObject serviceObject = new JSONObject(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) { + addProperty(propertiesObject, propertyWriteWithoutResponse, true); + } - addProperty(serviceObject, keyUuid, formatUuid(service.getUuid())); + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) { + addProperty(propertiesObject, propertyWrite, true); + } - JSONArray characteristicsArray = new JSONArray(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + addProperty(propertiesObject, propertyNotify, true); + } - List characteristics = service.getCharacteristics(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + addProperty(propertiesObject, propertyIndicate, true); + } - for (BluetoothGattCharacteristic characteristic : characteristics) { - JSONObject characteristicObject = new JSONObject(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) == BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) { + addProperty(propertiesObject, propertyAuthenticatedSignedWrites, true); + } - addProperty(characteristicObject, keyUuid, formatUuid(characteristic.getUuid())); - addProperty(characteristicObject, keyProperties, getProperties(characteristic)); - addProperty(characteristicObject, keyPermissions, getPermissions(characteristic)); + if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) == BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) { + addProperty(propertiesObject, propertyExtendedProperties, true); + } - JSONArray descriptorsArray = new JSONArray(); + if ((properties & 0x100) == 0x100) { + addProperty(propertiesObject, propertyNotifyEncryptionRequired, true); + } - List descriptors = characteristic.getDescriptors(); + if ((properties & 0x200) == 0x200) { + addProperty(propertiesObject, propertyIndicateEncryptionRequired, true); + } - for (BluetoothGattDescriptor descriptor : descriptors) { - JSONObject descriptorObject = new JSONObject(); + return propertiesObject; + } - addProperty(descriptorObject, keyUuid, formatUuid(descriptor.getUuid())); - addProperty(descriptorObject, keyPermissions, getPermissions(descriptor)); + private JSONObject getPermissions(BluetoothGattCharacteristic characteristic) { + int permissions = characteristic.getPermissions(); - descriptorsArray.put(descriptorObject); - } + JSONObject permissionsObject = new JSONObject(); - addProperty(characteristicObject, keyDescriptors, descriptorsArray); + if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ) == BluetoothGattCharacteristic.PERMISSION_READ) { + addProperty(permissionsObject, permissionRead, true); + } - characteristicsArray.put(characteristicObject); - } + if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) == BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) { + addProperty(permissionsObject, permissionReadEncrypted, true); + } - addProperty(serviceObject, keyCharacteristics, characteristicsArray); + if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) == BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) { + addProperty(permissionsObject, permissionReadEncryptedMITM, true); + } - servicesArray.put(serviceObject); - } + if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE) == BluetoothGattCharacteristic.PERMISSION_WRITE) { + addProperty(permissionsObject, permissionWrite, true); + } - addProperty(deviceObject, keyServices, servicesArray); + if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) == BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) { + addProperty(permissionsObject, permissionWriteEncrypted, true); + } - return deviceObject; + if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) == BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) { + addProperty(permissionsObject, permissionWriteEncryptedMITM, true); } - private JSONObject getProperties(BluetoothGattCharacteristic characteristic) { - int properties = characteristic.getProperties(); + if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) == BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) { + addProperty(permissionsObject, permissionWriteSigned, true); + } - JSONObject propertiesObject = new JSONObject(); + if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) == BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) { + addProperty(permissionsObject, permissionWriteSignedMITM, true); + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) == BluetoothGattCharacteristic.PROPERTY_BROADCAST) { - addProperty(propertiesObject, propertyBroadcast, true); - } + return permissionsObject; + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ) { - addProperty(propertiesObject, propertyRead, true); - } + private JSONObject getPermissions(BluetoothGattDescriptor descriptor) { + int permissions = descriptor.getPermissions(); - if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) { - addProperty(propertiesObject, propertyWriteWithoutResponse, true); - } + JSONObject permissionsObject = new JSONObject(); - if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) { - addProperty(propertiesObject, propertyWrite, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_READ) == BluetoothGattDescriptor.PERMISSION_READ) { + addProperty(permissionsObject, permissionRead, true); + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { - addProperty(propertiesObject, propertyNotify, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) == BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) { + addProperty(permissionsObject, permissionReadEncrypted, true); + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { - addProperty(propertiesObject, propertyIndicate, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) == BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) { + addProperty(permissionsObject, permissionReadEncryptedMITM, true); + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) == BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) { - addProperty(propertiesObject, propertyAuthenticatedSignedWrites, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE) == BluetoothGattDescriptor.PERMISSION_WRITE) { + addProperty(permissionsObject, permissionWrite, true); + } - if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) == BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) { - addProperty(propertiesObject, propertyExtendedProperties, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) == BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) { + addProperty(permissionsObject, permissionWriteEncrypted, true); + } - if ((properties & 0x100) == 0x100) { - addProperty(propertiesObject, propertyNotifyEncryptionRequired, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) == BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) { + addProperty(permissionsObject, permissionWriteEncryptedMITM, true); + } - if ((properties & 0x200) == 0x200) { - addProperty(propertiesObject, propertyIndicateEncryptionRequired, true); - } + if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) == BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) { + addProperty(permissionsObject, permissionWriteSigned, true); + } - return propertiesObject; + if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) == BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) { + addProperty(permissionsObject, permissionWriteSignedMITM, true); } - private JSONObject getPermissions(BluetoothGattCharacteristic characteristic) { - int permissions = characteristic.getPermissions(); + return permissionsObject; + } - JSONObject permissionsObject = new JSONObject(); + private String getPin(JSONObject obj) { + //Get the pin string from arguments + String pin = obj.optString(keyPin, null); + return pin; + } - if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ) == BluetoothGattCharacteristic.PERMISSION_READ) { - addProperty(permissionsObject, permissionRead, true); + //Bluetooth callback for connecting, discovering, reading and writing + private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); + + HashMap connection = connections.get(address); + if (connection == null) { + return; + } + + //Check for queued operations in progress on this device + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + LinkedList queue = (LinkedList) connection.get(keyQueue); + Operation operation = queue.peek(); + if (operation != null) { + queueRemove(connection); } + } - if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) == BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) { - addProperty(permissionsObject, permissionReadEncrypted, true); - } + CallbackContext callbackContext = (CallbackContext) connection.get(operationConnect); - if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) == BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) { - addProperty(permissionsObject, permissionReadEncryptedMITM, true); - } + JSONObject returnObj = new JSONObject(); - if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE) == BluetoothGattCharacteristic.PERMISSION_WRITE) { - addProperty(permissionsObject, permissionWrite, true); - } + addDevice(returnObj, device); - if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) == BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) { - addProperty(permissionsObject, permissionWriteEncrypted, true); - } + int oldState = Integer.valueOf(connection.get(keyState).toString()); + if (status != BluetoothGatt.GATT_SUCCESS && oldState == BluetoothProfile.STATE_CONNECTING) { + //Clear out all the callbacks + connection = new HashMap(); + connection.put(keyPeripheral, gatt); + connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + connection.put(keyDiscoveredState, STATE_UNDISCOVERED); - if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) == BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) { - addProperty(permissionsObject, permissionWriteEncryptedMITM, true); - } + connections.put(device.getAddress(), connection); - if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) == BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) { - addProperty(permissionsObject, permissionWriteSigned, true); + if (callbackContext == null) { + return; } - if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) == BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) { - addProperty(permissionsObject, permissionWriteSignedMITM, true); - } + addProperty(returnObj, keyError, errorConnect); + addProperty(returnObj, keyMessage, logConnectFail); - return permissionsObject; - } + callbackContext.error(returnObj); - private JSONObject getPermissions(BluetoothGattDescriptor descriptor) { - int permissions = descriptor.getPermissions(); + return; + } - JSONObject permissionsObject = new JSONObject(); + connection.put(keyState, newState); - if ((permissions & BluetoothGattDescriptor.PERMISSION_READ) == BluetoothGattDescriptor.PERMISSION_READ) { - addProperty(permissionsObject, permissionRead, true); + //Device was connected + if (newState == BluetoothProfile.STATE_CONNECTED) { + if (callbackContext == null) { + return; } - if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) == BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) { - addProperty(permissionsObject, permissionReadEncrypted, true); - } + addProperty(returnObj, keyStatus, statusConnected); - if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) == BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) { - addProperty(permissionsObject, permissionReadEncryptedMITM, true); - } + //Keep connection call back for disconnect + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + callbackContext.sendPluginResult(pluginResult); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + //Device was disconnected + CallbackContext[] callbacks = GetCallbacks(connection); + addProperty(returnObj, keyError, errorIsDisconnected); + addProperty(returnObj, keyMessage, logIsDisconnected); - if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE) == BluetoothGattDescriptor.PERMISSION_WRITE) { - addProperty(permissionsObject, permissionWrite, true); + for (CallbackContext callback : callbacks) { + callback.error(returnObj); } - if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) == BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) { - addProperty(permissionsObject, permissionWriteEncrypted, true); - } + returnObj.remove(keyError); + returnObj.remove(keyMessage); - if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) == BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) { - addProperty(permissionsObject, permissionWriteEncryptedMITM, true); - } + //Save the old discovered state + Object discoveredState = connection.get(keyDiscoveredState); - if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) == BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) { - addProperty(permissionsObject, permissionWriteSigned, true); - } + //Clear out all the callbacks + connection = new HashMap(); + connection.put(keyPeripheral, gatt); + connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + + //Save state in new connection + connection.put(keyDiscoveredState, discoveredState); - if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) == BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) { - addProperty(permissionsObject, permissionWriteSignedMITM, true); + connections.put(device.getAddress(), connection); + + if (callbackContext == null) { + return; } - return permissionsObject; + addProperty(returnObj, keyStatus, statusDisconnected); + + callbackContext.success(returnObj); + } } - //Bluetooth callback for connecting, discovering, reading and writing - private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - return; - } + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - CallbackContext callbackContext = (CallbackContext) connection.get(operationConnect); + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - JSONObject returnObj = new JSONObject(); + int discoveredState = (status == BluetoothGatt.GATT_SUCCESS) ? STATE_DISCOVERED : STATE_UNDISCOVERED; + connection.put(keyDiscoveredState, discoveredState); - addDevice(returnObj, device); + CallbackContext callbackContext = (CallbackContext) connection.get(operationDiscover); + connection.remove(operationDiscover); - int oldState = Integer.valueOf(connection.get(keyState).toString()); - if (status != BluetoothGatt.GATT_SUCCESS && oldState == BluetoothProfile.STATE_CONNECTING) { - //Clear out all the callbacks - connection = new HashMap(); - connection.put(keyPeripheral, gatt); - connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + //Shouldn't happen, but check for null callback + if (callbackContext == null) { + return; + } - connections.put(device.getAddress(), connection); + JSONObject returnObj = new JSONObject(); - if (callbackContext == null) { - return; - } + addDevice(returnObj, device); - addProperty(returnObj, keyError, errorConnect); - addProperty(returnObj, keyMessage, logConnectFail); + //If successfully discovered, return list of services, characteristics and descriptors + if (status == BluetoothGatt.GATT_SUCCESS) { + returnObj = getDiscovery(gatt); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorDiscover); + addProperty(returnObj, keyMessage, logDiscoveryFail); + callbackContext.error(returnObj); + } + } - callbackContext.error(returnObj); + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - return; - } + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - connection.put(keyState, newState); + queueRemove(connection); - //Device was connected - if (newState == BluetoothProfile.STATE_CONNECTED) { - if (callbackContext == null) { - return; - } + UUID characteristicUuid = characteristic.getUuid(); - addProperty(returnObj, keyStatus, statusConnected); + CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationRead); + RemoveCallback(characteristicUuid, connection, operationRead); - //Keep connection call back for disconnect - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - //Device was disconnected - CallbackContext[] callbacks = GetCallbacks(connection); - addProperty(returnObj, keyError, errorIsDisconnected); - addProperty(returnObj, keyMessage, logIsDisconnected); + //If no callback, just return + if (callbackContext == null) { + return; + } - for (CallbackContext callback : callbacks) { - callback.error(returnObj); - } + JSONObject returnObj = new JSONObject(); - returnObj.remove(keyError); - returnObj.remove(keyMessage); + addCharacteristic(returnObj, characteristic); - //Save the old discovered state - Object discoveredState = connection.get(keyDiscoveredState); + addDevice(returnObj, device); - //Clear out all the callbacks - connection = new HashMap(); - connection.put(keyPeripheral, gatt); - connection.put(keyState, BluetoothProfile.STATE_DISCONNECTED); + //If successfully read, return value + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusRead); + addPropertyBytes(returnObj, keyValue, characteristic.getValue()); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorRead); + addProperty(returnObj, keyMessage, logReadFailReturn); + callbackContext.error(returnObj); + } + } - //Save state in new connection - connection.put(keyDiscoveredState, discoveredState); + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - connections.put(device.getAddress(), connection); + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - if (callbackContext == null) { - return; - } + UUID characteristicUuid = characteristic.getUuid(); - addProperty(returnObj, keyStatus, statusDisconnected); + SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe); - callbackContext.success(returnObj); - } - } + //If no callback, just return + if (callbackContext == null) { + return; + } - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - return; - } + JSONObject returnObj = new JSONObject(); - int discoveredState = (status == BluetoothGatt.GATT_SUCCESS) ? STATE_DISCOVERED : STATE_UNDISCOVERED; - connection.put(keyDiscoveredState, discoveredState); + addDevice(returnObj, device); - CallbackContext callbackContext = (CallbackContext) connection.get(operationDiscover); - connection.remove(operationDiscover); + addCharacteristic(returnObj, characteristic); - //Shouldn't happen, but check for null callback - if (callbackContext == null) { - return; - } + addProperty(returnObj, keyStatus, statusSubscribedResult); + addPropertyBytes(returnObj, keyValue, characteristic.getValue()); - JSONObject returnObj = new JSONObject(); + int serviceIndex = 0; + BluetoothGattService serviceFromCharacteristic = characteristic.getService(); - addDevice(returnObj, device); + for (BluetoothGattService service : gatt.getServices()) { + if (service.equals(serviceFromCharacteristic)) { + break; + } else if(service.getUuid().equals(serviceFromCharacteristic.getUuid())) { + serviceIndex++; + } + } - //If successfully discovered, return list of services, characteristics and descriptors - if (status == BluetoothGatt.GATT_SUCCESS) { - returnObj = getDiscovery(gatt); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorDiscover); - addProperty(returnObj, keyMessage, logDiscoveryFail); - callbackContext.error(returnObj); - } + int characteristicIndex = 0; + for (BluetoothGattCharacteristic characteristicFromService : serviceFromCharacteristic.getCharacteristics()) { + if (characteristicFromService.equals(characteristic)) { + break; + } else if (characteristicFromService.getUuid().equals(characteristic.getUuid())) { + characteristicIndex++; } + } - @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - queueRemove(); - return; - } + addProperty(returnObj, "serviceIndex", serviceIndex); + addProperty(returnObj, "characteristicIndex", characteristicIndex); - UUID characteristicUuid = characteristic.getUuid(); + //Return the characteristic value + callbackContext.sendSequentialResult(returnObj); + } - CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationRead); - RemoveCallback(characteristicUuid, connection, operationRead); + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - //If no callback, just return - if (callbackContext == null) { - queueRemove(); - return; - } + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - JSONObject returnObj = new JSONObject(); + queueRemove(connection); - addCharacteristic(returnObj, characteristic); + UUID characteristicUuid = characteristic.getUuid(); - addDevice(returnObj, device); + CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationWrite); - //If successfully read, return value - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusRead); - addPropertyBytes(returnObj, keyValue, characteristic.getValue()); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorRead); - addProperty(returnObj, keyMessage, logReadFailReturn); - callbackContext.error(returnObj); - } + //Check if any other write commands are queued up + if (queueQuick.size() > 0) { + if (status == BluetoothGatt.GATT_SUCCESS) { + writeQ(connection, characteristic, gatt); + } else { + //If there was an error, clear the queue + queueQuick.clear(); - queueRemove(); - } + if (callbackContext == null) { + return; + } - @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - return; - } + JSONObject returnObj = new JSONObject(); - UUID characteristicUuid = characteristic.getUuid(); + addDevice(returnObj, device); + addCharacteristic(returnObj, characteristic); - CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe); + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteFailReturn); + addProperty(returnObj, keyStatus, status); + callbackContext.error(returnObj); + } - //If no callback, just return - if (callbackContext == null) { - return; - } + return; + } - JSONObject returnObj = new JSONObject(); + RemoveCallback(characteristicUuid, connection, operationWrite); - addDevice(returnObj, device); + //If no callback, just return + if (callbackContext == null) { + return; + } - addCharacteristic(returnObj, characteristic); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyStatus, statusSubscribedResult); - addPropertyBytes(returnObj, keyValue, characteristic.getValue()); + addDevice(returnObj, device); + addCharacteristic(returnObj, characteristic); - //Return the characteristic value - PluginResult result = new PluginResult(PluginResult.Status.OK, returnObj); - result.setKeepCallback(true); - callbackContext.sendPluginResult(result); - } + //If write was successful, return the written value + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusWritten); + addPropertyBytes(returnObj, keyValue, characteristic.getValue()); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorWrite); + addProperty(returnObj, keyMessage, logWriteFailReturn); + addProperty(returnObj, keyStatus, status); + callbackContext.error(returnObj); + } + } - @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - queueRemove(); - return; - } + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - UUID characteristicUuid = characteristic.getUuid(); + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationWrite); + queueRemove(connection); - //Check if any other write commands are queued up - if (queueQuick.size() > 0) { - if (status == BluetoothGatt.GATT_SUCCESS) { - writeQ(connection, characteristic, gatt); - } else { - //If there was an error, clear the queue - queueQuick.clear(); + BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); + UUID characteristicUuid = characteristic.getUuid(); + UUID descriptorUuid = descriptor.getUuid(); - if (callbackContext == null) { - return; - } + CallbackContext callbackContext = GetDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); + RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); - JSONObject returnObj = new JSONObject(); + //If callback is null, just return + if (callbackContext == null) { + return; + } - addDevice(returnObj, device); - addCharacteristic(returnObj, characteristic); + JSONObject returnObj = new JSONObject(); - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteFailReturn); - callbackContext.error(returnObj); - } + addDevice(returnObj, device); - return; - } + addDescriptor(returnObj, descriptor); - RemoveCallback(characteristicUuid, connection, operationWrite); + //If descriptor was successful, return the written value + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusReadDescriptor); + addPropertyBytes(returnObj, keyValue, descriptor.getValue()); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorReadDescriptor); + addProperty(returnObj, keyMessage, logReadDescriptorFailReturn); + callbackContext.error(returnObj); + } + } - //If no callback, just return - if (callbackContext == null) { - queueRemove(); - return; - } + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - JSONObject returnObj = new JSONObject(); - - addDevice(returnObj, device); - addCharacteristic(returnObj, characteristic); - - //If write was successful, return the written value - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusWritten); - addPropertyBytes(returnObj, keyValue, characteristic.getValue()); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorWrite); - addProperty(returnObj, keyMessage, logWriteFailReturn); - callbackContext.error(returnObj); - } + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - queueRemove(); - } + queueRemove(connection); - @Override - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - queueRemove(); - return; - } + BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); + UUID characteristicUuid = characteristic.getUuid(); + UUID descriptorUuid = descriptor.getUuid(); - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - UUID characteristicUuid = characteristic.getUuid(); - UUID descriptorUuid = descriptor.getUuid(); + JSONObject returnObj = new JSONObject(); - CallbackContext callbackContext = GetDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); - RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationRead); + addDevice(returnObj, device); - //If callback is null, just return - if (callbackContext == null) { - queueRemove(); - return; - } + addDescriptor(returnObj, descriptor); - JSONObject returnObj = new JSONObject(); + //See if notification/indication is enabled or disabled and use subscribe/unsubscribe callback instead + if (descriptorUuid.equals(clientConfigurationDescriptorUuid)) { + if (descriptor.getValue() == BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) { + //Unsubscribe to the characteristic + boolean result = gatt.setCharacteristicNotification(characteristic, false); - addDevice(returnObj, device); + CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationUnsubscribe); - addDescriptor(returnObj, descriptor); + //If no callback, just return + if (callbackContext == null) { + return; + } - //If descriptor was successful, return the written value - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusReadDescriptor); - addPropertyBytes(returnObj, keyValue, descriptor.getValue()); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorReadDescriptor); - addProperty(returnObj, keyMessage, logReadDescriptorFailReturn); - callbackContext.error(returnObj); - } + if (status != BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyError, errorSubscription); + addProperty(returnObj, keyMessage, logUnsubscribeFail); + callbackContext.error(returnObj); + return; + } - queueRemove(); - } + if (!result) { + addProperty(returnObj, keyError, errorSubscription); + addProperty(returnObj, keyMessage, logUnsubscribeFail); + callbackContext.error(returnObj); + return; + } - @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - queueRemove(); - return; - } + //Get the unsubscribed operation callback and clear + addProperty(returnObj, keyStatus, statusUnsubscribed); - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - UUID characteristicUuid = characteristic.getUuid(); - UUID descriptorUuid = descriptor.getUuid(); - - JSONObject returnObj = new JSONObject(); - - addDevice(returnObj, device); - - addDescriptor(returnObj, descriptor); - - //See if notification/indication is enabled or disabled and use subscribe/unsubscribe callback instead - if (descriptorUuid.equals(clientConfigurationDescriptorUuid)) { - if (descriptor.getValue() == BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) { - //Unsubscribe to the characteristic - boolean result = gatt.setCharacteristicNotification(characteristic, false); - - CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationUnsubscribe); - - //If no callback, just return - if (callbackContext == null) { - queueRemove(); - return; - } - - if (status != BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyError, errorSubscription); - addProperty(returnObj, keyMessage, logUnsubscribeFail); - callbackContext.error(returnObj); - queueRemove(); - return; - } - - if (!result) { - addProperty(returnObj, keyError, errorSubscription); - addProperty(returnObj, keyMessage, logUnsubscribeFail); - callbackContext.error(returnObj); - queueRemove(); - return; - } - - //Get the unsubscribed operation callback and clear - addProperty(returnObj, keyStatus, statusUnsubscribed); - - callbackContext.success(returnObj); - } else { - //Subscribe to the characteristic - boolean result = gatt.setCharacteristicNotification(characteristic, true); - - CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe); - - //If no callback, just return - if (callbackContext == null) { - queueRemove(); - return; - } - - if (!result) { - addProperty(returnObj, keyError, errorSubscription); - addProperty(returnObj, keyMessage, logSubscribeFail); - callbackContext.error(returnObj); - queueRemove(); - return; - } - - addProperty(returnObj, keyStatus, statusSubscribed); - - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - callbackContext.sendPluginResult(pluginResult); - } + callbackContext.success(returnObj); + } else { + SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe); - queueRemove(); - return; - } + //If no callback, just return + if (callbackContext == null) { + return; + } - CallbackContext callbackContext = GetDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); - RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); + addProperty(returnObj, keyStatus, statusSubscribed); - //If callback is null, just return - if (callbackContext == null) { - queueRemove(); - return; - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + callbackContext.getContext().sendPluginResult(pluginResult); + } - //If descriptor was written, return written value - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusWrittenDescriptor); - addPropertyBytes(returnObj, keyValue, descriptor.getValue()); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorWriteDescriptor); - addProperty(returnObj, keyMessage, logWriteDescriptorFailReturn); - callbackContext.error(returnObj); - } + return; + } - queueRemove(); - } + CallbackContext callbackContext = GetDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); + RemoveDescriptorCallback(descriptorUuid, characteristicUuid, connection, operationWrite); - @Override - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { - //Get the connected device - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); - - HashMap connection = connections.get(address); - if (connection == null) { - return; - } + //If callback is null, just return + if (callbackContext == null) { + return; + } - CallbackContext callbackContext = (CallbackContext) connection.get(operationRssi); - connection.remove(operationRssi); + //If descriptor was written, return written value + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusWrittenDescriptor); + addPropertyBytes(returnObj, keyValue, descriptor.getValue()); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorWriteDescriptor); + addProperty(returnObj, keyMessage, logWriteDescriptorFailReturn); + callbackContext.error(returnObj); + } + } - //If no callback, just return - if (callbackContext == null) { - return; - } + @Override + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + //Get the connected device + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); + + HashMap connection = connections.get(address); + if (connection == null) { + return; + } + + CallbackContext callbackContext = (CallbackContext) connection.get(operationRssi); + connection.remove(operationRssi); + + //If no callback, just return + if (callbackContext == null) { + return; + } + + JSONObject returnObj = new JSONObject(); + addDevice(returnObj, device); + + //If successfully read RSSI, return value + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusRssi); + addProperty(returnObj, keyRssi, rssi); + callbackContext.success(returnObj); + } else { + //Else it failed + addProperty(returnObj, keyError, errorRssi); + addProperty(returnObj, keyMessage, logRssiFailReturn); + callbackContext.error(returnObj); + } + } - JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - - //If successfully read RSSI, return value - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusRssi); - addProperty(returnObj, keyRssi, rssi); - callbackContext.success(returnObj); - } else { - //Else it failed - addProperty(returnObj, keyError, errorRssi); - addProperty(returnObj, keyMessage, logRssiFailReturn); - callbackContext.error(returnObj); - } - } + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + BluetoothDevice device = gatt.getDevice(); + String address = device.getAddress(); - @Override - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - BluetoothDevice device = gatt.getDevice(); - String address = device.getAddress(); + HashMap connection = connections.get(address); + if (connection == null) { + return; + } - HashMap connection = connections.get(address); - if (connection == null) { - return; - } + CallbackContext callbackContext = (CallbackContext) connection.get(operationMtu); + connection.remove(operationMtu); - CallbackContext callbackContext = (CallbackContext) connection.get(operationMtu); - connection.remove(operationMtu); + if (callbackContext == null) { + return; + } - if (callbackContext == null) { - return; - } + JSONObject returnObj = new JSONObject(); + addDevice(returnObj, device); - JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, keyStatus, statusMtu); - addProperty(returnObj, keyMtu, mtu); - callbackContext.success(returnObj); - } else { - addProperty(returnObj, keyError, errorMtu); - addProperty(returnObj, keyMessage, logMtuFailReturn); - callbackContext.error(returnObj); - } - } - }; + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, keyStatus, statusMtu); + addProperty(returnObj, keyMtu, mtu); + callbackContext.success(returnObj); + } else { + addProperty(returnObj, keyError, errorMtu); + addProperty(returnObj, keyMessage, logMtuFailReturn); + callbackContext.error(returnObj); + } + } + }; - //Bluetooth callback for connecting, discovering, reading and writing - private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() { - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { - if (initPeripheralCallback == null) { - return; - } + //Bluetooth callback for connecting, discovering, reading and writing + private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() { + public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addCharacteristic(returnObj, characteristic); + addDevice(returnObj, device); + addCharacteristic(returnObj, characteristic); - addProperty(returnObj, "status", "readRequested"); - addProperty(returnObj, "requestId", requestId); - addProperty(returnObj, "offset", offset); + addProperty(returnObj, "status", "readRequested"); + addProperty(returnObj, "requestId", requestId); + addProperty(returnObj, "offset", offset); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { - if (initPeripheralCallback == null) { - return; - } + public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addCharacteristic(returnObj, characteristic); + addDevice(returnObj, device); + addCharacteristic(returnObj, characteristic); - addProperty(returnObj, "status", "writeRequested"); - addProperty(returnObj, "requestId", requestId); - addProperty(returnObj, "offset", offset); - addPropertyBytes(returnObj, "value", value); + addProperty(returnObj, "status", "writeRequested"); + addProperty(returnObj, "requestId", requestId); + addProperty(returnObj, "offset", offset); + addPropertyBytes(returnObj, "value", value); - addProperty(returnObj, "preparedWrite", preparedWrite); - addProperty(returnObj, "responseNeeded", responseNeeded); + addProperty(returnObj, "preparedWrite", preparedWrite); + addProperty(returnObj, "responseNeeded", responseNeeded); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { - if (initPeripheralCallback == null) { - return; - } + public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); + addDevice(returnObj, device); - if (newState == BluetoothGatt.STATE_CONNECTED) { - addProperty(returnObj, "status", "connected"); - } else { - addProperty(returnObj, "status", "disconnected"); - } + if (newState == BluetoothGatt.STATE_CONNECTED) { + addProperty(returnObj, "status", "connected"); + } else { + addProperty(returnObj, "status", "disconnected"); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { - if (initPeripheralCallback == null) { - return; - } + public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addDescriptor(returnObj, descriptor); + addDevice(returnObj, device); + addDescriptor(returnObj, descriptor); - addProperty(returnObj, "status", "readRequested"); - addProperty(returnObj, "requestId", requestId); - addProperty(returnObj, "offset", offset); + addProperty(returnObj, "status", "readRequested"); + addProperty(returnObj, "requestId", requestId); + addProperty(returnObj, "offset", offset); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { - if (initPeripheralCallback == null) { - return; - } + public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { + if (initPeripheralCallback == null) { + return; + } - if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { - JSONObject returnObj = new JSONObject(); + if (descriptor.getUuid().equals(clientConfigurationDescriptorUuid)) { + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addCharacteristic(returnObj, descriptor.getCharacteristic()); + addDevice(returnObj, device); + addCharacteristic(returnObj, descriptor.getCharacteristic()); - if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) { - addProperty(returnObj, "status", "unsubscribed"); - } else { - addProperty(returnObj, "status", "subscribed"); - } + if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) { + addProperty(returnObj, "status", "unsubscribed"); + } else { + addProperty(returnObj, "status", "subscribed"); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); - gattServer.sendResponse(device, requestId, 0, offset, value); + gattServer.sendResponse(device, requestId, 0, offset, value); - return; - } + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addDescriptor(returnObj, descriptor); + addDevice(returnObj, device); + addDescriptor(returnObj, descriptor); - addProperty(returnObj, "status", "writeRequested"); - addProperty(returnObj, "requestId", requestId); - addProperty(returnObj, "offset", offset); - addPropertyBytes(returnObj, "value", value); + addProperty(returnObj, "status", "writeRequested"); + addProperty(returnObj, "requestId", requestId); + addProperty(returnObj, "offset", offset); + addPropertyBytes(returnObj, "value", value); - addProperty(returnObj, "preparedWrite", preparedWrite); - addProperty(returnObj, "responseNeeded", responseNeeded); + addProperty(returnObj, "preparedWrite", preparedWrite); + addProperty(returnObj, "responseNeeded", responseNeeded); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - //TODO implement this later - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { - //Log.d("BLE", "execute write"); - } + //TODO implement this later + public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { + //Log.d("BLE", "execute write"); + } - public void onMtuChanged(BluetoothDevice device, int mtu) { - if (initPeripheralCallback == null) { - return; - } + public void onMtuChanged(BluetoothDevice device, int mtu) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); - addProperty(returnObj, "status", "mtuChanged"); - addProperty(returnObj, "mtu", mtu); + addDevice(returnObj, device); + addProperty(returnObj, "status", "mtuChanged"); + addProperty(returnObj, "mtu", mtu); - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onNotificationSent(BluetoothDevice device, int status) { - if (initPeripheralCallback == null) { - return; - } + public void onNotificationSent(BluetoothDevice device, int status) { + if (initPeripheralCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addDevice(returnObj, device); + addDevice(returnObj, device); - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, "status", "notificationSent"); - } else { - addProperty(returnObj, "error", "notificationSent"); - addProperty(returnObj, "message", "Unable to send notification"); - } + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, "status", "notificationSent"); + } else { + addProperty(returnObj, "error", "notificationSent"); + addProperty(returnObj, "message", "Unable to send notification"); + } - PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); - pluginResult.setKeepCallback(true); - initPeripheralCallback.sendPluginResult(pluginResult); - } + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj); + pluginResult.setKeepCallback(true); + initPeripheralCallback.sendPluginResult(pluginResult); + } - public void onServiceAdded(int status, BluetoothGattService service) { - if (addServiceCallback == null) { - return; - } + public void onServiceAdded(int status, BluetoothGattService service) { + if (addServiceCallback == null) { + return; + } - JSONObject returnObj = new JSONObject(); + JSONObject returnObj = new JSONObject(); - addService(returnObj, service); + addService(returnObj, service); - if (status == BluetoothGatt.GATT_SUCCESS) { - addProperty(returnObj, "status", "serviceAdded"); - addServiceCallback.success(returnObj); - } else { - addProperty(returnObj, "error", "service"); - addProperty(returnObj, "message", "Unable to add service"); - addServiceCallback.error(returnObj); - } - } - }; + if (status == BluetoothGatt.GATT_SUCCESS) { + addProperty(returnObj, "status", "serviceAdded"); + addServiceCallback.success(returnObj); + } else { + addProperty(returnObj, "error", "service"); + addProperty(returnObj, "message", "Unable to add service"); + addServiceCallback.error(returnObj); + } + } + }; + + private void initGattServer() { + //Re-opening Gatt server seems to cause some issues + if (gattServer == null) { + Activity activity = cordova.getActivity(); + BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); + gattServer = bluetoothManager.openGattServer(activity.getApplicationContext(), bluetoothGattServerCallback); + } + } } diff --git a/android/src/main/java/com/codename1/bluetoothle/Operation.java b/android/src/main/java/com/codename1/bluetoothle/Operation.java index 3b6b4cd..84c1506 100644 --- a/android/src/main/java/com/codename1/bluetoothle/Operation.java +++ b/android/src/main/java/com/codename1/bluetoothle/Operation.java @@ -1,7 +1,8 @@ package com.codename1.bluetoothle; +import com.codename1.cordova.CallbackContext; import ca.weblite.codename1.json.JSONArray; -import com.codename1.cordova.*; +import android.bluetooth.BluetoothDevice; public class Operation { public String type; diff --git a/android/src/main/java/com/codename1/bluetoothle/SequentialCallbackContext.java b/android/src/main/java/com/codename1/bluetoothle/SequentialCallbackContext.java new file mode 100644 index 0000000..0657ca5 --- /dev/null +++ b/android/src/main/java/com/codename1/bluetoothle/SequentialCallbackContext.java @@ -0,0 +1,48 @@ +// Inspiration taken from cordova-plugin-ble-central + +package com.codename1.bluetoothle; + +import com.codename1.cordova.CallbackContext; +import com.codename1.cordova.PluginResult; +import java.util.ArrayList; +import java.util.List; +import ca.weblite.codename1.json.JSONObject; + +public class SequentialCallbackContext { + private int sequence; + private CallbackContext context; + + public SequentialCallbackContext(CallbackContext context) { + this.context = context; + this.sequence = 0; + } + + private int getNextSequenceNumber() { + synchronized(this) { + return this.sequence++; + } + } + + public CallbackContext getContext() { + return this.context; + } + + public PluginResult createSequentialResult(JSONObject returnObj) { + List resultList = new ArrayList(2); + + PluginResult dataResult = new PluginResult(PluginResult.Status.OK, returnObj); + PluginResult sequenceResult = new PluginResult(PluginResult.Status.OK, this.getNextSequenceNumber()); + + resultList.add(dataResult); + resultList.add(sequenceResult); + + return new PluginResult(PluginResult.Status.OK, resultList); + } + + public void sendSequentialResult(JSONObject returnObj) { + PluginResult result = this.createSequentialResult(returnObj); + result.setKeepCallback(true); + + this.context.sendPluginResult(result); + } +} diff --git a/ios/src/main/objectivec/BluetoothLePlugin.h b/ios/src/main/objectivec/BluetoothLePlugin.h index dceebf9..bf860f9 100644 --- a/ios/src/main/objectivec/BluetoothLePlugin.h +++ b/ios/src/main/objectivec/BluetoothLePlugin.h @@ -1,4 +1,4 @@ -#import "CDV.h" +#import #import @interface BluetoothLePlugin : CDVPlugin { @@ -15,6 +15,14 @@ int requestId; NSMutableDictionary* requestsHash; NSMutableDictionary* servicesHash; + + BOOL writeQIsRunning; + int writeQtype; + NSInteger writeQLocation; + NSInteger writeQLength; + NSInteger writeQChunkSize; + NSData *writeQData; + CBCharacteristic *currentWriteCharacteristic; } - (void)initialize:(CDVInvokedUrlCommand *)command; @@ -23,6 +31,8 @@ - (void)startScan:(CDVInvokedUrlCommand *)command; - (void)stopScan:(CDVInvokedUrlCommand *)command; - (void)retrieveConnected:(CDVInvokedUrlCommand *)command; +- (void)bond:(CDVInvokedUrlCommand *)command; +- (void)unbond:(CDVInvokedUrlCommand *)command; - (void)connect:(CDVInvokedUrlCommand *)command; - (void)reconnect:(CDVInvokedUrlCommand *)command; - (void)disconnect:(CDVInvokedUrlCommand *)command; @@ -44,6 +54,7 @@ - (void)isInitialized:(CDVInvokedUrlCommand *)command; - (void)isEnabled:(CDVInvokedUrlCommand *)command; - (void)isScanning:(CDVInvokedUrlCommand *)command; +- (void)isBonded:(CDVInvokedUrlCommand *)command; - (void)wasConnected:(CDVInvokedUrlCommand *)command; - (void)isConnected:(CDVInvokedUrlCommand *)command; - (void)isDiscovered:(CDVInvokedUrlCommand *)command; @@ -51,6 +62,7 @@ - (void)requestPermission:(CDVInvokedUrlCommand *)command; - (void)isLocationEnabled:(CDVInvokedUrlCommand *)command; - (void)requestLocation:(CDVInvokedUrlCommand *)command; +- (void)retrievePeripheralsByAddress:(CDVInvokedUrlCommand *)command; - (void)initializePeripheral:(CDVInvokedUrlCommand *)command; - (void)addService:(CDVInvokedUrlCommand *)command; @@ -62,4 +74,4 @@ - (void)respond:(CDVInvokedUrlCommand *)command; - (void)notify:(CDVInvokedUrlCommand *)command; -@end \ No newline at end of file +@end diff --git a/ios/src/main/objectivec/BluetoothLePlugin.m b/ios/src/main/objectivec/BluetoothLePlugin.m index b7dde7b..5e33f40 100644 --- a/ios/src/main/objectivec/BluetoothLePlugin.m +++ b/ios/src/main/objectivec/BluetoothLePlugin.m @@ -8,14 +8,17 @@ NSString *const keyMessage = @"message"; NSString *const keyName = @"name"; NSString *const keyAddress = @"address"; +NSString *const keyAddresses = @"addresses"; NSString *const keyProperties = @"properties"; NSString *const keyRssi = @"rssi"; NSString *const keyAdvertisement = @"advertisement"; NSString *const keyUuid = @"uuid"; NSString *const keyService = @"service"; +NSString *const keyServiceIndex = @"serviceIndex"; NSString *const keyServices = @"services"; NSString *const keyCharacteristic = @"characteristic"; NSString *const keyCharacteristics = @"characteristics"; +NSString *const keyCharacteristicIndex = @"characteristicIndex"; NSString *const keyDescriptor = @"descriptor"; NSString *const keyDescriptors = @"descriptors"; NSString *const keyValue = @"value"; @@ -28,6 +31,7 @@ NSString *const keyIsDiscoveredQueue = @"isDiscoveredQueue"; NSString *const keyPeripheral = @"peripheral"; NSString *const keyAllowDuplicates = @"allowDuplicates"; +NSString *const keyChunkSize = @"chunkSize"; //Write Type NSString *const writeTypeNoResponse = @"noResponse"; @@ -73,6 +77,9 @@ NSString *const errorArguments = @"arguments"; NSString *const errorStartScan = @"startScan"; NSString *const errorStopScan = @"stopScan"; +NSString *const errorBond = @"bond"; +NSString *const errorUnbond = @"unbond"; +NSString *const errorIsBonded = @"isBonded"; NSString *const errorConnect = @"connect"; NSString *const errorReconnect = @"reconnect"; NSString *const errorDiscover = @"discover"; @@ -300,12 +307,18 @@ - (void)startAdvertising:(CDVInvokedUrlCommand *)command { } NSDictionary* obj = (NSDictionary *)[command.arguments objectAtIndex:0]; - NSMutableArray* services = [self getUuids:obj forType:@"services"]; + NSMutableDictionary* advertData = [NSMutableDictionary dictionary]; + + [advertData setValue:[self getUuids:obj forType:@"services"] forKey:CBAdvertisementDataServiceUUIDsKey]; NSString* name = [obj valueForKey:@"name"]; + if (name) { + [advertData setValue:name forKey:CBAdvertisementDataLocalNameKey]; + } + advertisingCallback = command.callbackId; - [peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : services, CBAdvertisementDataLocalNameKey: name}]; + [peripheralManager startAdvertising:advertData]; } - (void)stopAdvertising:(CDVInvokedUrlCommand *)command { @@ -466,32 +479,32 @@ - (void)notify:(CDVInvokedUrlCommand *)command { - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral { NSString* error = nil; switch ([peripheral state]) { - case CBPeripheralManagerStatePoweredOff: { + case CBManagerStatePoweredOff: { error = logPoweredOff; break; } - case CBPeripheralManagerStateUnauthorized: { + case CBManagerStateUnauthorized: { error = logUnauthorized; break; } - case CBPeripheralManagerStateUnknown: { + case CBManagerStateUnknown: { error = logUnknown; break; } - case CBPeripheralManagerStateResetting: { + case CBManagerStateResetting: { error = logResetting; break; } - case CBPeripheralManagerStateUnsupported: { + case CBManagerStateUnsupported: { error = logUnsupported; break; } - case CBPeripheralManagerStatePoweredOn: { + case CBManagerStatePoweredOn: { //Bluetooth on! break; } @@ -666,7 +679,7 @@ - (void)initialize:(CDVInvokedUrlCommand *)command { if (centralManager != nil) { NSDictionary* returnObj = nil; CDVPluginResult* pluginResult = nil; - if ([centralManager state] == CBCentralManagerStatePoweredOn) + if ([centralManager state] == CBManagerStatePoweredOn) { returnObj = [NSDictionary dictionaryWithObjectsAndKeys: statusEnabled, keyStatus, nil]; @@ -711,7 +724,6 @@ - (void)initialize:(CDVInvokedUrlCommand *)command { //Create dictionary to hold connections and all their callbacks connections = [NSMutableDictionary dictionary]; - [connections retain]; } - (void)enable:(CDVInvokedUrlCommand *)command { @@ -833,6 +845,57 @@ - (void)retrieveConnected:(CDVInvokedUrlCommand *)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)retrievePeripheralsByAddress:(CDVInvokedUrlCommand *)command { + //Ensure Bluetooth is enabled + if ([self isNotInitialized:command]) { + return; + } + + //Get an array of addresses to filter by + NSDictionary *obj = [self getArgsObject:command.arguments]; + NSMutableArray* addresses = nil; + if (obj != nil) { + addresses = [self getAddresses:obj forType:keyAddresses]; + } + + //retrievePeripheralsWithIdentifiers doesn't like nil UUID array + if (addresses == nil) { + addresses = [NSMutableArray array]; + } + + //Get paired peripherals with specified addresses/identifiers + NSArray* peripherals = [centralManager retrievePeripheralsWithIdentifiers:addresses]; + + //Array to store returned peripherals + NSMutableArray* peripheralsOut = [[NSMutableArray alloc] init]; + + //Create an object from each peripheral containing connection ID and name, and add to array + for (CBPeripheral* peripheral in peripherals) { + NSMutableDictionary* peripheralOut = [NSMutableDictionary dictionary]; + [self addDevice:peripheral :peripheralOut]; + [peripheralsOut addObject:peripheralOut]; + } + + //Return the array + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:peripheralsOut]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)bond:(CDVInvokedUrlCommand *)command { + NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: errorBond, keyError, logOperationUnsupported, keyMessage, nil]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)unbond:(CDVInvokedUrlCommand *)command { + NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: errorUnbond, keyError, logOperationUnsupported, keyMessage, nil]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (void)connect:(CDVInvokedUrlCommand *)command { //Ensure Bluetooth is enabled if ([self isNotInitialized:command]) { @@ -1543,9 +1606,9 @@ - (void)writeQ:(CDVInvokedUrlCommand *)command { } //Get the value to write - NSData* value = [self getValue:obj]; + writeQData = [self getValue:obj]; //And ensure it's not empty - if (value == nil) { + if (writeQData == nil) { NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; [self addDevice:peripheral :returnObj]; @@ -1563,34 +1626,15 @@ - (void)writeQ:(CDVInvokedUrlCommand *)command { [self addCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite forCallback:command.callbackId]; //Get the write type (response or no response) - int writeType = [self getWriteType:obj]; - - NSUInteger length = [value length]; - NSUInteger chunkSize = 20; - NSUInteger offset = 0; - do { - NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset; - NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[value bytes] + offset length:thisChunkSize freeWhenDone:NO]; - - offset += thisChunkSize; - [peripheral writeValue:chunk forCharacteristic:characteristic type:writeType]; - } while (offset < length); + writeQtype = [self getWriteType:obj]; - //Write without response won't execute any callbacks, so return immediately - if (writeType == CBCharacteristicWriteWithoutResponse) { - NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; - - [self addDevice:peripheral :returnObj]; - [self addCharacteristic:characteristic :returnObj]; + writeQLength = [writeQData length]; + writeQLocation = 0; + writeQChunkSize = [self getChunkSize:obj]; - [self addValue:value toDictionary:returnObj]; - - [returnObj setValue:statusWritten forKey:keyStatus]; + writeQIsRunning = true; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; - [pluginResult setKeepCallbackAsBool:false]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - } + [self writeDataToCharacteristic:characteristic toPeripheral:peripheral]; } - (void)readDescriptor:(CDVInvokedUrlCommand *)command { @@ -1803,7 +1847,7 @@ - (void)isInitialized:(CDVInvokedUrlCommand *)command { - (void)isEnabled:(CDVInvokedUrlCommand *)command { //See if Bluetooth is currently enabled - NSNumber* result = [NSNumber numberWithBool:(centralManager != nil && centralManager.state == CBCentralManagerStatePoweredOn)]; + NSNumber* result = [NSNumber numberWithBool:(centralManager != nil && centralManager.state == CBManagerStatePoweredOn)]; NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: result, keyIsEnabled, nil]; CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; @@ -1821,6 +1865,13 @@ - (void)isScanning:(CDVInvokedUrlCommand *)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)isBonded:(CDVInvokedUrlCommand *)command { + NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: errorIsBonded, keyError, logOperationUnsupported, keyMessage, nil]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + - (void)wasConnected:(CDVInvokedUrlCommand *)command { //Ensure Bluetooth is enabled if ([self isNotInitialized:command]) { @@ -2000,32 +2051,32 @@ - (void) centralManagerDidUpdateState:(CBCentralManager *)central { //Decide on error message NSString* error = nil; switch ([centralManager state]) { - case CBCentralManagerStatePoweredOff: { + case CBManagerStatePoweredOff: { error = logPoweredOff; break; } - case CBCentralManagerStateUnauthorized: { + case CBManagerStateUnauthorized: { error = logUnauthorized; break; } - case CBCentralManagerStateUnknown: { + case CBManagerStateUnknown: { error = logUnknown; break; } - case CBCentralManagerStateResetting: { + case CBManagerStateResetting: { error = logResetting; break; } - case CBCentralManagerStateUnsupported: { + case CBManagerStateUnsupported: { error = logUnsupported; break; } - case CBCentralManagerStatePoweredOn: { + case CBManagerStatePoweredOn: { //Bluetooth on! break; } @@ -2207,6 +2258,9 @@ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPe if (callback == nil) { return; } + + //Reset writeQIsRunning flag since we have no way of knowing if one was interupted + writeQIsRunning = false; //Return disconnected connection information NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; @@ -2524,6 +2578,28 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(C [self addDevice:peripheral :returnObj]; [self addCharacteristic:characteristic :returnObj]; + // find & set serviceIndex & characteristicIndex + int serviceIndex = 0; + for (CBService* item in peripheral.services) { + if ([item isEqual: characteristic.service]) { + break; + } else if ([item.UUID isEqual: characteristic.service.UUID]) { + serviceIndex++; + } + } + + int characteristicIndex = 0; + for (CBCharacteristic* item in characteristic.service.characteristics) { + if ([item isEqual: characteristic]) { + break; + } else if ([item.UUID isEqual: characteristic.UUID]) { + characteristicIndex++; + } + } + + [returnObj setValue:@(serviceIndex) forKey:keyServiceIndex]; + [returnObj setValue:@(characteristicIndex) forKey:keyCharacteristicIndex]; + //If an error exists... if (error != nil) { //Get the callback based on whether subscription or read @@ -2649,41 +2725,47 @@ - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CB return; } - //Get the proper callback for write operation - NSString* callback = [self getCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; - [self removeCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; + if (writeQIsRunning && (writeQLocation < writeQLength)) { + [self writeDataToCharacteristic:characteristic toPeripheral:peripheral]; + } else { + //Get the proper callback for write operation + NSString* callback = [self getCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; + [self removeCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; - //Return if callback is null - if (callback == nil) { - return; - } + //Return if callback is null + if (callback == nil) { + return; + } - NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; + NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; - [self addDevice:peripheral :returnObj]; - [self addCharacteristic:characteristic :returnObj]; + [self addDevice:peripheral :returnObj]; + [self addCharacteristic:characteristic :returnObj]; - //If error exists, return error - if (error != nil) { - [returnObj setValue:errorWrite forKey:keyError]; - [returnObj setValue:error.description forKey:keyMessage]; + //If error exists, return error + if (error != nil) { + [returnObj setValue:errorWrite forKey:keyError]; + [returnObj setValue:error.description forKey:keyMessage]; - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:returnObj]; - [pluginResult setKeepCallbackAsBool:false]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; - return; - } + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; + return; + } - //Add characteristic value to object - [self addValue:characteristic.value toDictionary:returnObj]; + //Add characteristic value to object + [self addValue:characteristic.value toDictionary:returnObj]; - //Update status - [returnObj setValue:statusWritten forKey:keyStatus]; + //Update status + [returnObj setValue:statusWritten forKey:keyStatus]; - //Return data - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; - [pluginResult setKeepCallbackAsBool:false]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; + writeQIsRunning = false; + + //Return data + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; + } } - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error { @@ -2799,7 +2881,7 @@ - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForChara } } -- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error { +- (void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error { //Get connection NSMutableDictionary* connection = [connections objectForKey:peripheral.identifier]; if (connection == nil) { @@ -2832,7 +2914,7 @@ - (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)erro } //Return RSSI value - [returnObj setValue:peripheral.RSSI forKey:keyRssi]; + [returnObj setValue:RSSI forKey:keyRssi]; [returnObj setValue:statusRssi forKey:keyStatus]; CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; @@ -2840,6 +2922,43 @@ - (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)erro [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; } +//This is called when peripheral is ready to accept more data when using write without response +- (void)peripheralIsReadyToSendWriteWithoutResponse:(CBPeripheral *)peripheral { + CBCharacteristic * const characteristic = currentWriteCharacteristic; + currentWriteCharacteristic = nil; + + if (characteristic && writeQIsRunning) { + if (writeQLocation < writeQLength) { + [self writeDataToCharacteristic:characteristic toPeripheral:peripheral]; + } else { + //Get connection + NSMutableDictionary* connection = [connections objectForKey:peripheral.identifier]; + if (connection == nil) { + return; + } + + //Get the proper callback for write operation + NSString* callback = [self getCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; + [self removeCallback:characteristic.UUID forConnection:connection forOperationType:operationWrite]; + + NSMutableDictionary* returnObj = [NSMutableDictionary dictionary]; + + [self addDevice:peripheral :returnObj]; + [self addCharacteristic:characteristic :returnObj]; + + [self addValue:characteristic.value toDictionary:returnObj]; + + [returnObj setValue:statusWritten forKey:keyStatus]; + + writeQIsRunning = false; + + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:returnObj]; + [pluginResult setKeepCallbackAsBool:false]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:callback]; + } + } +} + //Helpers for Callbacks - (NSMutableDictionary*) ensureCallback: (CBUUID *) characteristicUuid forConnection:(NSMutableDictionary*) connection { //See if callback map exists for characteristic @@ -3020,7 +3139,7 @@ - (BOOL) isNotInitialized:(CDVInvokedUrlCommand *)command { } - (BOOL) isNotEnabled:(CDVInvokedUrlCommand *)command { - if (centralManager.state != CBCentralManagerStatePoweredOn) { + if (centralManager.state != CBManagerStatePoweredOn) { NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: errorEnable, keyError, logNotEnabled, keyMessage, nil]; CDVPluginResult *pluginResult = nil; @@ -3313,10 +3432,6 @@ -(NSData*) getValue:(NSDictionary *) obj { NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0]; - if (data == nil || data.length == 0) { - return nil; - } - return data; } @@ -3400,6 +3515,38 @@ -(NSUUID*) getAddress:(NSDictionary *)obj { return [[NSUUID UUID] initWithUUIDString:addressString]; } +-(NSMutableArray*) getAddresses:(NSDictionary *) dictionary forType:(NSString*) type { + NSMutableArray* addresses = [[NSMutableArray alloc] init]; + + NSArray* addressStrings = [dictionary valueForKey:type]; + + if (addressStrings == nil) { + return nil; + } + + if (![addressStrings isKindOfClass:[NSArray class]]) { + return nil; + } + + for (NSString* addressString in addressStrings) { + if (![addressString isKindOfClass:[NSString class]]) { + continue; + } + + NSUUID* address = [[NSUUID UUID] initWithUUIDString:addressString]; + + if (address != nil) { + [addresses addObject:address]; + } + } + + if (addresses.count == 0) { + return nil; + } + + return addresses; +} + -(NSNumber*) getRequest:(NSDictionary *)obj { NSNumber* request = [obj valueForKey:keyRequest]; @@ -3451,6 +3598,18 @@ -(int) getWriteType:(NSDictionary *)obj { return CBCharacteristicWriteWithoutResponse; } +-(int) getChunkSize:(NSDictionary *)obj { + NSNumber* chunkSize = [obj valueForKey:keyChunkSize]; + + if (chunkSize == nil) { + return 20; + } + + int chunkSizeValue = [chunkSize intValue]; + + return chunkSizeValue; +} + -(NSObject*) formatName:(NSString*)name { if (name != nil) { return name; @@ -3480,15 +3639,23 @@ -(CBService*) getService:(NSDictionary *)obj forPeripheral:(CBPeripheral*)periph return nil; } - CBService* service = nil; + NSString* serviceIndexFromDict = [obj valueForKey:keyServiceIndex]; + int serviceIndex = 0; + + if (serviceIndexFromDict != nil) { + serviceIndex = [serviceIndexFromDict intValue]; + } + int found = 0; for (CBService* item in peripheral.services) { - if ([item.UUID isEqual: uuid]) { - service = item; + if ([item.UUID isEqual: uuid] && (serviceIndex == found)) { + return item; + } else if ([item.UUID isEqual: uuid] && (serviceIndex != found)) { + found++; } } - - return service; + + return nil; } -(CBCharacteristic*) getCharacteristic:(NSDictionary *) obj forService:(CBService*) service { @@ -3512,15 +3679,23 @@ -(CBCharacteristic*) getCharacteristic:(NSDictionary *) obj forService:(CBServic return nil; } - CBCharacteristic* characteristic = nil; + NSString* characteristicIndexFromDict = [obj valueForKey:keyCharacteristicIndex]; + int characteristicIndex = 0; + + if (characteristicIndexFromDict != nil) { + characteristicIndex = [characteristicIndexFromDict intValue]; + } + int found = 0; for (CBCharacteristic* item in service.characteristics) { - if ([item.UUID isEqual: uuid]) { - characteristic = item; + if ([item.UUID isEqual: uuid] && (characteristicIndex == found)) { + return item; + } else if ([item.UUID isEqual: uuid] && (characteristicIndex != found)) { + found++; } } - - return characteristic; + + return nil; } -(CBDescriptor*) getDescriptor:(NSDictionary *) obj forCharacteristic:(CBCharacteristic*) characteristic { @@ -3602,12 +3777,35 @@ -(NSMutableDictionary*) getProperties:(CBCharacteristic*) characteristic { return propertiesObject; } --(void)dealloc { - if (connections != nil) { - [connections release]; - connections = nil; +- (void)writeDataToCharacteristic:(CBCharacteristic *)characteristic toPeripheral:(CBPeripheral*) peripheral { + NSData *data; + + if (self->writeQtype == CBCharacteristicWriteWithoutResponse) { + // Since WriteWithoutResponse triggers a different callback which has no access to characteristic + self->currentWriteCharacteristic = characteristic; + + while (self->writeQLocation < self->writeQLength && peripheral.canSendWriteWithoutResponse) { + NSInteger currentLength = self->writeQLength - self->writeQLocation; + NSInteger chunkSize = currentLength < self->writeQChunkSize ? currentLength : self->writeQChunkSize; + + data = [self->writeQData subdataWithRange:NSMakeRange(self->writeQLocation, chunkSize)]; + + [peripheral writeValue:data forCharacteristic:characteristic type:self->writeQtype]; + + self->writeQLocation = self->writeQLocation + chunkSize; } - [super dealloc]; + } else { + while (self->writeQLocation < self->writeQLength) { + NSInteger currentLength = self->writeQLength - self->writeQLocation; + NSInteger chunkSize = currentLength < self->writeQChunkSize ? currentLength : self->writeQChunkSize; + + data = [self->writeQData subdataWithRange:NSMakeRange(self->writeQLocation, chunkSize)]; + + [peripheral writeValue:data forCharacteristic:characteristic type:self->writeQtype]; + + self->writeQLocation = self->writeQLocation + chunkSize; + } + } } @end