Skip to content

Commit

Permalink
Merge pull request #687 from mes-indesign/fix/419
Browse files Browse the repository at this point in the history
Fix out-of-order notifications on Android
  • Loading branch information
randdusing authored Jun 8, 2021
2 parents a638c90 + 9dc6d8a commit 1a0a107
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 11 deletions.
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
</config-file>
<source-file src="src/android/BluetoothLePlugin.java" target-dir="src/com/randdusing/bluetoothle" />
<source-file src="src/android/Operation.java" target-dir="src/com/randdusing/bluetoothle" />
<source-file src="src/android/SequentialCallbackContext.java" target-dir="src/com/randdusing/bluetoothle" />
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
Expand Down
40 changes: 30 additions & 10 deletions src/android/BluetoothLePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -1844,7 +1844,7 @@ private boolean subscribeAction(Operation operation) {

addCharacteristic(returnObj, characteristic);

CallbackContext checkExisting = GetCallback(characteristicUuid, connection, operationSubscribe);
SequentialCallbackContext checkExisting = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);
if (checkExisting != null) {
addProperty(returnObj, keyError, errorSubscription);
addProperty(returnObj, keyMessage, logSubscribeAlready);
Expand Down Expand Up @@ -1878,7 +1878,7 @@ private boolean subscribeAction(Operation operation) {
return false;
}

AddCallback(characteristicUuid, connection, operationSubscribe, callbackContext);
AddSequentialCallbackContext(characteristicUuid, connection, operationSubscribe, callbackContext);

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

addCharacteristic(returnObj, characteristic);

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

private void AddSequentialCallbackContext(UUID characteristicUuid, HashMap<Object, Object> connection, String operationType, CallbackContext callbackContext) {
HashMap<Object, Object> characteristicCallbacks = EnsureCallback(characteristicUuid, connection);

characteristicCallbacks.put(operationType, new SequentialCallbackContext(callbackContext));
}

private SequentialCallbackContext GetSequentialCallbackContext(UUID characteristicUuid, HashMap<Object, Object> connection, String operationType) {
HashMap<Object, Object> characteristicCallbacks = (HashMap<Object, Object>) connection.get(characteristicUuid);

if (characteristicCallbacks == null) {
return null;
}

//This may return null
return (SequentialCallbackContext) characteristicCallbacks.get(operationType);
}

private CallbackContext[] GetCallbacks(HashMap<Object, Object> connection) {
ArrayList<CallbackContext> callbacks = new ArrayList<CallbackContext>();

Expand Down Expand Up @@ -3324,7 +3341,12 @@ private void GetMoreCallbacks(HashMap<Object, Object> lower, ArrayList<CallbackC
continue;
}

CallbackContext callback = (CallbackContext) lower.get(key);
CallbackContext callback;
if (key.equals(operationSubscribe)) {
callback = ((SequentialCallbackContext) lower.get(key)).getContext();
} else {
callback = (CallbackContext) lower.get(key);
}

if (callback == null) {
continue;
Expand Down Expand Up @@ -4175,7 +4197,7 @@ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteris

UUID characteristicUuid = characteristic.getUuid();

CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe);
SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);

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

//Return the characteristic value
PluginResult result = new PluginResult(PluginResult.Status.OK, returnObj);
result.setKeepCallback(true);
callbackContext.sendPluginResult(result);
callbackContext.sendSequentialResult(returnObj);
}

@Override
Expand Down Expand Up @@ -4365,7 +4385,7 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri

callbackContext.success(returnObj);
} else {
CallbackContext callbackContext = GetCallback(characteristicUuid, connection, operationSubscribe);
SequentialCallbackContext callbackContext = GetSequentialCallbackContext(characteristicUuid, connection, operationSubscribe);

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

PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, returnObj);
pluginResult.setKeepCallback(true);
callbackContext.sendPluginResult(pluginResult);
callbackContext.getContext().sendPluginResult(pluginResult);
}

return;
Expand Down
48 changes: 48 additions & 0 deletions src/android/SequentialCallbackContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Inspiration taken from cordova-plugin-ble-central

package com.randdusing.bluetoothle;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;
import java.util.ArrayList;
import java.util.List;
import org.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<PluginResult> resultList = new ArrayList<PluginResult>(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);
}
}
40 changes: 39 additions & 1 deletion www/bluetoothle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
var bluetoothleName = "BluetoothLePlugin";
var bluetoothle = {
_newReorderer: function(successCallback) {
let context = {
callback: successCallback,
onHold: {},
nextExpected: 0,
};
return bluetoothle._reorderCallback.bind(context);
},
_reorderCallback: function(obj, sequence) {
/**
* If there is not a sequence number present, just pass the callback through
* without reordering it.
*/
if (sequence == null) {
this.callback(obj);
return;
}

if (sequence != this.nextExpected) console.warn("Received out of order: expected " + this.nextExpected +" got " + sequence);

this.onHold[sequence] = obj;

bluetoothle._tryDispatchInOrder.bind(this)();
},
_tryDispatchInOrder: function() {
while (this.nextExpected in this.onHold) {
try {
let value = this.onHold[this.nextExpected];
delete this.onHold[this.nextExpected];

this.nextExpected += 1;

this.callback(value);
} catch (err) {
console.error("Error in callback in Reorderer", err);
}
}
},
initialize: function(successCallback, params) {
cordova.exec(successCallback, successCallback, bluetoothleName, "initialize", [params]);
},
Expand Down Expand Up @@ -55,7 +93,7 @@ var bluetoothle = {
cordova.exec(successCallback, errorCallback, bluetoothleName, "read", [params]);
},
subscribe: function(successCallback, errorCallback, params) {
cordova.exec(successCallback, errorCallback, bluetoothleName, "subscribe", [params]);
cordova.exec(bluetoothle._newReorderer(successCallback), errorCallback, bluetoothleName, "subscribe", [params]);
},
unsubscribe: function(successCallback, errorCallback, params) {
cordova.exec(successCallback, errorCallback, bluetoothleName, "unsubscribe", [params]);
Expand Down

0 comments on commit 1a0a107

Please sign in to comment.