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. --- 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 ++++++++ 14 files changed, 876 insertions(+) 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 (limited to 'bt') 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" + ); + } +} -- cgit v1.2.3