package io.trygvis.android.bt; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.util.Log; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Queue; import io.trygvis.android.F2; import io.trygvis.android.Function; import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicChanged; import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicRead; import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicWrite; import static io.trygvis.android.bt.BtActionExecutor.EventType.onConnectionStateChange; import static io.trygvis.android.bt.BtActionExecutor.EventType.onDescriptorRead; import static io.trygvis.android.bt.BtActionExecutor.EventType.onDescriptorWrite; import static io.trygvis.android.bt.BtActionExecutor.EventType.onFailure; import static io.trygvis.android.bt.BtActionExecutor.EventType.onFinally; import static io.trygvis.android.bt.BtActionExecutor.EventType.onReliableWriteCompleted; import static io.trygvis.android.bt.BtActionExecutor.EventType.onServicesDiscovered; public class BtActionExecutor { private final static String TAG = BtActionExecutor.class.getSimpleName(); private final Queue actionQ = new ArrayDeque<>(); private final Queue failureQ = new ArrayDeque<>(); private final Queue finallyQ = new ArrayDeque<>(); public synchronized BtActionExecutor onConnectionStateChange(F2 callback) { actionQ.add(new BtCallback("onConnectionStateChange") { @Override public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { return callback.apply(gatt, newState); } }); return this; } public synchronized BtActionExecutor onServicesDiscovered(Function callback) { actionQ.add(new BtCallback("onServicesDiscovered") { @Override public boolean onServicesDiscovered(BluetoothGatt gatt) { return callback.apply(gatt); } }); return this; } public synchronized BtActionExecutor onCharacteristicRead(F2 callback) { actionQ.add(new BtCallback("onCharacteristicRead") { @Override public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtActionExecutor onCharacteristicWrite(F2 callback) { actionQ.add(new BtCallback("onCharacteristicWrite") { @Override public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtActionExecutor onCharacteristicChanged(F2 callback) { actionQ.add(new BtCallback("onCharacteristicChanged") { @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { return callback.apply(gatt, characteristic); } }); return this; } public synchronized BtActionExecutor onDescriptorRead(F2 callback) { actionQ.add(new BtCallback("onDescriptorRead") { @Override public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { return callback.apply(gatt, descriptor); } }); return this; } public synchronized BtActionExecutor onDescriptorWrite(F2 callback) { actionQ.add(new BtCallback("onDescriptorWrite") { @Override public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { return callback.apply(gatt, descriptor); } }); return this; } public synchronized BtActionExecutor onReliableWriteCompleted(Function callback) { actionQ.add(new BtCallback("onReliableWriteCompleted") { @Override public boolean onReliableWriteCompleted(BluetoothGatt gatt) { return callback.apply(gatt); } }); return this; } public synchronized BtActionExecutor onReadRemoteRssi(F2 callback) { actionQ.add(new BtCallback("onReadRemoteRssi") { @Override public boolean onReadRemoteRssi(BluetoothGatt gatt, int rssi) { return callback.apply(gatt, rssi); } }); return this; } public synchronized BtActionExecutor onFailure(Runnable callback) { failureQ.add(new BtCallback("onFailure") { @Override public void onFailure() { callback.run(); } }); return this; } public synchronized BtActionExecutor onFinally(Runnable callback) { finallyQ.add(new BtCallback("finally") { @Override public void onFinally() { callback.run(); } }); return this; } public String toString() { StringBuilder s = new StringBuilder("Queue: "); Iterator it = actionQ.iterator(); int i = 0; while (it.hasNext()) { BtCallback c = it.next(); if (i > 0) { s.append(", "); } s.append(c.name); i++; } return s.toString(); } public BluetoothGattCallback asCallback() { return new MyBluetoothGattCallback(); } enum EventType { onConnectionStateChange, onServicesDiscovered, onCharacteristicRead, onCharacteristicWrite, onCharacteristicChanged, onDescriptorRead, onDescriptorWrite, onReliableWriteCompleted, onReadRemoteRssi, onFailure, onFinally, } private static Boolean callCallback(EventType key, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int newState, BtCallback btCallback) { switch (key) { case onConnectionStateChange: return btCallback.onConnectionStateChange(gatt, newState); case onServicesDiscovered: return btCallback.onServicesDiscovered(gatt); case onCharacteristicRead: return btCallback.onCharacteristicRead(gatt, characteristic); case onCharacteristicWrite: return btCallback.onCharacteristicWrite(gatt, characteristic); case onCharacteristicChanged: return btCallback.onCharacteristicChanged(gatt, characteristic); case onDescriptorRead: return btCallback.onDescriptorRead(gatt, descriptor); case onDescriptorWrite: return btCallback.onDescriptorWrite(gatt, descriptor); case onReliableWriteCompleted: return btCallback.onReliableWriteCompleted(gatt); case onFailure: btCallback.onFailure(); return null; case onFinally: btCallback.onFinally(); return null; default: Log.w(TAG, "Unknown callback: " + key); return null; } } private class MyBluetoothGattCallback extends BluetoothGattCallback { private Queue initialActionQ = new ArrayDeque<>(actionQ); private List events = new ArrayList<>(); 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, "Operation failed: " + key + ", " + values + ", success=" + success); if (!success) { doFailure(); return; } BtCallback btCallback; synchronized (this) { if (actionQ.isEmpty()) { Log.d(TAG, "All Bluetooth actions are done"); doFinally(); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { // ignore } btCallback = actionQ.remove(); Log.i(TAG, "Executing bt action: " + btCallback.name); } try { boolean ok = callCallback(key, gatt, characteristic, descriptor, newState, btCallback); if (!ok) { Log.w(TAG, "The callback don't want to continue."); doFinally(); } if (actionQ.isEmpty()) { Log.i(TAG, "The queue is empty"); } } catch (NotOverriddenException e) { Log.w(TAG, "Unexpected callback by listener: " + key); doFailure(); } } private void doFailure() { StringBuilder msg = new StringBuilder(); msg.append("Expected events: \n"); for (BtCallback cb : initialActionQ) { msg.append(" ").append(cb.name).append("\n"); } msg.append("Actual events: \n"); for (String event : events) { msg.append(" ").append(event).append("\n"); } Log.w(TAG, msg.toString()); Log.w(TAG, "Executing " + failureQ.size() + " failure handlers"); for (BtCallback callback = failureQ.poll(); callback != null; callback = failureQ.poll()) { try { callCallback(onFailure, null, null, null, 0, callback); } catch (NotOverriddenException e) { return; } } doFinally(); } private void doFinally() { actionQ.clear(); Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers"); for (BtCallback callback = finallyQ.poll(); callback != null; callback = finallyQ.poll()) { try { callCallback(onFinally, null, null, null, 0, callback); } catch (NotOverriddenException e) { return; } } } // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { // onEvent(onReadRemoteRssi, "status=" + status, gatt, null, null, status, 0); } } }