From 4a2ca2d94c827566f8682e8dbd6fbdf17d70b4dd Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 4 Jan 2015 23:48:53 +0100 Subject: o Adding a way to prepend callbacks on the promise's queue. Needed for devices that give you [disconnect, connect] events when connecting. Yay. o Reading meta data from the Soil Moisture device. --- .../main/java/io/trygvis/android/bt/BtPromise.java | 404 +++++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 app/src/main/java/io/trygvis/android/bt/BtPromise.java (limited to 'app/src/main/java/io/trygvis/android/bt/BtPromise.java') diff --git a/app/src/main/java/io/trygvis/android/bt/BtPromise.java b/app/src/main/java/io/trygvis/android/bt/BtPromise.java new file mode 100644 index 0000000..55b6315 --- /dev/null +++ b/app/src/main/java/io/trygvis/android/bt/BtPromise.java @@ -0,0 +1,404 @@ +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.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +import io.trygvis.android.F2; +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.onFailure; +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 Queue actionQ = new ArrayDeque<>(); + private final Queue failureQ = new ArrayDeque<>(); + private final Queue finallyQ = new ArrayDeque<>(); + + public static final PromiseResult continueDownChain = new Continue(); + public static final PromiseResult doneWithChain = new Done(); + + public abstract static class PromiseResult { + } + + private static class Continue extends PromiseResult { + } + + private static class Done extends PromiseResult { + } + + private static class Detour extends PromiseResult { + final BtPromise promise; + + private Detour(BtPromise promise) { + this.promise = promise; + } + } + + public synchronized BtPromise onConnectionStateChange(F2 callback) { + actionQ.add(new BtCallback("onConnectionStateChange") { + @Override + public PromiseResult onConnectionStateChange(BluetoothGatt gatt, int newState) { + return callback.apply(gatt, newState); + } + }); + return this; + } + + public synchronized BtPromise onServicesDiscovered(Function callback) { + actionQ.add(new BtCallback("onServicesDiscovered") { + @Override + public PromiseResult onServicesDiscovered(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + return this; + } + + public synchronized BtPromise onCharacteristicRead(F2 callback) { + actionQ.add(new BtCallback("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("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("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("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("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("onReliableWriteCompleted") { + @Override + public PromiseResult onReliableWriteCompleted(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + return this; + } + + public synchronized BtPromise onReadRemoteRssi(F2 callback) { + actionQ.add(new BtCallback("onReadRemoteRssi") { + @Override + public PromiseResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + return callback.apply(gatt, rssi); + } + }); + return this; + } + + public synchronized BtPromise onFailure(Runnable callback) { + failureQ.add(new BtCallback("onFailure") { + @Override + public void onFailure() { + callback.run(); + } + }); + return this; + } + + public synchronized BtPromise onFinally(Runnable callback) { + finallyQ.add(new BtCallback("finally") { + @Override + public void onFinally() { + callback.run(); + } + }); + 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() { + return new BtBluetoothGattCallback(actionQ, failureQ, finallyQ); + } + + public PromiseResult toDetour() { + return new Detour(this); + } + + enum EventType { + onConnectionStateChange, + onServicesDiscovered, + onCharacteristicRead, + onCharacteristicWrite, + onCharacteristicChanged, + onDescriptorRead, + onDescriptorWrite, + onReliableWriteCompleted, + + onReadRemoteRssi, + + onFailure, + onFinally, + } + + private static PromiseResult callCallback(EventType key, BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + BluetoothGattDescriptor descriptor, int newState, BtCallback btCallback) { + switch (key) { + case onConnectionStateChange: + return btCallback.onConnectionStateChange(gatt, 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 onFailure: + btCallback.onFailure(); + return null; + case onFinally: + btCallback.onFinally(); + return null; + default: + Log.w(TAG, "Unknown callback: " + key); + return null; + } + } + + static class BtBluetoothGattCallback extends BluetoothGattCallback { + + private ArrayDeque actionQ; + private ArrayDeque failureQ; + ArrayDeque finallyQ; +// private ArrayDeque initialActionQ; + private List events = new ArrayList<>(); + + private BtBluetoothGattCallback(Queue actionQ, Queue failureQ, Queue finallyQ) { + this.actionQ = new ArrayDeque<>(actionQ); + this.failureQ = new ArrayDeque<>(failureQ); + this.finallyQ = new ArrayDeque<>(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, key + "(" + values + "), success=" + success); + + if (!success) { + doFailure(); + return; + } + + BtCallback btCallback; + synchronized (this) { + if (actionQ.isEmpty()) { + Log.d(TAG, "All Bluetooth actions are done"); + + doFinally(); + return; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } + btCallback = actionQ.remove(); + Log.i(TAG, "Executing bt action: " + btCallback.name); + } + + try { + PromiseResult result = callCallback(key, gatt, characteristic, descriptor, newState, btCallback); + + if (result instanceof Done) { + Log.i(TAG, "The chain is done."); + doFinally(); + } else if (result instanceof Detour) { + Log.i(TAG, "Adding detour"); + BtPromise promise = ((Detour) result).promise; + if (!promise.failureQ.isEmpty()) { + Log.i(TAG, "Ignoring " + promise.failureQ.size() + " items from the failure Q"); + } + if (!promise.finallyQ.isEmpty()) { + Log.i(TAG, "Ignoring " + promise.finallyQ.size() + " items from the finally Q"); + } + + for (BtCallback callback : promise.actionQ) { + actionQ.addFirst(callback); + } + } + + if (actionQ.isEmpty()) { + Log.i(TAG, "The queue is empty"); + } + } catch (NotOverriddenException e) { + Log.w(TAG, "Unexpected callback by listener: " + key); + doFailure(); + } + } + + private void doFailure() { + StringBuilder msg = new StringBuilder(); + + msg.append("Expected events: \n"); + for (BtCallback cb : actionQ) { + msg.append(" ").append(cb.name).append("\n"); + } + + msg.append("Actual events: \n"); + for (String event : events) { + msg.append(" ").append(event).append("\n"); + } + + Log.w(TAG, msg.toString()); + + Log.w(TAG, "Executing " + failureQ.size() + " failure handlers"); + + for (BtCallback callback = failureQ.poll(); callback != null; callback = failureQ.poll()) { + try { + callCallback(onFailure, null, null, null, 0, callback); + } catch (NotOverriddenException e) { + return; + } + } + + doFinally(); + } + + private void doFinally() { + actionQ.clear(); + + Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers"); + + for (BtCallback callback = finallyQ.poll(); callback != null; callback = finallyQ.poll()) { + try { + callCallback(onFinally, null, null, null, 0, 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); + } + } +} -- cgit v1.2.3