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; } } }