package io.trygvis.android.bt; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.util.Log; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import io.trygvis.android.Consumer; import io.trygvis.android.F2; import io.trygvis.android.F3; import io.trygvis.android.Function; import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicChanged; import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicRead; import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicWrite; import static io.trygvis.android.bt.BtPromise.EventType.onConnectionStateChange; import static io.trygvis.android.bt.BtPromise.EventType.onDescriptorRead; import static io.trygvis.android.bt.BtPromise.EventType.onDescriptorWrite; import static io.trygvis.android.bt.BtPromise.EventType.onDirect; import static io.trygvis.android.bt.BtPromise.EventType.onFinally; import static io.trygvis.android.bt.BtPromise.EventType.onReliableWriteCompleted; import static io.trygvis.android.bt.BtPromise.EventType.onServicesDiscovered; public class BtPromise { private final static String TAG = BtPromise.class.getSimpleName(); private final List actionQ = new ArrayList<>(); // private final List failureQ = new ArrayList<>(); private final List finallyQ = new ArrayList<>(); private static final PromiseResult waitForNextEvent = new WaitForNextEvent(); private static final PromiseResult stop = new Stop(); private static final PromiseResult fail = new Fail(); private static final PromiseResult continueDirectly = new ContinueDirectly(); private Boolean stopOnFailure; public static class PromiseResult { private PromiseResult() { } public static PromiseResult waitForNextEvent() { return waitForNextEvent; } public static PromiseResult continueDirectly() { return continueDirectly; } public static PromiseResult stop() { return stop; } public static PromiseResult fail() { return fail; } public static PromiseResult detour(BtPromise promise) { return new Detour(promise); } } private static class WaitForNextEvent extends PromiseResult { } private static class ContinueDirectly extends PromiseResult { } private static class Stop extends PromiseResult { } private static class Fail extends PromiseResult { } private static class Detour extends PromiseResult { final BtPromise promise; private Detour(BtPromise promise) { this.promise = promise; } } private boolean stopOnFailure() { if (stopOnFailure != null) { boolean b = stopOnFailure; stopOnFailure = null; return b; } return false; } public BtPromise ignoreFailureForNext() { stopOnFailure = true; return this; } public synchronized BtPromise onConnectionStateChange(F3 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onConnectionStateChange") { @Override public PromiseResult onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { return callback.apply(gatt, status, newState); } }); return this; } public synchronized BtPromise onServicesDiscovered(Function callback) { actionQ.add(new BtCallback(stopOnFailure(), "onServicesDiscovered") { @Override public PromiseResult onServicesDiscovered(BluetoothGatt gatt) { return callback.apply(gatt); } }); return this; } public synchronized BtPromise onCharacteristicRead(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onCharacteristicRead") { @Override public PromiseResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtPromise onCharacteristicWrite(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onCharacteristicWrite") { @Override public PromiseResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtPromise onCharacteristicChanged(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onCharacteristicChanged") { @Override public PromiseResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtPromise onDescriptorRead(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onDescriptorRead") { @Override public PromiseResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { return callback.apply(gatt, descriptor); } }); return this; } public synchronized BtPromise onDescriptorWrite(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onDescriptorWrite") { @Override public PromiseResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { return callback.apply(gatt, descriptor); } }); return this; } public synchronized BtPromise onReliableWriteCompleted(Function callback) { actionQ.add(new BtCallback(stopOnFailure(), "onReliableWriteCompleted") { @Override public PromiseResult onReliableWriteCompleted(BluetoothGatt gatt) { return callback.apply(gatt); } }); return this; } public synchronized BtPromise onReadRemoteRssi(F2 callback) { actionQ.add(new BtCallback(stopOnFailure(), "onReadRemoteRssi") { @Override public PromiseResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { return callback.apply(gatt, rssi); } }); return this; } public synchronized BtPromise onDirect(Function callback) { actionQ.add(new BtCallback(stopOnFailure(), "onDirect") { @Override public PromiseResult onDirect(BluetoothGatt value) { return callback.apply(value); } }); return this; } // public synchronized BtPromise onFailure(Runnable callback) { // failureQ.add(new BtCallback(stopOnFailure(), "onFailure") { // @Override // public void onFailure() { // callback.run(); // } // }); // return this; // } public synchronized BtPromise onFinally(Consumer callback) { finallyQ.add(new BtCallback(stopOnFailure(), "finally") { @Override public void onFinally(boolean success) { callback.accept(success); } }); return this; } public BtPromise andThen(BtPromise btPromise) { actionQ.addAll(btPromise.actionQ); finallyQ.addAll(btPromise.finallyQ); return this; } public String toString() { StringBuilder s = new StringBuilder("Queue: "); Iterator it = actionQ.iterator(); int i = 0; while (it.hasNext()) { BtCallback c = it.next(); if (i > 0) { s.append(", "); } s.append(c.name); i++; } return s.toString(); } BtBluetoothGattCallback asCallback(String address) { return new BtBluetoothGattCallback(address, actionQ, /*failureQ, */finallyQ); } enum EventType { onConnectionStateChange, onServicesDiscovered, onCharacteristicRead, onCharacteristicWrite, onCharacteristicChanged, onDescriptorRead, onDescriptorWrite, onReliableWriteCompleted, onReadRemoteRssi, onDirect, onFailure, onFinally, } private static PromiseResult callCallback(EventType key, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState, Boolean success, BtCallback btCallback) { switch (key) { case onConnectionStateChange: return btCallback.onConnectionStateChange(gatt, status, newState); case onServicesDiscovered: return btCallback.onServicesDiscovered(gatt); case onCharacteristicRead: return btCallback.onCharacteristicRead(gatt, characteristic); case onCharacteristicWrite: return btCallback.onCharacteristicWrite(gatt, characteristic); case onCharacteristicChanged: return btCallback.onCharacteristicChanged(gatt, characteristic); case onDescriptorRead: return btCallback.onDescriptorRead(gatt, descriptor); case onDescriptorWrite: return btCallback.onDescriptorWrite(gatt, descriptor); case onReliableWriteCompleted: return btCallback.onReliableWriteCompleted(gatt); case onDirect: return btCallback.onDirect(gatt); case onFailure: btCallback.onFailure(); return null; case onFinally: btCallback.onFinally(success); return null; default: Log.w(TAG, "Unknown callback: " + key); return null; } } static class BtBluetoothGattCallback extends BluetoothGattCallback { private final String address; private List actionQ; private int currentAction = 0; // private List failureQ; private List finallyQ; private List events = new ArrayList<>(); private BtBluetoothGattCallback(String address, List actionQ, /*List failureQ, */List finallyQ) { this.address = address; this.actionQ = new ArrayList<>(actionQ); // this.failureQ = new ArrayList<>(failureQ); this.finallyQ = new ArrayList<>(finallyQ); } void onEvent(EventType key, String values, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState) { boolean success = status == BluetoothGatt.GATT_SUCCESS; events.add(key + "(" + values + "), success=" + success); Log.i(TAG, "event: " + key + "(" + values + "), success=" + success); BtCallback btCallback; synchronized (this) { if (!hasNext()) { Log.d(TAG, "All Bluetooth actions are done, no handler for last event."); doFinally(true); return; } btCallback = actionQ.get(currentAction++); if (!success) { if (btCallback.stopOnFailure) { // doFailure(); doFinally(false); return; } else { Log.i(TAG, "Last status was a failure, but the callback still want it."); } } // Log.i(TAG, "Graceful sleep" + btCallback.name); // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // // ignore // } } try { Log.i(TAG, "Executing bt action: " + btCallback.name); PromiseResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, null, btCallback); if (result instanceof Stop) { Log.i(TAG, "The chain want to stop."); doFinally(true); return; } if (result instanceof Fail) { Log.i(TAG, "The chain returned fail()."); doFinally(false); return; } if (result instanceof Detour) { BtPromise detour = ((Detour) result).promise; // Log.i(TAG, "Adding detour with " + detour.actionQ.size() + " actions."); events.add("detour, action size=" + detour.actionQ.size() + ", " + // "failure size=" + detour.failureQ.size() + ", " + "finally size=" + detour.finallyQ.size()); // The new promise should probably be stacked on top, so that all of its // finally handlers are executed after the added set concludes and then the // current stack can continue. actionQ.addAll(currentAction, detour.actionQ); // failureQ.addAll(detour.failureQ);B finallyQ.addAll(detour.finallyQ); result = PromiseResult.continueDirectly(); } if (result instanceof ContinueDirectly) { onEvent(onDirect, "direct", gatt, null, null, BluetoothGatt.GATT_SUCCESS, 0); return; } if (!hasNext()) { Log.i(TAG, "The queue is empty"); showEvents(); } } catch (NotOverriddenException e) { Log.w(TAG, "Unexpected callback by listener: " + key); // doFailure(); doFinally(false); } catch (Exception e) { Log.w(TAG, "Exception in callback", e); // doFailure(); doFinally(false); } } private boolean hasNext() { return currentAction < actionQ.size(); } // private void doFailure() { // showEvents(); // // Log.w(TAG, "Executing " + failureQ.size() + " failure handlers"); // // for (BtCallback callback : failureQ) { // callCallback(onFailure, null, null, null, 0, 0, callback, null); // } // // doFinally(); // } private void showEvents() { StringBuilder msg = new StringBuilder(); msg.append("Address: ").append(address).append("\n"); msg.append("Event handlers: \n"); for (BtCallback cb : actionQ) { msg.append("- ").append(cb.name).append("\n"); } // msg.append("Failure handlers: \n"); // for (BtCallback cb : failureQ) { // msg.append("- ").append(cb.name).append("\n"); // } msg.append("Finally handlers: \n"); for (BtCallback cb : finallyQ) { msg.append("- ").append(cb.name).append("\n"); } msg.append("Events received: \n"); for (String event : events) { msg.append("- ").append(event).append("\n"); } Log.w(TAG, msg.toString()); } private void doFinally(boolean success) { showEvents(); actionQ.clear(); Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers, success=" + success); for (BtCallback callback : finallyQ) { try { callCallback(onFinally, null, null, null, 0, 0, success, callback); } catch (NotOverriddenException e) { return; } } } // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { // onEvent(onReadRemoteRssi, "status=" + status, gatt, null, null, status, 0); } } }