From 3e619a735e63a1222e71060d9e65b354a156b158 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Wed, 28 Jan 2015 23:45:38 +0100 Subject: o Major refactoring on the BtPromise, mainly internal. Renaming BtPromise to BtSequence and BtSequencer. --- app/build.gradle | 23 +- app/src/main/java/io/trygvis/android/Consumer.java | 5 - app/src/main/java/io/trygvis/android/F2.java | 5 - app/src/main/java/io/trygvis/android/F3.java | 5 - app/src/main/java/io/trygvis/android/F4.java | 5 - app/src/main/java/io/trygvis/android/Function.java | 5 - app/src/main/java/io/trygvis/android/Optional.java | 45 -- app/src/main/java/io/trygvis/android/Supplier.java | 5 - .../java/io/trygvis/android/bt/BtCallback.java | 73 --- .../main/java/io/trygvis/android/bt/BtDevice.java | 29 +- .../main/java/io/trygvis/android/bt/BtPromise.java | 519 --------------------- .../trygvis/android/bt/NotOverriddenException.java | 4 - .../soilmoisture/DefaultSoilMoistureService.java | 28 +- .../io/trygvis/soilmoisture/SensorActivity.java | 1 - bt/build.gradle | 46 ++ bt/src/main/java/io/trygvis/android/Consumer.java | 5 + bt/src/main/java/io/trygvis/android/F2.java | 5 + bt/src/main/java/io/trygvis/android/F3.java | 5 + bt/src/main/java/io/trygvis/android/F4.java | 5 + bt/src/main/java/io/trygvis/android/Function.java | 5 + bt/src/main/java/io/trygvis/android/Optional.java | 45 ++ bt/src/main/java/io/trygvis/android/Supplier.java | 5 + .../android/bt/BtBluetoothGattCallback.java | 73 +++ .../java/io/trygvis/android/bt/BtCallback.java | 74 +++ .../java/io/trygvis/android/bt/BtSequence.java | 243 ++++++++++ .../java/io/trygvis/android/bt/BtSequencer.java | 265 +++++++++++ .../trygvis/android/bt/NotOverriddenException.java | 4 + .../io/trygvis/android/bt/BtSequencerTest.java | 96 ++++ build.gradle | 4 +- settings.gradle | 2 +- trygvis-io-bt-stubs/build.gradle | 5 + .../main/java/android/bluetooth/BluetoothGatt.java | 32 ++ .../android/bluetooth/BluetoothGattCallback.java | 31 ++ .../bluetooth/BluetoothGattCharacteristic.java | 13 + .../android/bluetooth/BluetoothGattDescriptor.java | 13 + .../android/bluetooth/BluetoothGattService.java | 4 + .../src/main/java/android/util/Log.java | 20 + 37 files changed, 1046 insertions(+), 706 deletions(-) delete mode 100644 app/src/main/java/io/trygvis/android/Consumer.java delete mode 100644 app/src/main/java/io/trygvis/android/F2.java delete mode 100644 app/src/main/java/io/trygvis/android/F3.java delete mode 100644 app/src/main/java/io/trygvis/android/F4.java delete mode 100644 app/src/main/java/io/trygvis/android/Function.java delete mode 100644 app/src/main/java/io/trygvis/android/Optional.java delete mode 100644 app/src/main/java/io/trygvis/android/Supplier.java delete mode 100644 app/src/main/java/io/trygvis/android/bt/BtCallback.java delete mode 100644 app/src/main/java/io/trygvis/android/bt/BtPromise.java delete mode 100644 app/src/main/java/io/trygvis/android/bt/NotOverriddenException.java create mode 100644 bt/build.gradle create mode 100644 bt/src/main/java/io/trygvis/android/Consumer.java create mode 100644 bt/src/main/java/io/trygvis/android/F2.java create mode 100644 bt/src/main/java/io/trygvis/android/F3.java create mode 100644 bt/src/main/java/io/trygvis/android/F4.java create mode 100644 bt/src/main/java/io/trygvis/android/Function.java create mode 100644 bt/src/main/java/io/trygvis/android/Optional.java create mode 100644 bt/src/main/java/io/trygvis/android/Supplier.java create mode 100644 bt/src/main/java/io/trygvis/android/bt/BtBluetoothGattCallback.java create mode 100644 bt/src/main/java/io/trygvis/android/bt/BtCallback.java create mode 100644 bt/src/main/java/io/trygvis/android/bt/BtSequence.java create mode 100644 bt/src/main/java/io/trygvis/android/bt/BtSequencer.java create mode 100644 bt/src/main/java/io/trygvis/android/bt/NotOverriddenException.java create mode 100644 bt/src/test/java/io/trygvis/android/bt/BtSequencerTest.java create mode 100644 trygvis-io-bt-stubs/build.gradle create mode 100644 trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGatt.java create mode 100644 trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCallback.java create mode 100644 trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCharacteristic.java create mode 100644 trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattDescriptor.java create mode 100644 trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattService.java create mode 100644 trygvis-io-bt-stubs/src/main/java/android/util/Log.java diff --git a/app/build.gradle b/app/build.gradle index bea207c..0b6ba45 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +// https://github.com/JCAndKSolutions/android-unit-test + def getVersionCode = { -> try { def stdout = new ByteArrayOutputStream() @@ -56,7 +58,7 @@ android { versionCode getVersionCode() versionName getVersionName() - println("versionCode=$versionCode, versionName=$versionName"); + return println("versionCode=$versionCode, versionName=$versionName"); } signingConfigs { release { @@ -90,5 +92,22 @@ dependencies { } compile 'org.sqldroid:sqldroid:1.0.3' compile 'org.flywaydb:flyway-core:3.1' - // compile 'net.sourceforge.streamsupport:streamsupport:1.1.2' + compile project(':bt') +} + +/* +apply plugin: 'android-unit-test' + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile('com.crashlytics.sdk.android:crashlytics:2.1.0@aar') { + transitive = true; + } + compile 'org.sqldroid:sqldroid:1.0.3' + compile 'org.flywaydb:flyway-core:3.1' + + testCompile 'junit:junit:4.11' + testCompile 'org.robolectric:robolectric:2.4' + testCompile 'org.easytesting:fest-assert:1.4' } +*/ diff --git a/app/src/main/java/io/trygvis/android/Consumer.java b/app/src/main/java/io/trygvis/android/Consumer.java deleted file mode 100644 index bf8c5e5..0000000 --- a/app/src/main/java/io/trygvis/android/Consumer.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface Consumer { - void accept(T t); -} diff --git a/app/src/main/java/io/trygvis/android/F2.java b/app/src/main/java/io/trygvis/android/F2.java deleted file mode 100644 index 1f362cc..0000000 --- a/app/src/main/java/io/trygvis/android/F2.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface F2 { - C apply(A a, B b); -} diff --git a/app/src/main/java/io/trygvis/android/F3.java b/app/src/main/java/io/trygvis/android/F3.java deleted file mode 100644 index 30d3adf..0000000 --- a/app/src/main/java/io/trygvis/android/F3.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface F3 { - D apply(A a, B b, C c); -} diff --git a/app/src/main/java/io/trygvis/android/F4.java b/app/src/main/java/io/trygvis/android/F4.java deleted file mode 100644 index 5ff50f5..0000000 --- a/app/src/main/java/io/trygvis/android/F4.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface F4 { - E apply(A a, B b, C c, D d); -} diff --git a/app/src/main/java/io/trygvis/android/Function.java b/app/src/main/java/io/trygvis/android/Function.java deleted file mode 100644 index 5e3bb96..0000000 --- a/app/src/main/java/io/trygvis/android/Function.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface Function { - B apply(A a); -} diff --git a/app/src/main/java/io/trygvis/android/Optional.java b/app/src/main/java/io/trygvis/android/Optional.java deleted file mode 100644 index 616f9a7..0000000 --- a/app/src/main/java/io/trygvis/android/Optional.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.trygvis.android; - -public final class Optional { - private final T value; - - public Optional(T value) { - this.value = value; - } - - public T get() { - if (value == null) { - throw new IllegalStateException("get() on empty"); - } - - return value; - } - - public boolean isPresent() { - return value != null; - } - - public void ifPresent(Consumer consumer) { - if (value == null) { - return; - } - - consumer.accept(value); - } - - public static Optional of(T t) { - if (t == null) { - throw new IllegalArgumentException("t can't be null"); - } - - return new Optional<>(t); - } - - public static Optional empty() { - return new Optional<>(null); - } - - public static Optional ofNullable(T t) { - return t != null ? of(t) : empty(); - } -} diff --git a/app/src/main/java/io/trygvis/android/Supplier.java b/app/src/main/java/io/trygvis/android/Supplier.java deleted file mode 100644 index 222dff2..0000000 --- a/app/src/main/java/io/trygvis/android/Supplier.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.trygvis.android; - -public interface Supplier { - A get(); -} diff --git a/app/src/main/java/io/trygvis/android/bt/BtCallback.java b/app/src/main/java/io/trygvis/android/bt/BtCallback.java deleted file mode 100644 index 0f7287f..0000000 --- a/app/src/main/java/io/trygvis/android/bt/BtCallback.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.trygvis.android.bt; - -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; - -import static io.trygvis.android.bt.BtPromise.PromiseResult; - -public class BtCallback { - public final boolean stopOnFailure; - public final String name; - - public BtCallback(boolean stopOnFailure, String name) { - this.stopOnFailure = stopOnFailure; - this.name = name; - } - - public PromiseResult onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - throw new NotOverriddenException(); - } - - public PromiseResult onServicesDiscovered(BluetoothGatt gatt) { - throw new NotOverriddenException(); - } - - public PromiseResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - throw new NotOverriddenException(); - } - - public PromiseResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - throw new NotOverriddenException(); - } - - public PromiseResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - throw new NotOverriddenException(); - } - - public PromiseResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { - throw new NotOverriddenException(); - } - - public PromiseResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { - throw new NotOverriddenException(); - } - - public PromiseResult onReliableWriteCompleted(BluetoothGatt gatt) { - throw new NotOverriddenException(); - } - - public PromiseResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { - throw new NotOverriddenException(); - } - - public PromiseResult onDirect(BluetoothGatt value) { - throw new NotOverriddenException(); - } - - public void onFailure() { - throw new NotOverriddenException(); - } - - public void onFinally(boolean success) { - throw new NotOverriddenException(); - } - - @Override - public String toString() { - return "BtCallback{" + - "name='" + name + '\'' + - ", stopOnFailure=" + stopOnFailure + - '}'; - } -} diff --git a/app/src/main/java/io/trygvis/android/bt/BtDevice.java b/app/src/main/java/io/trygvis/android/bt/BtDevice.java index b90ac4f..07e4e52 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtDevice.java +++ b/app/src/main/java/io/trygvis/android/bt/BtDevice.java @@ -10,10 +10,9 @@ import android.util.Log; import java.util.Date; -import static io.trygvis.android.bt.BtPromise.BtBluetoothGattCallback; -import static io.trygvis.android.bt.BtPromise.PromiseResult.detour; -import static io.trygvis.android.bt.BtPromise.PromiseResult.fail; -import static io.trygvis.android.bt.BtPromise.PromiseResult.waitForNextEvent; +import static io.trygvis.android.bt.BtSequence.SequenceResult.detour; +import static io.trygvis.android.bt.BtSequence.SequenceResult.fail; +import static io.trygvis.android.bt.BtSequence.SequenceResult.waitForNextEvent; public class BtDevice implements Comparable { private final static String TAG = BtDevice.class.getSimpleName(); @@ -103,16 +102,16 @@ public class BtDevice implements Comparable { *

* Services will be discovered. */ - public synchronized void withConnection(BtPromise promise) { + public synchronized void withConnection(BtSequence sequence) { if (callback != null) { throw new RuntimeException("The current callback is not done."); } Log.i(TAG, "withConnection(), address=" + address + ", connected: " + (gatt != null)); - BtPromise newPromise; + BtSequence newSequence; if (gatt == null) { - newPromise = new BtPromise(). + newSequence = new BtSequence(). ignoreFailureForNext(). onConnectionStateChange((gatt, status, newState) -> { Log.i(TAG, "defaultConnectCallback: status=" + status + ", newState=" + newState); @@ -125,7 +124,7 @@ public class BtDevice implements Comparable { } else { Log.i(TAG, "Could not connect to " + address + ", trying again"); - return detour(new BtPromise().onConnectionStateChange((gatt2, status2, newState2) -> { + return detour(new BtSequence().onConnectionStateChange((gatt2, status2, newState2) -> { if (status2 == BluetoothGatt.GATT_SUCCESS && newState2 == BluetoothGatt.STATE_CONNECTED) { Log.i(TAG, "Connected to " + address + ", discovering services"); return gatt.discoverServices() ? waitForNextEvent() : fail(); @@ -139,25 +138,23 @@ public class BtDevice implements Comparable { }). onServicesDiscovered(gatt -> { Log.i(TAG, "Services discovered, has " + gatt.getServices().size() + " services"); - return detour(promise); + return detour(sequence); }); } else { - newPromise = promise; + newSequence = sequence; } - callback = newPromise. + BtSequencer sequencer = new BtSequencer(bluetoothDevice.getAddress(), newSequence. onFinally(success -> { Log.i(TAG, "Promise done, device is available again: address=" + address + ", success=" + success); callback = null; - }). - asCallback(bluetoothDevice.getAddress()); + })); + callback = new BtBluetoothGattCallback(sequencer); if (gatt == null) { gatt = bluetoothDevice.connectGatt(btService, false, wrappingCallback); } else { - callback.onEvent(BtPromise.EventType.onDirect, "", gatt, null, null, - BluetoothGatt.GATT_SUCCESS, - BluetoothGatt.STATE_CONNECTED); + sequencer.onDirect(); } } diff --git a/app/src/main/java/io/trygvis/android/bt/BtPromise.java b/app/src/main/java/io/trygvis/android/bt/BtPromise.java deleted file mode 100644 index bffdd19..0000000 --- a/app/src/main/java/io/trygvis/android/bt/BtPromise.java +++ /dev/null @@ -1,519 +0,0 @@ -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); - } - } -} diff --git a/app/src/main/java/io/trygvis/android/bt/NotOverriddenException.java b/app/src/main/java/io/trygvis/android/bt/NotOverriddenException.java deleted file mode 100644 index 0f2bb6a..0000000 --- a/app/src/main/java/io/trygvis/android/bt/NotOverriddenException.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.trygvis.android.bt; - -class NotOverriddenException extends RuntimeException { -} diff --git a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java index 3e1c93b..2cf85d9 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java +++ b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java @@ -26,15 +26,16 @@ import java.util.TreeSet; import io.trygvis.android.Function; import io.trygvis.android.LocalBinder; import io.trygvis.android.bt.BtDevice; -import io.trygvis.android.bt.BtPromise; +import io.trygvis.android.bt.BtSequence; import io.trygvis.android.bt.BtService; import io.trygvis.android.bt.DefaultBtService; import io.trygvis.bluetooth.TrygvisIoUuids; -import static io.trygvis.android.bt.BtPromise.PromiseResult.continueDirectly; -import static io.trygvis.android.bt.BtPromise.PromiseResult.detour; -import static io.trygvis.android.bt.BtPromise.PromiseResult.stop; -import static io.trygvis.android.bt.BtPromise.PromiseResult.waitForNextEvent; +import static io.trygvis.android.bt.BtSequence.SequenceResult; +import static io.trygvis.android.bt.BtSequence.SequenceResult.continueDirectly; +import static io.trygvis.android.bt.BtSequence.SequenceResult.detour; +import static io.trygvis.android.bt.BtSequence.SequenceResult.stop; +import static io.trygvis.android.bt.BtSequence.SequenceResult.waitForNextEvent; import static io.trygvis.android.bt.BtService.BtServiceListenerBroadcastReceiver; import static io.trygvis.bluetooth.TrygvisIoUuids.CLIENT_CHARACTERISTIC_CONFIG; import static io.trygvis.soilmoisture.SmDevice.GetSensorCountRes; @@ -139,8 +140,8 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS } }; - private BtPromise readAttribute(String value, byte[] req, Function handler) { - return new BtPromise(). + private BtSequence readAttribute(String value, byte[] req, Function handler) { + return new BtSequence(). onDirect(gatt -> { Log.i(TAG, "Getting attribute: " + value); @@ -158,7 +159,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS }); } - private BtPromise readSensorName(SmDevice device, int index) { + private BtSequence readSensorName(SmDevice device, int index) { byte[] req = createGetSensorNameReq((byte) index); return readAttribute("sensor name, index#" + index, req, bytes -> { @@ -179,7 +180,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS SmDevice smDevice = btDevice.getTag(); Log.i(TAG, "Probing " + address + ", name=" + btDevice.getName()); - BtPromise promise = new BtPromise(). + BtSequence sequence = new BtSequence(). onDirect(gatt -> { BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); @@ -210,7 +211,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS } }); - BtPromise btPromise = readAttribute("sensor count", createGetSensorCountReq(), bytes -> { + BtSequence btSequence = readAttribute("sensor count", createGetSensorCountReq(), bytes -> { GetSensorCountRes getSensorCountRes = parseResponse(bytes, GET_SENSOR_COUNT, GetSensorCountRes.class); int count = getSensorCountRes.count; @@ -221,9 +222,8 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS return detour(readSensorName(smDevice, 0)); }); - promise.andThen(btPromise); - btDevice.withConnection(promise); + btDevice.withConnection(sequence.andThen(btSequence)); } private void markDeviceAsNotUseful(SmDevice device) { @@ -371,7 +371,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS } void readCurrentValue(SmSensor sensor) { - BtPromise promise = new BtPromise(). + BtSequence sequence = new BtSequence(). onDirect(gatt -> { BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); @@ -402,7 +402,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS } }); - sensor.getDevice().getBtDevice().withConnection(promise); + sensor.getDevice().getBtDevice().withConnection(sequence); } // ----------------------------------------------------------------------- diff --git a/app/src/main/java/io/trygvis/soilmoisture/SensorActivity.java b/app/src/main/java/io/trygvis/soilmoisture/SensorActivity.java index 358382c..a5b68d8 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/SensorActivity.java +++ b/app/src/main/java/io/trygvis/soilmoisture/SensorActivity.java @@ -128,7 +128,6 @@ public class SensorActivity extends Activity { } - private class SensorSoilMoistureListener extends SoilMoistureListener { } } diff --git a/bt/build.gradle b/bt/build.gradle new file mode 100644 index 0000000..5beefce --- /dev/null +++ b/bt/build.gradle @@ -0,0 +1,46 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'me.tatarka:gradle-retrolambda:2.5.0' + } +} + +apply plugin: 'java' +apply plugin: 'idea' + +configurations { + provided +} + +sourceSets { + main.compileClasspath += configurations.provided + test.compileClasspath += configurations.provided + test.runtimeClasspath += configurations.provided +} + +//sourceCompatibility JavaVersion.VERSION_1_8 +//targetCompatibility JavaVersion.VERSION_1_8 +//sourceCompatibility "1.8" +//targetCompatibility "1.8" + +//compileJava { +// sourceCompatibility 1.8 +// targetCompatibility 1.8 +//} + +apply plugin: 'me.tatarka.retrolambda' + +dependencies { + provided project(':trygvis-io-bt-stubs') + testCompile 'junit:junit:4.11' + testCompile 'org.easytesting:fest-assert:1.4' +} + +idea { + module { + scopes.PROVIDED.plus += [configurations.provided] // for Gradle 2.0 + } +} diff --git a/bt/src/main/java/io/trygvis/android/Consumer.java b/bt/src/main/java/io/trygvis/android/Consumer.java new file mode 100644 index 0000000..bf8c5e5 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/Consumer.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface Consumer { + void accept(T t); +} diff --git a/bt/src/main/java/io/trygvis/android/F2.java b/bt/src/main/java/io/trygvis/android/F2.java new file mode 100644 index 0000000..1f362cc --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/F2.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface F2 { + C apply(A a, B b); +} diff --git a/bt/src/main/java/io/trygvis/android/F3.java b/bt/src/main/java/io/trygvis/android/F3.java new file mode 100644 index 0000000..30d3adf --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/F3.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface F3 { + D apply(A a, B b, C c); +} diff --git a/bt/src/main/java/io/trygvis/android/F4.java b/bt/src/main/java/io/trygvis/android/F4.java new file mode 100644 index 0000000..5ff50f5 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/F4.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface F4 { + E apply(A a, B b, C c, D d); +} diff --git a/bt/src/main/java/io/trygvis/android/Function.java b/bt/src/main/java/io/trygvis/android/Function.java new file mode 100644 index 0000000..5e3bb96 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/Function.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface Function { + B apply(A a); +} diff --git a/bt/src/main/java/io/trygvis/android/Optional.java b/bt/src/main/java/io/trygvis/android/Optional.java new file mode 100644 index 0000000..616f9a7 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/Optional.java @@ -0,0 +1,45 @@ +package io.trygvis.android; + +public final class Optional { + private final T value; + + public Optional(T value) { + this.value = value; + } + + public T get() { + if (value == null) { + throw new IllegalStateException("get() on empty"); + } + + return value; + } + + public boolean isPresent() { + return value != null; + } + + public void ifPresent(Consumer consumer) { + if (value == null) { + return; + } + + consumer.accept(value); + } + + public static Optional of(T t) { + if (t == null) { + throw new IllegalArgumentException("t can't be null"); + } + + return new Optional<>(t); + } + + public static Optional empty() { + return new Optional<>(null); + } + + public static Optional ofNullable(T t) { + return t != null ? of(t) : empty(); + } +} diff --git a/bt/src/main/java/io/trygvis/android/Supplier.java b/bt/src/main/java/io/trygvis/android/Supplier.java new file mode 100644 index 0000000..222dff2 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/Supplier.java @@ -0,0 +1,5 @@ +package io.trygvis.android; + +public interface Supplier { + A get(); +} diff --git a/bt/src/main/java/io/trygvis/android/bt/BtBluetoothGattCallback.java b/bt/src/main/java/io/trygvis/android/bt/BtBluetoothGattCallback.java new file mode 100644 index 0000000..1f4921f --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/bt/BtBluetoothGattCallback.java @@ -0,0 +1,73 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicChanged; +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicRead; +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicWrite; +import static io.trygvis.android.bt.BtSequencer.EventType.onConnectionStateChange; +import static io.trygvis.android.bt.BtSequencer.EventType.onDescriptorRead; +import static io.trygvis.android.bt.BtSequencer.EventType.onDescriptorWrite; +import static io.trygvis.android.bt.BtSequencer.EventType.onReliableWriteCompleted; +import static io.trygvis.android.bt.BtSequencer.EventType.onServicesDiscovered; + +public class BtBluetoothGattCallback extends BluetoothGattCallback { + + private final BtSequencer sequencer; + + BtBluetoothGattCallback(BtSequencer sequencer) { + this.sequencer = sequencer; + } + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + sequencer.onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState); + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + sequencer.onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9); + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + sequencer.onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + sequencer.onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + sequencer.onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); + } + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + sequencer.onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + sequencer.onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); + } + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + sequencer.onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); + } + +// @Override +// public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { +// sequencer.onEvent(onReadRemoteRssi, "status=" + status, gatt, null, null, status, 0); +// } +} \ No newline at end of file diff --git a/bt/src/main/java/io/trygvis/android/bt/BtCallback.java b/bt/src/main/java/io/trygvis/android/bt/BtCallback.java new file mode 100644 index 0000000..4aa8529 --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/bt/BtCallback.java @@ -0,0 +1,74 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import static io.trygvis.android.bt.BtSequence.SequenceResult; +import static io.trygvis.android.bt.BtSequencer.EventType; + +public class BtCallback { + public final boolean stopOnFailure; + public final EventType type; + + public BtCallback(boolean stopOnFailure, EventType type) { + this.stopOnFailure = stopOnFailure; + this.type = type; + } + + public SequenceResult onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + throw new NotOverriddenException(); + } + + public SequenceResult onServicesDiscovered(BluetoothGatt gatt) { + throw new NotOverriddenException(); + } + + public SequenceResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public SequenceResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public SequenceResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + throw new NotOverriddenException(); + } + + public SequenceResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + throw new NotOverriddenException(); + } + + public SequenceResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + throw new NotOverriddenException(); + } + + public SequenceResult onReliableWriteCompleted(BluetoothGatt gatt) { + throw new NotOverriddenException(); + } + + public SequenceResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + throw new NotOverriddenException(); + } + + public SequenceResult onDirect(BluetoothGatt value) { + throw new NotOverriddenException(); + } + + public void onFailure() { + throw new NotOverriddenException(); + } + + public void onFinally(boolean success) { + throw new NotOverriddenException(); + } + + @Override + public String toString() { + return "BtCallback{" + + "type='" + type + '\'' + + ", stopOnFailure=" + stopOnFailure + + '}'; + } +} diff --git a/bt/src/main/java/io/trygvis/android/bt/BtSequence.java b/bt/src/main/java/io/trygvis/android/bt/BtSequence.java new file mode 100644 index 0000000..be1be4c --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/bt/BtSequence.java @@ -0,0 +1,243 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import io.trygvis.android.Consumer; +import io.trygvis.android.F2; +import io.trygvis.android.F3; +import io.trygvis.android.Function; +import io.trygvis.android.Optional; + +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicChanged; +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicRead; +import static io.trygvis.android.bt.BtSequencer.EventType.onCharacteristicWrite; +import static io.trygvis.android.bt.BtSequencer.EventType.onConnectionStateChange; +import static io.trygvis.android.bt.BtSequencer.EventType.onDescriptorRead; +import static io.trygvis.android.bt.BtSequencer.EventType.onDescriptorWrite; +import static io.trygvis.android.bt.BtSequencer.EventType.onDirect; +import static io.trygvis.android.bt.BtSequencer.EventType.onFinally; +import static io.trygvis.android.bt.BtSequencer.EventType.onReadRemoteRssi; +import static io.trygvis.android.bt.BtSequencer.EventType.onReliableWriteCompleted; +import static io.trygvis.android.bt.BtSequencer.EventType.onServicesDiscovered; +import static java.util.Collections.unmodifiableList; + +public class BtSequence { + private static final SequenceResult waitForNextEvent = new WaitForNextEvent(); + private static final SequenceResult stop = new Stop(); + private static final SequenceResult fail = new Fail(); + private static final SequenceResult continueDirectly = new ContinueDirectly(); + + public static class SequenceResult { + private SequenceResult() { + } + + public static SequenceResult waitForNextEvent() { + return waitForNextEvent; + } + + public static SequenceResult continueDirectly() { + return continueDirectly; + } + + public static SequenceResult stop() { + return stop; + } + + public static SequenceResult fail() { + return fail; + } + + public static SequenceResult detour(BtSequence sequence) { + return new Detour(sequence); + } + } + + public static class WaitForNextEvent extends SequenceResult { + } + + public static class ContinueDirectly extends SequenceResult { + } + + public static class Stop extends SequenceResult { + } + + public static class Fail extends SequenceResult { + } + + public static class Detour extends SequenceResult { + final BtSequence sequence; + + private Detour(BtSequence sequence) { + this.sequence = sequence; + } + } + + private final List actionQ; + private final List finallyQ; + private final Optional next; + + private Boolean stopOnFailure = true; + + public BtSequence() { + actionQ = new ArrayList<>(); + finallyQ = new ArrayList<>(); + next = Optional.empty(); + } + + private BtSequence(List actionQ, List finallyQ, Optional next) { + this.actionQ = actionQ; + this.finallyQ = finallyQ; + this.next = next; + } + + public BtSequence andThen(BtSequence btSequence) { + return new BtSequence(actionQ, finallyQ, Optional.of(btSequence)); + } + + public ListIterator actionQ() { + return actionQ.listIterator(); + } + + public List finallyQ() { + return unmodifiableList(finallyQ); + } + + public boolean firstIsOnDirect() { + return actionQ.size() > 0 && actionQ.get(0).type == onDirect; + } + + public Optional getNext() { + return next; + } + + public String toString() { + return "actionQ=" + actionQ.size() + ", finallyQ=" + finallyQ.size() + ", has next=" + next.isPresent(); + } + + private BtSequence add(BtCallback callback) { + List actions = new ArrayList<>(this.actionQ); + actions.add(callback); + return new BtSequence(actions, finallyQ, next); + } + + private boolean stopOnFailure() { + if (stopOnFailure != null) { + boolean b = stopOnFailure; + stopOnFailure = null; + return b; + } + return false; + } + + public BtSequence ignoreFailureForNext() { + stopOnFailure = true; + return this; + } + + public BtSequence onConnectionStateChange(F3 callback) { + return add(new BtCallback(stopOnFailure(), onConnectionStateChange) { + @Override + public SequenceResult onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + return callback.apply(gatt, status, newState); + } + }); + } + + public BtSequence onServicesDiscovered(Function callback) { + return add(new BtCallback(stopOnFailure(), onServicesDiscovered) { + @Override + public SequenceResult onServicesDiscovered(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + } + + public BtSequence onCharacteristicRead(F2 callback) { + return add(new BtCallback(stopOnFailure(), onCharacteristicRead) { + @Override + public SequenceResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + } + + public BtSequence onCharacteristicWrite(F2 callback) { + return add(new BtCallback(stopOnFailure(), onCharacteristicWrite) { + @Override + public SequenceResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + } + + public BtSequence onCharacteristicChanged(F2 callback) { + return add(new BtCallback(stopOnFailure(), onCharacteristicChanged) { + @Override + public SequenceResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + } + + public BtSequence onDescriptorRead(F2 callback) { + return add(new BtCallback(stopOnFailure(), onDescriptorRead) { + @Override + public SequenceResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + return callback.apply(gatt, descriptor); + } + }); + } + + public BtSequence onDescriptorWrite(F2 callback) { + return add(new BtCallback(stopOnFailure(), onDescriptorWrite) { + @Override + public SequenceResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + return callback.apply(gatt, descriptor); + } + }); + } + + public BtSequence onReliableWriteCompleted(Function callback) { + return add(new BtCallback(stopOnFailure(), onReliableWriteCompleted) { + @Override + public SequenceResult onReliableWriteCompleted(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + } + + public BtSequence onReadRemoteRssi(F2 callback) { + return add(new BtCallback(stopOnFailure(), onReadRemoteRssi) { + @Override + public SequenceResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + return callback.apply(gatt, rssi); + } + }); + } + + public BtSequence onDirect(Function callback) { + return add(new BtCallback(stopOnFailure(), onDirect) { + @Override + public SequenceResult onDirect(BluetoothGatt value) { + return callback.apply(value); + } + }); + } + + public BtSequence onFinally(Consumer callback) { + List finallyQ = new ArrayList<>(this.finallyQ); + finallyQ.add(new BtCallback(stopOnFailure(), onFinally) { + @Override + public void onFinally(boolean success) { + callback.accept(success); + } + }); + return new BtSequence(actionQ, finallyQ, next); + } +} diff --git a/bt/src/main/java/io/trygvis/android/bt/BtSequencer.java b/bt/src/main/java/io/trygvis/android/bt/BtSequencer.java new file mode 100644 index 0000000..7eecbed --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/bt/BtSequencer.java @@ -0,0 +1,265 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.util.Log; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; + +import static io.trygvis.android.bt.BtSequence.ContinueDirectly; +import static io.trygvis.android.bt.BtSequence.Detour; +import static io.trygvis.android.bt.BtSequence.Fail; +import static io.trygvis.android.bt.BtSequence.SequenceResult; +import static io.trygvis.android.bt.BtSequence.Stop; +import static io.trygvis.android.bt.BtSequencer.EventType.onDirect; +import static io.trygvis.android.bt.BtSequencer.EventType.onFinally; + +class BtSequencer { + private final static String TAG = BtSequencer.class.getSimpleName(); + + enum EventType { + onConnectionStateChange, + onServicesDiscovered, + onCharacteristicRead, + onCharacteristicWrite, + onCharacteristicChanged, + onDescriptorRead, + onDescriptorWrite, + onReliableWriteCompleted, + + onReadRemoteRssi, + + onDirect, + onFailure, + onFinally, + } + + private final String address; + private final Deque sequences = new ArrayDeque<>(); + private final Deque> iterators = new ArrayDeque<>(); + + private Iterator callbacks; + private BtSequence sequence; + private List events = new ArrayList<>(); + + private boolean finallyDone; + + BtSequencer(String address, BtSequence sequence) { + this.address = address; + push(sequence); + } + + private void pop() { + sequence = sequences.pop(); + callbacks = iterators.pop(); + } + + private void push(BtSequence sequence) { + if (this.sequence != null) { + sequences.push(this.sequence); + iterators.push(this.callbacks); + } + + this.sequence = sequence; + this.callbacks = sequence.actionQ(); + } + + public void onDirect() { + onEvent(onDirect, "initial onDirect", null, null, null, BluetoothGatt.GATT_SUCCESS, + BluetoothGatt.STATE_CONNECTED); + } + + public synchronized void onEvent(EventType key, String values, BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, + int status, int newState) { + if (finallyDone) { + Log.w(TAG, "Got event after finally has been executed: " + key + "(" + values + ")."); + return; + } + + boolean success = status == BluetoothGatt.GATT_SUCCESS; + events.add(key + "(" + values + "), success=" + success); + + Log.i(TAG, "event: " + key + "(" + values + "), success=" + success); + + BtCallback callback; + synchronized (this) { + if (!callbacks.hasNext() && sequence.getNext().isPresent()) { + Log.i(TAG, "Switching to next sequence"); + + doFinally(success, sequence); + + sequence = sequence.getNext().get(); + callbacks = sequence.actionQ(); + } + + if (!callbacks.hasNext()) { + if (!sequences.isEmpty()) { + Log.d(TAG, "Sequence is done, continuing on previous sequence"); + doFinally(true, sequence); + pop(); + } else { + Log.d(TAG, "Sequence is done, no more sequences"); + doFinally(true); + return; + } + } + + callback = callbacks.next(); + + if (!success) { + if (callback.stopOnFailure) { + doFinally(false); + return; + } else { + Log.i(TAG, "Last status was a failure, but the callback still want it."); + } + } + } + + try { + Log.i(TAG, "Executing bt action: " + callback.type); + SequenceResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, null, + callback); + + if (result instanceof Stop) { + Log.i(TAG, "The sequence stopped."); + doFinally(true); + return; + } + + if (result instanceof Fail) { + Log.i(TAG, "The sequence failed."); + doFinally(false); + return; + } + + if (result instanceof Detour) { + BtSequence detour = ((Detour) result).sequence; + Log.i(TAG, "Adding detour: " + detour); + + if (!detour.firstIsOnDirect()) { + throw new IllegalArgumentException("The first handler in a detour must be an onDirect."); + } + + events.add("detour, " + detour); + + push(detour); + onEvent(onDirect, "direct", gatt, null, null, BluetoothGatt.GATT_SUCCESS, 0); + return; + } + + if (result instanceof ContinueDirectly) { + onEvent(onDirect, "direct", gatt, null, null, BluetoothGatt.GATT_SUCCESS, 0); + return; + } + + if (!callbacks.hasNext()) { + if (!sequences.isEmpty()) { + Log.d(TAG, "Sequence is done, continuing on previous sequence"); + doFinally(true, sequence); + pop(); + } else { + Log.d(TAG, "Sequence is done, no more sequences"); + doFinally(true); + } + } + } 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 void doFinally(boolean success) { + finallyDone = true; + showEvents(); + + doFinally(success, sequence); + + while (!sequences.isEmpty()) { + doFinally(success, sequences.pop()); + } + } + + private void doFinally(boolean success, BtSequence s) { + List q = s.finallyQ(); + + Log.w(TAG, "Executing " + q.size() + " finally handlers, success=" + success); + for (BtCallback callback : q) { + try { + callCallback(onFinally, null, null, null, 0, 0, success, callback); + } catch (NotOverriddenException e) { + // ignore + } + } + } + + 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("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 static SequenceResult 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; + } + } +} diff --git a/bt/src/main/java/io/trygvis/android/bt/NotOverriddenException.java b/bt/src/main/java/io/trygvis/android/bt/NotOverriddenException.java new file mode 100644 index 0000000..0f2bb6a --- /dev/null +++ b/bt/src/main/java/io/trygvis/android/bt/NotOverriddenException.java @@ -0,0 +1,4 @@ +package io.trygvis.android.bt; + +class NotOverriddenException extends RuntimeException { +} diff --git a/bt/src/test/java/io/trygvis/android/bt/BtSequencerTest.java b/bt/src/test/java/io/trygvis/android/bt/BtSequencerTest.java new file mode 100644 index 0000000..862eda5 --- /dev/null +++ b/bt/src/test/java/io/trygvis/android/bt/BtSequencerTest.java @@ -0,0 +1,96 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.List; + +import static io.trygvis.android.bt.BtSequence.SequenceResult.continueDirectly; +import static io.trygvis.android.bt.BtSequence.SequenceResult.detour; +import static io.trygvis.android.bt.BtSequence.SequenceResult.stop; +import static org.fest.assertions.Assertions.assertThat; + +@RunWith(JUnit4.class) +public class BtSequencerTest { + + public static final String ADDRESS = "AA:BB:CC:DD:EE:FF"; + + @Test + public void eventAndFinally() { + List events = new ArrayList<>(); + + BtSequence seq = new BtSequence().onConnectionStateChange((gatt, newState, status) -> { + events.add("onConnectionStateChange"); + return stop(); + }).onFinally(success -> events.add("finally: " + success)); + + BtSequencer sequencer = new BtSequencer(ADDRESS, seq); + + sequencer.onEvent(BtSequencer.EventType.onConnectionStateChange, null, null, null, null, + BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED); + + assertThat(events).containsSequence( + "onConnectionStateChange", + "finally: true" + ); + } + + @Test + public void detours() { + List events = new ArrayList<>(); + + BtSequence tour = new BtSequence().onDirect(gatt -> { + events.add("tour: onDirect"); + return stop(); + }).onFinally(success -> events.add("tour: finally: " + success)); + + BtSequence seq = new BtSequence().onConnectionStateChange((gatt, newState, status) -> { + events.add("onConnectionStateChange"); + return detour(tour.andThen(tour)); + }).onFinally(success -> events.add("finally: " + success)); + + BtSequencer sequencer = new BtSequencer(ADDRESS, seq); + + sequencer.onEvent(BtSequencer.EventType.onConnectionStateChange, null, null, null, null, + BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED); + + assertThat(events).containsSequence( + "onConnectionStateChange", + "tour: onDirect", + "tour: finally: true", + "finally: true" + ); + } + + @Test + public void andThen() { + List events = new ArrayList<>(); + + BtSequence a = new BtSequence().onConnectionStateChange((gatt, newState, status) -> { + events.add("a"); + return continueDirectly(); + }).onFinally(success -> events.add("a: finally: " + success)); + BtSequence b = new BtSequence().onDirect(gatt -> { + events.add("b"); + return stop(); + }).onFinally(success -> events.add("b: finally: " + success)); + + BtSequence seq = a.andThen(b); + + BtSequencer sequencer = new BtSequencer(ADDRESS, seq); + + sequencer.onEvent(BtSequencer.EventType.onConnectionStateChange, null, null, null, null, + BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED); + + assertThat(events).containsSequence( + "a", + "a: finally: true", + "b", + "b: finally: true" + ); + } +} diff --git a/build.gradle b/build.gradle index 234dff0..fae46cd 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,9 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.0.0-rc4' + classpath 'com.android.tools.build:gradle:1.0.1' +// classpath 'org.robolectric:robolectric-gradle-plugin:0.14.1' + classpath 'com.github.jcandksolutions.gradle:android-unit-test:2.1.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/settings.gradle b/settings.gradle index e7b4def..1d1e858 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app', ':bt', ':trygvis-io-bt-stubs' diff --git a/trygvis-io-bt-stubs/build.gradle b/trygvis-io-bt-stubs/build.gradle new file mode 100644 index 0000000..c152b19 --- /dev/null +++ b/trygvis-io-bt-stubs/build.gradle @@ -0,0 +1,5 @@ +apply plugin: 'java' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} \ No newline at end of file diff --git a/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGatt.java b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGatt.java new file mode 100644 index 0000000..13b33ed --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGatt.java @@ -0,0 +1,32 @@ +package android.bluetooth; + +import java.util.List; +import java.util.UUID; + +public class BluetoothGatt { + public static final int GATT_SUCCESS = 1; + public static final int STATE_CONNECTED = 2; + + public boolean discoverServices() { + return false; + } + + public List getServices() { + return null; + } + + public BluetoothGattService getService(UUID uuid) { + return null; + } + + public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { + return false; + } + + public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean b) { + } + + public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { + return false; + } +} diff --git a/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCallback.java b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCallback.java new file mode 100644 index 0000000..a2a2943 --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCallback.java @@ -0,0 +1,31 @@ +package android.bluetooth; + +public class BluetoothGattCallback { + + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + } + + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + } + + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + } + + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + } + + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + } + + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + } + + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + } + + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + } + + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + } +} diff --git a/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCharacteristic.java b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCharacteristic.java new file mode 100644 index 0000000..8398b5a --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -0,0 +1,13 @@ +package android.bluetooth; + +import java.util.UUID; + +public class BluetoothGattCharacteristic { + public UUID getUuid() { + return null; + } + + public byte[] getValue() { + return null; + } +} diff --git a/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattDescriptor.java b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattDescriptor.java new file mode 100644 index 0000000..0a86c7e --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattDescriptor.java @@ -0,0 +1,13 @@ +package android.bluetooth; + +import java.util.UUID; + +public class BluetoothGattDescriptor { + public UUID getUuid() { + return null; + } + + public BluetoothGattCharacteristic getCharacteristic() { + return null; + } +} diff --git a/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattService.java b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattService.java new file mode 100644 index 0000000..cd2a57f --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/bluetooth/BluetoothGattService.java @@ -0,0 +1,4 @@ +package android.bluetooth; + +public class BluetoothGattService { +} diff --git a/trygvis-io-bt-stubs/src/main/java/android/util/Log.java b/trygvis-io-bt-stubs/src/main/java/android/util/Log.java new file mode 100644 index 0000000..036ce22 --- /dev/null +++ b/trygvis-io-bt-stubs/src/main/java/android/util/Log.java @@ -0,0 +1,20 @@ +package android.util; + +public class Log { + public static void d(String tag, String s) { + System.out.println("d: " + tag + ": " + s); + } + + public static void i(String tag, String s) { + System.out.println("i: " + tag + ": " + s); + } + + public static void w(String tag, String s) { + System.out.println("w: " + tag + ": " + s); + } + + public static void w(String tag, String s, Exception e) { + System.out.println("w: " + tag + ": " + s); + e.printStackTrace(System.out); + } +} -- cgit v1.2.3