Skip to content

Commit 1a0a107

Browse files
authored
Merge pull request #687 from mes-indesign/fix/419
Fix out-of-order notifications on Android
2 parents a638c90 + 9dc6d8a commit 1a0a107

File tree

4 files changed

+118
-11
lines changed

4 files changed

+118
-11
lines changed

plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
</config-file>
2121
<source-file src="src/android/BluetoothLePlugin.java" target-dir="src/com/randdusing/bluetoothle" />
2222
<source-file src="src/android/Operation.java" target-dir="src/com/randdusing/bluetoothle" />
23+
<source-file src="src/android/SequentialCallbackContext.java" target-dir="src/com/randdusing/bluetoothle" />
2324
<config-file target="AndroidManifest.xml" parent="/manifest">
2425
<uses-permission android:name="android.permission.BLUETOOTH"/>
2526
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

src/android/BluetoothLePlugin.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,7 +1844,7 @@ private boolean subscribeAction(Operation operation) {
18441844

18451845
addCharacteristic(returnObj, characteristic);
18461846

1847-
CallbackContext checkExisting = GetCallback(characteristicUuid, connection, operationSubscribe);
1847+
SequentialCallbackContext checkExisting = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);
18481848
if (checkExisting != null) {
18491849
addProperty(returnObj, keyError, errorSubscription);
18501850
addProperty(returnObj, keyMessage, logSubscribeAlready);
@@ -1878,7 +1878,7 @@ private boolean subscribeAction(Operation operation) {
18781878
return false;
18791879
}
18801880

1881-
AddCallback(characteristicUuid, connection, operationSubscribe, callbackContext);
1881+
AddSequentialCallbackContext(characteristicUuid, connection, operationSubscribe, callbackContext);
18821882

18831883
//Write the descriptor value
18841884
result = bluetoothGatt.writeDescriptor(descriptor);
@@ -1952,7 +1952,7 @@ private boolean unsubscribeAction(Operation operation) {
19521952

19531953
addCharacteristic(returnObj, characteristic);
19541954

1955-
CallbackContext checkExisting = GetCallback(characteristicUuid, connection, operationSubscribe);
1955+
SequentialCallbackContext checkExisting = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);
19561956
if (checkExisting == null) {
19571957
addProperty(returnObj, keyError, errorSubscription);
19581958
addProperty(returnObj, keyMessage, logUnsubscribeAlready);
@@ -3283,6 +3283,23 @@ private CallbackContext GetCallback(UUID characteristicUuid, HashMap<Object, Obj
32833283
return (CallbackContext) characteristicCallbacks.get(operationType);
32843284
}
32853285

3286+
private void AddSequentialCallbackContext(UUID characteristicUuid, HashMap<Object, Object> connection, String operationType, CallbackContext callbackContext) {
3287+
HashMap<Object, Object> characteristicCallbacks = EnsureCallback(characteristicUuid, connection);
3288+
3289+
characteristicCallbacks.put(operationType, new SequentialCallbackContext(callbackContext));
3290+
}
3291+
3292+
private SequentialCallbackContext GetSequentialCallbackContext(UUID characteristicUuid, HashMap<Object, Object> connection, String operationType) {
3293+
HashMap<Object, Object> characteristicCallbacks = (HashMap<Object, Object>) connection.get(characteristicUuid);
3294+
3295+
if (characteristicCallbacks == null) {
3296+
return null;
3297+
}
3298+
3299+
//This may return null
3300+
return (SequentialCallbackContext) characteristicCallbacks.get(operationType);
3301+
}
3302+
32863303
private CallbackContext[] GetCallbacks(HashMap<Object, Object> connection) {
32873304
ArrayList<CallbackContext> callbacks = new ArrayList<CallbackContext>();
32883305

@@ -3324,7 +3341,12 @@ private void GetMoreCallbacks(HashMap<Object, Object> lower, ArrayList<CallbackC
33243341
continue;
33253342
}
33263343

3327-
CallbackContext callback = (CallbackContext) lower.get(key);
3344+
CallbackContext callback;
3345+
if (key.equals(operationSubscribe)) {
3346+
callback = ((SequentialCallbackContext) lower.get(key)).getContext();
3347+
} else {
3348+
callback = (CallbackContext) lower.get(key);
3349+
}
33283350

33293351
if (callback == null) {
33303352
continue;
@@ -4175,7 +4197,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris
41754197

41764198
UUID characteristicUuid = characteristic.getUuid();
41774199

4178-
CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe);
4200+
SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);
41794201

41804202
//If no callback, just return
41814203
if (callbackContext == null) {
@@ -4192,9 +4214,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris
41924214
addPropertyBytes(returnObj, keyValue, characteristic.getValue());
41934215

41944216
//Return the characteristic value
4195-
PluginResult result = new PluginResult(PluginResult.Status.OK, returnObj);
4196-
result.setKeepCallback(true);
4197-
callbackContext.sendPluginResult(result);
4217+
callbackContext.sendSequentialResult(returnObj);
41984218
}
41994219

42004220
@Override
@@ -4365,7 +4385,7 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri
43654385

43664386
callbackContext.success(returnObj);
43674387
} else {
4368-
CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe);
4388+
SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);
43694389

43704390
//If no callback, just return
43714391
if (callbackContext == null) {
@@ -4376,7 +4396,7 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri
43764396

43774397
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj);
43784398
pluginResult.setKeepCallback(true);
4379-
callbackContext.sendPluginResult(pluginResult);
4399+
callbackContext.getContext().sendPluginResult(pluginResult);
43804400
}
43814401

43824402
return;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Inspiration taken from cordova-plugin-ble-central
2+
3+
package com.randdusing.bluetoothle;
4+
5+
import org.apache.cordova.CallbackContext;
6+
import org.apache.cordova.PluginResult;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import org.json.JSONObject;
10+
11+
public class SequentialCallbackContext {
12+
private int sequence;
13+
private CallbackContext context;
14+
15+
public SequentialCallbackContext(CallbackContext context) {
16+
this.context = context;
17+
this.sequence = 0;
18+
}
19+
20+
private int getNextSequenceNumber() {
21+
synchronized(this) {
22+
return this.sequence++;
23+
}
24+
}
25+
26+
public CallbackContext getContext() {
27+
return this.context;
28+
}
29+
30+
public PluginResult createSequentialResult(JSONObject returnObj) {
31+
List<PluginResult> resultList = new ArrayList<PluginResult>(2);
32+
33+
PluginResult dataResult = new PluginResult(PluginResult.Status.OK, returnObj);
34+
PluginResult sequenceResult = new PluginResult(PluginResult.Status.OK, this.getNextSequenceNumber());
35+
36+
resultList.add(dataResult);
37+
resultList.add(sequenceResult);
38+
39+
return new PluginResult(PluginResult.Status.OK, resultList);
40+
}
41+
42+
public void sendSequentialResult(JSONObject returnObj) {
43+
PluginResult result = this.createSequentialResult(returnObj);
44+
result.setKeepCallback(true);
45+
46+
this.context.sendPluginResult(result);
47+
}
48+
}

www/bluetoothle.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
var bluetoothleName = "BluetoothLePlugin";
22
var bluetoothle = {
3+
_newReorderer: function(successCallback) {
4+
let context = {
5+
callback: successCallback,
6+
onHold: {},
7+
nextExpected: 0,
8+
};
9+
return bluetoothle._reorderCallback.bind(context);
10+
},
11+
_reorderCallback: function(obj, sequence) {
12+
/**
13+
* If there is not a sequence number present, just pass the callback through
14+
* without reordering it.
15+
*/
16+
if (sequence == null) {
17+
this.callback(obj);
18+
return;
19+
}
20+
21+
if (sequence != this.nextExpected) console.warn("Received out of order: expected " + this.nextExpected +" got " + sequence);
22+
23+
this.onHold[sequence] = obj;
24+
25+
bluetoothle._tryDispatchInOrder.bind(this)();
26+
},
27+
_tryDispatchInOrder: function() {
28+
while (this.nextExpected in this.onHold) {
29+
try {
30+
let value = this.onHold[this.nextExpected];
31+
delete this.onHold[this.nextExpected];
32+
33+
this.nextExpected += 1;
34+
35+
this.callback(value);
36+
} catch (err) {
37+
console.error("Error in callback in Reorderer", err);
38+
}
39+
}
40+
},
341
initialize: function(successCallback, params) {
442
cordova.exec(successCallback, successCallback, bluetoothleName, "initialize", [params]);
543
},
@@ -55,7 +93,7 @@ var bluetoothle = {
5593
cordova.exec(successCallback, errorCallback, bluetoothleName, "read", [params]);
5694
},
5795
subscribe: function(successCallback, errorCallback, params) {
58-
cordova.exec(successCallback, errorCallback, bluetoothleName, "subscribe", [params]);
96+
cordova.exec(bluetoothle._newReorderer(successCallback), errorCallback, bluetoothleName, "subscribe", [params]);
5997
},
6098
unsubscribe: function(successCallback, errorCallback, params) {
6199
cordova.exec(successCallback, errorCallback, bluetoothleName, "unsubscribe", [params]);

0 commit comments

Comments
 (0)