Skip to content

Commit 2e33e86

Browse files
authored
Merge pull request #696 from VinardoZzZ2000/feat/notify-queue
Android: Notification queueing
2 parents d9517d8 + 3d67010 commit 2e33e86

File tree

2 files changed

+93
-8
lines changed

2 files changed

+93
-8
lines changed

readme.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,14 @@ Discovery works differently between Android and iOS. In Android, a single functi
181181

182182

183183
## Queuing (Android) ##
184-
Read, write, subscribe, unsubscribe, readDescriptor and writeDescriptor queueing has been added to the master branch and will be part of the 4.1.0 release. If you'd like to try it out, install the plugin directly from GitHub using: ```cordova plugin https://github.com/randdusing/cordova-plugin-bluetoothle```
184+
On Android, the plugin automatically queues the following operations:
185+
* read
186+
* write
187+
* subscribe
188+
* unsubscribe
189+
* readDescriptor
190+
* writeDescriptor
191+
* notify
185192

186193

187194
## UUIDs ##
@@ -1809,7 +1816,7 @@ Initialization works slightly different between Android and iOS. On iOS, you don
18091816

18101817

18111818
### Notifications ###
1812-
Notifications work slightly differently between Android and iOS. On Android, you should wait for the ```notificationSent``` event before calling notify() again. On iOS, you need to check the notify() callback for the sent property. If the sent property is set to false, you should wait until receiving the ```peripheralManagerIsReadyToUpdateSubscribers``` event to resend the notification. In future versions, I hope to standardize the functionality between platforms.
1819+
Notifications work slightly differently between Android and iOS. Queueing for notifications is currently only implemented on Android. On Android, the notify() success callback will always include ```sent: true```. On iOS, if the sent property is set to false, you should wait until receiving the ```peripheralManagerIsReadyToUpdateSubscribers``` event to resend the notification. In future versions, I hope to standardize the functionality between platforms.
18131820

18141821

18151822
### Descriptors ###
@@ -1845,7 +1852,7 @@ bluetoothle.initializePeripheral(success, error, params);
18451852
* status => subscribed = Subscription started request, use notify() to send new data
18461853
* status => unsubscribed = Subscription ended request, stop sending data
18471854
* status => notificationReady = Resume sending subscription updates (iOS)
1848-
* status => notificationSent = Notification has been sent (Android)
1855+
* status => notificationSent = Notification has been sent (Android) [DEPRECATED: Use notify() callback instead]
18491856
* status => connected = A device has connected
18501857
* status => disconnected = A device has disconnected
18511858
* status => mtuChanged = MTU has changed for device

src/android/BluetoothLePlugin.java

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ public class BluetoothLePlugin extends CordovaPlugin {
8989
//Quick Writes
9090
private LinkedList<byte[]> queueQuick = new LinkedList<byte[]>();
9191

92+
//Peripheral queue
93+
private LinkedList<Operation> peripheralQueue = new LinkedList<Operation>();
94+
9295
//Object keys
9396
private final String keyStatus = "status";
9497
private final String keyError = "error";
@@ -127,6 +130,7 @@ public class BluetoothLePlugin extends CordovaPlugin {
127130
private final String keyConnectionPriority = "connectionPriority";
128131
private final String keyMtu = "mtu";
129132
private final String keyPin = "pin";
133+
private final String keySent = "sent";
130134
private final String keyQueue = "queue";
131135

132136
//Write Types
@@ -155,6 +159,7 @@ public class BluetoothLePlugin extends CordovaPlugin {
155159
private final String statusRssi = "rssi";
156160
private final String statusConnectionPriorityRequested = "connectionPriorityRequested";
157161
private final String statusMtu = "mtu";
162+
private final String statusNotified = "notified";
158163

159164
//Properties
160165
private final String propertyBroadcast = "broadcast";
@@ -212,6 +217,7 @@ public class BluetoothLePlugin extends CordovaPlugin {
212217
private final String errorRequestConnectionPriority = "requestConnectPriority";
213218
private final String errorMtu = "mtu";
214219
private final String errorRetrievePeripheralsByAddress = "retrievePeripheralsByAddress";
220+
private final String errorNotify = "notify";
215221

216222
//Error Messages
217223
//Initialization
@@ -414,7 +420,9 @@ public boolean execute(String action, final JSONArray args, final CallbackContex
414420
} else if ("respond".equals(action)) {
415421
respondAction(args, callbackContext);
416422
} else if ("notify".equals(action)) {
417-
notifyAction(args, callbackContext);
423+
Operation operation = new Operation("notify", args, callbackContext);
424+
peripheralQueue.add(operation);
425+
peripheralQueueStart();
418426
} else if ("setPin".equals(action)) {
419427
setPinAction(args, callbackContext);
420428
} else if ("retrievePeripheralsByAddress".equals(action)) {
@@ -822,15 +830,18 @@ private void respondAction(JSONArray args, CallbackContext callbackContext) {
822830
}
823831
}
824832

825-
private void notifyAction(JSONArray args, CallbackContext callbackContext) {
833+
private boolean notifyAction(Operation operation) {
834+
JSONArray args = operation.args;
835+
CallbackContext callbackContext = operation.callbackContext;
836+
826837
JSONObject obj = getArgsObject(args);
827838
if (isNotArgsObject(obj, callbackContext)) {
828-
return;
839+
return false;
829840
}
830841

831842
String address = getAddress(obj);
832843
if (isNotAddress(address, callbackContext)) {
833-
return;
844+
return false;
834845
}
835846
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
836847

@@ -841,6 +852,7 @@ private void notifyAction(JSONArray args, CallbackContext callbackContext) {
841852
addProperty(returnObj, "error", "service");
842853
addProperty(returnObj, "message", "Service not found");
843854
callbackContext.error(returnObj);
855+
return false;
844856
}
845857

846858
UUID characteristicUuid = getUUID(obj.optString("characteristic", null));
@@ -850,6 +862,7 @@ private void notifyAction(JSONArray args, CallbackContext callbackContext) {
850862
addProperty(returnObj, "error", "characteristic");
851863
addProperty(returnObj, "message", "Characteristic not found");
852864
callbackContext.error(returnObj);
865+
return false;
853866
}
854867

855868
byte[] value = getPropertyBytes(obj, "value");
@@ -859,6 +872,7 @@ private void notifyAction(JSONArray args, CallbackContext callbackContext) {
859872
addProperty(returnObj, "error", "respond");
860873
addProperty(returnObj, "message", "Failed to set value");
861874
callbackContext.error(returnObj);
875+
return false;
862876
}
863877

864878
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientConfigurationDescriptorUuid);
@@ -869,14 +883,16 @@ private void notifyAction(JSONArray args, CallbackContext callbackContext) {
869883
isIndicate = true;
870884
}
871885

872-
//Wait for onNotificationSent event
873886
boolean result = gattServer.notifyCharacteristicChanged(device, characteristic, isIndicate);
874887
if (!result) {
875888
JSONObject returnObj = new JSONObject();
876889
addProperty(returnObj, "error", "notify");
877890
addProperty(returnObj, "message", "Failed to notify");
878891
callbackContext.error(returnObj);
892+
return false;
879893
}
894+
895+
return true;
880896
}
881897

882898
public void hasPermissionAction(CallbackContext callbackContext) {
@@ -3243,6 +3259,38 @@ private void queueRemove(HashMap<Object, Object> connection) {
32433259
queueNext(connection);
32443260
}
32453261

3262+
private void peripheralQueueStart() {
3263+
if (peripheralQueue.size() > 1) {
3264+
return;
3265+
}
3266+
3267+
peripheralQueueNext();
3268+
}
3269+
3270+
private void peripheralQueueNext() {
3271+
Operation operation = peripheralQueue.peek();
3272+
3273+
boolean result = notifyAction(operation);
3274+
3275+
if (!result) {
3276+
peripheralQueueRemove();
3277+
}
3278+
}
3279+
3280+
private void peripheralQueueRemove() {
3281+
if (peripheralQueue.size() == 0) {
3282+
return;
3283+
}
3284+
3285+
peripheralQueue.poll();
3286+
3287+
if (peripheralQueue.size() == 0) {
3288+
return;
3289+
}
3290+
3291+
peripheralQueueNext();
3292+
}
3293+
32463294
private HashMap<Object, Object> EnsureCallback(UUID characteristicUuid, HashMap<Object, Object> connection) {
32473295
HashMap<Object, Object> characteristicCallbacks = (HashMap<Object, Object>) connection.get(characteristicUuid);
32483296

@@ -4630,6 +4678,36 @@ public void onMtuChanged(BluetoothDevice device, int mtu) {
46304678
}
46314679

46324680
public void onNotificationSent(BluetoothDevice device, int status) {
4681+
Operation operation = peripheralQueue.peek();
4682+
4683+
JSONArray args = operation.args;
4684+
CallbackContext callbackContext = operation.callbackContext;
4685+
4686+
peripheralQueueRemove();
4687+
4688+
//If no callback, just return
4689+
if (callbackContext == null) {
4690+
return;
4691+
}
4692+
4693+
JSONObject obj = getArgsObject(args);
4694+
4695+
JSONObject returnObj = new JSONObject();
4696+
4697+
addDevice(returnObj, device);
4698+
4699+
//If successfully notified, return value
4700+
if (status == BluetoothGatt.GATT_SUCCESS) {
4701+
addProperty(returnObj, keyStatus, statusNotified);
4702+
addProperty(returnObj, keySent, true);
4703+
callbackContext.success(returnObj);
4704+
} else {
4705+
//Else it failed
4706+
addProperty(returnObj, keyError, errorNotify);
4707+
callbackContext.error(returnObj);
4708+
}
4709+
4710+
//TODO: Remove code below (kept for backward compatibility)
46334711
if (initPeripheralCallback == null) {
46344712
return;
46354713
}

0 commit comments

Comments
 (0)