diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 60c8217..7c47f96 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,13 @@
+
+
+
+
+
+
+
mBleOperations = new ArrayList<>();
+ private BluetoothGatt mBtGatt;
+ private boolean mConnectionReady = false;
+ private boolean mBleOperationInProgress = false;
+ private BehaviorSubject> mBluetoothOperationIdPS =
+ BehaviorSubject.create();
+ private BehaviorSubject mBluetoothOperationResultsPS =
+ BehaviorSubject.create();
+ private BehaviorSubject mBluetoothDeviceStatusPS = BehaviorSubject.create();
+ private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "New CharacteristicWrite");
+
+ // Result of a characteristic write operation
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mBluetoothOperationResultsPS.onNext(characteristic);
+ // TODO Handler Errors too (Maybe Object with all the Info ?)
+ }
+
+ performNextOperation();
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "New CharacteristicRead");
+
+ // Result of a characteristic read operation
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mBluetoothOperationResultsPS.onNext(characteristic);
+ // TODO Handler Errors too (Maybe Object with all the Info ?)
+ }
+
+ performNextOperation();
+ }
+
+ @Override
+ public void onDescriptorWrite(BluetoothGatt gatt,
+ BluetoothGattDescriptor descriptor,
+ int status) {
+ Log.d(TAG, "New DescriptorWrite");
+ performNextOperation();
+ }
+
+ @Override
+ public void onDescriptorRead(BluetoothGatt gatt,
+ BluetoothGattDescriptor descriptor,
+ int status) {
+ Log.d(TAG, "New DescriptorRead");
+ performNextOperation();
+ }
+
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt,
+ int status,
+ int newState) {
+ Log.d(TAG, "onConnectionStateChange");
+ switch (newState) {
+ case STATE_DISCONNECTED:
+ Log.d(TAG, "onConnectionStateChange STATE_DISCONNECTED");
+ mBtGatt = null;
+ mConnectionReady = false;
+ mBleOperationInProgress = false;
+ mBluetoothDeviceStatusPS.onNext(BluetoothDeviceState.DISCONNECTED);
+ break;
+ case STATE_CONNECTED:
+ Log.d(TAG, "onConnectionStateChange STATE_CONNECTED");
+ mBtGatt.discoverServices();
+ mBluetoothDeviceStatusPS.onNext(BluetoothDeviceState.CONNECTED);
+ break;
+ default:
+ Log.d(TAG, "onConnectionStateChange OTHER");
+ break;
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt,
+ int status) {
+ Log.d(TAG, "onServicesDiscovered");
+ if (status == GATT_SUCCESS) {
+ mConnectionReady = true;
+ onBTServicesDiscovered();
+ performNextOperation();
+ }
+ }
+ };
+
+ @SuppressLint("CheckResult")
+ public BluetoothDeviceManager(BluetoothManager bluetoothManager, Context context,
+ String deviceMacAddress) {
+ mContext = context;
+ mDeviceMacAddress = deviceMacAddress;
+ mBluetoothManager = bluetoothManager;
+
+ mBluetoothManager.observeBluetoothStatus()
+ .compose(RxSchedulersUtils.observeObservableOnIO())
+ .subscribe(status -> {
+ if (status == BluetoothState.BLUETOOTH_OFF) {
+ clearStates();
+ Log.d(TAG, "Bluetooth Status: " + String.valueOf(status));
+ }
+ }, error -> {
+ });
+ }
+
+ public Observable observeDeviceState() {
+ return mBluetoothDeviceStatusPS;
+ }
+
+ public void dump() {
+ BluetoothDevice bluetoothDevice = getPairedDevice(mDeviceMacAddress);
+
+ if (bluetoothDevice == null) {
+ return; // Nothing
+ }
+
+ BluetoothUtils.dumpBluetoothDevice(bluetoothDevice);
+ }
+
+ private BluetoothDevice getPairedDevice(String mac) {
+ for (BluetoothDevice bluetoothDevice : mBluetoothManager.getPairedDevices().blockingGet()) {
+ if (bluetoothDevice.getAddress().equals(mac)) {
+ return bluetoothDevice;
+ }
+ }
+
+ return null;
+ }
+
+ protected Completable write(UUID service, UUID charUuid, byte[] data, boolean highPriority) {
+ return checkConnection()
+ .andThen(Completable.fromAction(() -> {
+ addOperationToQueue(
+ BleOperation.newWriteOperation(service, charUuid, data, highPriority));
+ checkQueue();
+ }))
+ .subscribeOn(BLUETOOTH_SINGLE_SCHEDULER);
+ }
+
+ private void doWrite(BleOperation bleOperation) {
+ BluetoothGattService gattService = mBtGatt.getService(bleOperation.getService());
+ BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(
+ bleOperation.getCharUuid());
+ characteristic.setValue(bleOperation.getData());
+
+ if (mBtGatt.writeCharacteristic(characteristic)) {
+ Log.d(TAG, "Performing write");
+ } else {
+ Log.d(TAG, "Couldn't perform write!");
+ }
+ }
+
+ protected Single read(UUID service, UUID charUuid) {
+ return read(service, charUuid, false);
+ }
+
+ protected Single read(UUID service, UUID charUuid,
+ boolean highPriority) {
+ //TODO Replicate to Write Flow
+ BleOperation bleOperation = BleOperation.newReadOperation(service, charUuid, highPriority);
+
+ return checkConnection()
+ .andThen(Completable.fromAction(() -> {
+ addOperationToQueue(bleOperation);
+ checkQueue();
+ }))
+ .andThen(mBluetoothOperationIdPS
+ .filter(operationIds -> operationIds.first == bleOperation.getId())
+ .firstOrError()
+ .flatMap(operationIds -> mBluetoothOperationResultsPS
+ .filter(bluetoothGattCharacteristic ->
+ bluetoothGattCharacteristic.getInstanceId()
+ == operationIds.second)
+ .doOnNext(resultPepe -> Log.i(TAG,
+ "New Read Result: " + resultPepe.toString()))
+ .firstOrError()))
+ .subscribeOn(BLUETOOTH_SINGLE_SCHEDULER);
+ }
+
+ private void doRead(BleOperation bleOperation) {
+ BluetoothGattService gattService = mBtGatt.getService(bleOperation.getService());
+ BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(
+ bleOperation.getCharUuid());
+
+ //TODO Replicate to Write Flow
+ mBluetoothOperationIdPS.onNext(
+ new Pair<>(bleOperation.getId(), characteristic.getInstanceId()));
+
+ if (mBtGatt.readCharacteristic(characteristic)) {
+ Log.d(TAG, "Performing read");
+ } else {
+ Log.d(TAG, "Couldn't perform read!");
+ }
+ }
+
+ protected Completable subscribe(UUID service, UUID charUuid, boolean enable,
+ boolean highPriority) {
+ return checkConnection()
+ .andThen(Completable.fromAction(() -> {
+ addOperationToQueue(
+ BleOperation.newSubscribeOperation(service, charUuid, enable,
+ highPriority));
+ checkQueue();
+ }))
+ .subscribeOn(BLUETOOTH_SINGLE_SCHEDULER);
+ }
+
+ private void doSubscribe(BleOperation bleOperation) {
+ Log.d(TAG, "doSubscribe");
+ BluetoothGattService gattService = mBtGatt.getService(bleOperation.getService());
+ BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(
+ bleOperation.getCharUuid());
+ if (mBtGatt.setCharacteristicNotification(characteristic, bleOperation.isEnable())) {
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); //TODO Check this
+ if (descriptor != null) {
+ byte[] value =
+ bleOperation.isEnable() ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
+ : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
+ descriptor.setValue(value);
+
+ if (mBtGatt.writeDescriptor(descriptor)) {
+ Log.d(TAG, "Performing subscription");
+ } else {
+ Log.d(TAG, "Couldn't perform subscription!");
+ }
+ }
+ }
+ }
+
+ private Completable checkConnection() {
+ return mBluetoothManager.turnOn()
+ .doOnError(error -> Log.d(TAG, "Error Turning ON BT"))
+ .andThen(Completable.fromAction(this::startConnection));
+ }
+
+ private synchronized void startConnection()
+ throws BleException.BluetoothDeviceUnpairedException {
+ if (mBtGatt == null) {
+ BluetoothDevice bluetoothDevice = getPairedDevice(mDeviceMacAddress);
+ if (bluetoothDevice == null) { // Device got unpaired
+ Log.d(TAG, "Device got unpaired");
+ throw new BleException.BluetoothDeviceUnpairedException();
+ }
+
+ mBtGatt = bluetoothDevice.connectGatt(mContext, false, mBluetoothGattCallback);
+ }
+ }
+
+ protected void onBTServicesDiscovered() {
+ // Do something ?
+ }
+
+ private synchronized void addOperationToQueue(BleOperation bleOperation) {
+ int position = 0;
+
+ for (int i = 0; i < mBleOperations.size(); i++) {
+ if (!mBleOperations.get(0).isHighPriority()) { // Keep High Priority Operations at top
+ break;
+ }
+
+ position++;
+ }
+
+ mBleOperations.add(position, bleOperation);
+ }
+
+ private synchronized void checkQueue() {
+ if (!mBleOperationInProgress) {
+ performNextOperation();
+ }
+ }
+
+ @SuppressLint("CheckResult")
+ private synchronized void performNextOperation() {
+ if (!mConnectionReady) {
+ return; // Connection Not Ready
+ }
+
+ if (mBleOperations.size() == 0) {
+ mBleOperationInProgress = false;
+ return;
+ }
+
+ mBleOperationInProgress = true;
+
+ Completable.fromAction(() -> performOperation(mBleOperations.remove(0)))
+ .subscribeOn(BLUETOOTH_SINGLE_SCHEDULER)
+ .subscribe(
+ () -> Log.d(TAG, "Performing BT Operation"),
+ error -> Log.w(TAG, "Error performing BT Operation")
+ );
+ }
+
+ private synchronized void performOperation(BleOperation bleOperation) {
+ switch (bleOperation.getType()) {
+ case TYPE_READ:
+ doRead(bleOperation);
+ break;
+ case TYPE_WRITE:
+ doWrite(bleOperation);
+ break;
+ case TYPE_SUBSCRIBE:
+ doSubscribe(bleOperation);
+ break;
+ }
+ }
+
+ protected void clearStates() {
+ if (mBtGatt != null) {
+ mBtGatt.close();
+ mBtGatt = null;
+ }
+
+ mBleOperations = new ArrayList<>();
+ mConnectionReady = false;
+ mBleOperationInProgress = false;
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothDeviceState.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothDeviceState.java
new file mode 100644
index 0000000..b5f9b85
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothDeviceState.java
@@ -0,0 +1,19 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothDeviceState
+ .CONNECTED;
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothDeviceState
+ .DISCONNECTED;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+@Retention(SOURCE)
+@IntDef({CONNECTED, DISCONNECTED})
+public @interface BluetoothDeviceState {
+ int CONNECTED = 0;
+ int DISCONNECTED = 1;
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManager.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManager.java
new file mode 100644
index 0000000..9517f11
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManager.java
@@ -0,0 +1,50 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.List;
+
+import io.reactivex.Completable;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+
+/**
+ * BLE Generic Operations:
+ * - Turn On / Off
+ * - Discover Devices
+ * - Pairing / Unpairing
+ *
+ * Once you got your device paired you must use a BluetoothDeviceManager Instance to perform I/O
+ * events.
+ */
+public interface BluetoothManager {
+ // Status
+ Single getBluetoothStatus();
+
+ Observable observeBluetoothStatus();
+
+ // Turn On / Off
+ Completable turnOn();
+
+ Completable turnOff();
+
+ // Devices
+ Single> getPairedDevices();
+
+ // Discovery
+ Completable startDiscovery();
+
+ Completable stopDiscovery();
+
+ Observable observeBluetoothDiscovery();
+
+ // Actions
+ Completable pairDevice(BluetoothDevice bluetoothDevice);
+
+ void unpair(String deviceMacAddress);
+
+ Completable discoverAndPairDevice(String deviceMacAddress);
+
+ // Stop / Clear states
+ void stop();
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManagerImpl.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManagerImpl.java
new file mode 100644
index 0000000..a0195c1
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothManagerImpl.java
@@ -0,0 +1,322 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import co.lateralview.myapp.infraestructure.manager.bluetooth.base.exceptions.BleException;
+import io.reactivex.Completable;
+import io.reactivex.Observable;
+import io.reactivex.Single;
+import io.reactivex.subjects.BehaviorSubject;
+import io.reactivex.subjects.PublishSubject;
+
+public class BluetoothManagerImpl implements BluetoothManager {
+ private static final String TAG = "BluetoothManagerImpl";
+
+ // Forever PS
+ private PublishSubject mBluetoothStatusPS = PublishSubject.create();
+ private final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ switch (state) {
+ case BluetoothAdapter.STATE_OFF:
+ mBluetoothStatusPS.onNext(BluetoothState.BLUETOOTH_OFF);
+ break;
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ break;
+ case BluetoothAdapter.STATE_ON:
+ mBluetoothStatusPS.onNext(BluetoothState.BLUETOOTH_ON);
+ break;
+ case BluetoothAdapter.STATE_TURNING_ON:
+ break;
+ }
+ }
+ }
+ };
+ private PublishSubject mBluetoothDeviceDiscoveryPS =
+ PublishSubject.create();
+ private final BroadcastReceiver mBTDiscoveryReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // When discovery finds a device
+ if (BluetoothDevice.ACTION_FOUND.equals(action)) {
+ // Get the BluetoothDevice object from the Intent
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ mBluetoothDeviceDiscoveryPS.onNext(device);
+ }
+ }
+ };
+ // One Time PS
+ private BehaviorSubject mBluetoothDevicePairingPS;
+ private final BroadcastReceiver mBTPairingReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // When discovery finds a device
+ if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
+
+ int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+
+ if (state == BluetoothDevice.BOND_BONDING) {
+ //bonding process is still working
+ //essentially this means that the Confirmation Dialog is still visible
+ return; // Do Nothing
+ }
+
+ if (state == BluetoothDevice.BOND_BONDED) {
+ //bonding process was successful
+ //also means that the user pressed OK on the Dialog
+
+ // Get the BluetoothDevice object from the Intent
+ BluetoothDevice device = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE);
+
+ mBluetoothDevicePairingPS.onNext(device);
+
+ return;
+ }
+
+ // Paired device not found means error/cancel during pairing process
+ mBluetoothDevicePairingPS.onError(new BleException.PairingToBTDeviceException());
+ }
+ }
+ };
+
+ // ======== Start Bluetooth Status Related ========
+
+ private Context mContext;
+ private boolean mDiscoveringReceiverRegistered = false;
+ private boolean mPairingReceiverRegistered = false;
+
+ public BluetoothManagerImpl(Context context) {
+ mContext = context;
+ registerToBluetoothChanges();
+ }
+
+ @Override
+ public Single getBluetoothStatus() {
+ return Single.fromCallable(() -> {
+ BluetoothAdapter mBluetoothAdapter = getBluetoothAdapter();
+ if (mBluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ throw new BleException.BluetoothNotSupportedException();
+ }
+
+ return mBluetoothAdapter.isEnabled() ? BluetoothState.BLUETOOTH_ON
+ : BluetoothState.BLUETOOTH_OFF;
+ });
+ }
+
+ @Override
+ public Observable observeBluetoothStatus() {
+ return mBluetoothStatusPS;
+ }
+
+ @Override
+ public Completable turnOn() {
+ return Single.fromCallable(() -> {
+ BluetoothAdapter mBluetoothAdapter = getBluetoothAdapter();
+ if (mBluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ throw new BleException.BluetoothNotSupportedException();
+ }
+
+ if (!mBluetoothAdapter.isEnabled()) {
+ mBluetoothAdapter.enable(); // Works only with ADMIN BLUETOOTH permission.
+ return true; // Wait for connection, turn on can take a while.
+ }
+
+ return false;
+ }
+ ).flatMapCompletable(waitForConnection -> {
+ if (waitForConnection) {
+ return mBluetoothStatusPS
+ .filter(status -> status == BluetoothState.BLUETOOTH_ON)
+ .firstOrError()
+ .ignoreElement();
+ } else {
+ return Completable.complete();
+ }
+ });
+ }
+
+ // ======== End Bluetooth Status Related ========
+
+ @Override
+ public Completable turnOff() {
+ return Completable.fromAction(() -> {
+ BluetoothAdapter mBluetoothAdapter = getBluetoothAdapter();
+ if (mBluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ throw new BleException.BluetoothNotSupportedException();
+ }
+
+ if (mBluetoothAdapter.isEnabled()) {
+ mBluetoothAdapter.disable();
+ }
+ }
+ );
+ }
+
+ // ======== Start Pairing Related ========
+
+ @Override
+ public Single> getPairedDevices() {
+ return Single.fromCallable(() -> new ArrayList<>(getBluetoothPairedDevices()));
+ }
+
+ @Override
+ public Completable pairDevice(BluetoothDevice bluetoothDevice) {
+ // Check if device is already paired
+ if (getPairedDevice(bluetoothDevice.getAddress()) != null) {
+ return Completable.complete();
+ }
+
+ mBluetoothDevicePairingPS = BehaviorSubject.create();
+
+ return mBluetoothDevicePairingPS
+ .firstOrError()
+ // Re-Check address just for case.
+ .filter(pairedBluetoothDevice -> pairedBluetoothDevice.getAddress()
+ .equals(bluetoothDevice.getAddress()))
+ .flatMapCompletable(__ -> Completable.complete())
+ .doOnSubscribe(__ -> {
+ registerToBTPairingReceiver();
+ bluetoothDevice.createBond();
+ });
+ }
+
+ @Override
+ public Completable startDiscovery() {
+ return Completable.fromAction(() -> {
+ if (!getBluetoothAdapter().isDiscovering()) {
+ getBluetoothAdapter().startDiscovery();
+ }
+
+ registerToBTDiscoveryReceiver();
+ });
+ }
+
+ @Override
+ public Completable stopDiscovery() {
+ return Completable.fromAction(() -> {
+ unregisterToBTDiscoveryReceiver();
+ getBluetoothAdapter().cancelDiscovery();
+ });
+ }
+
+ // ======== End Pairing Related ========
+
+ // ======== Start Discovery Related ========
+
+ @Override
+ public Observable observeBluetoothDiscovery() {
+ return mBluetoothDeviceDiscoveryPS;
+ }
+
+ @Override
+ public void unpair(String deviceMacAddress) {
+ for (BluetoothDevice bluetoothDevice : getBluetoothPairedDevices()) {
+ if (bluetoothDevice.getAddress().equals(deviceMacAddress)) {
+ try {
+ Method m = bluetoothDevice.getClass()
+ .getMethod("removeBond", (Class[]) null);
+ m.invoke(bluetoothDevice, (Object[]) null);
+ } catch (Exception e) {
+ Log.d(TAG, "Removing has been failed.", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Completable discoverAndPairDevice(String deviceMacAddress) {
+ return stopDiscovery()
+ .andThen(startDiscovery())
+ .andThen(
+ observeBluetoothDiscovery()
+ .filter(bluetoothDevice -> bluetoothDevice.getAddress().equals(
+ deviceMacAddress))
+ .firstOrError()
+ .flatMapCompletable(this::pairDevice)
+ .doAfterTerminate(this::stopDiscovery));
+ }
+
+ @Override
+ public void stop() {
+ getBluetoothAdapter().cancelDiscovery();
+ unregisterToBTDiscoveryReceiver();
+ unregisterToBTPairingReceiver();
+ }
+
+ private void registerToBluetoothChanges() {
+ IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ mContext.registerReceiver(mBluetoothStatusReceiver, filter);
+ }
+
+ private void registerToBTPairingReceiver() {
+ if (!mPairingReceiverRegistered) {
+ IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ mContext.registerReceiver(mBTPairingReceiver, filter);
+ mPairingReceiverRegistered = true;
+ }
+ }
+
+ // ======== End Discovery Related ========
+
+ private void unregisterToBTPairingReceiver() {
+ if (mPairingReceiverRegistered) {
+ mContext.unregisterReceiver(mBTPairingReceiver);
+ mPairingReceiverRegistered = false;
+ }
+ }
+
+ private void registerToBTDiscoveryReceiver() {
+ IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
+ mContext.registerReceiver(mBTDiscoveryReceiver, filter);
+ mDiscoveringReceiverRegistered = true;
+ }
+
+ private void unregisterToBTDiscoveryReceiver() {
+ if (mDiscoveringReceiverRegistered) {
+ mContext.unregisterReceiver(mBTDiscoveryReceiver);
+ mDiscoveringReceiverRegistered = false;
+ }
+ }
+
+ private Set getBluetoothPairedDevices() {
+ return getBluetoothAdapter().getBondedDevices();
+ }
+
+ protected BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
+
+ private boolean isDevicePaired(String mac) {
+ return getPairedDevice(mac) != null;
+ }
+
+ private BluetoothDevice getPairedDevice(String mac) {
+ for (BluetoothDevice bluetoothDevice : getBluetoothPairedDevices()) {
+ if (bluetoothDevice.getAddress().equals(mac)) {
+ return bluetoothDevice;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothState.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothState.java
new file mode 100644
index 0000000..bed32ca
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothState.java
@@ -0,0 +1,19 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothState
+ .BLUETOOTH_OFF;
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothState
+ .BLUETOOTH_ON;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+@Retention(SOURCE)
+@IntDef({BLUETOOTH_ON, BLUETOOTH_OFF})
+public @interface BluetoothState {
+ int BLUETOOTH_ON = 0;
+ int BLUETOOTH_OFF = 1;
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothUtils.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothUtils.java
new file mode 100644
index 0000000..b8e3061
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/BluetoothUtils.java
@@ -0,0 +1,49 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+public final class BluetoothUtils {
+ public static final String TAG = "BluetoothUtils";
+ private static final String LOG_SEPARATOR = "===========================================";
+ private static final String LOG_NEW_LINE = "\n";
+ private static final String LOG_ITEM_SEPARATOR = "----------";
+
+ private BluetoothUtils() {
+
+ }
+
+ public static void dumpBluetoothDevice(BluetoothDevice bluetoothDevice) {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ stringBuilder.append(LOG_SEPARATOR);
+ stringBuilder.append("Dumping Bluetooth Device Data");
+ stringBuilder.append(LOG_NEW_LINE);
+ stringBuilder.append("Mac Address: " + bluetoothDevice.getAddress());
+ stringBuilder.append(LOG_NEW_LINE);
+ stringBuilder.append("Name: " + bluetoothDevice.getName());
+ stringBuilder.append(LOG_NEW_LINE);
+ stringBuilder.append(LOG_ITEM_SEPARATOR);
+ stringBuilder.append(LOG_NEW_LINE);
+
+ stringBuilder.append("List Of Services: ");
+ stringBuilder.append(LOG_NEW_LINE);
+ stringBuilder.append(LOG_NEW_LINE);
+
+ ParcelUuid[] uuids = bluetoothDevice.getUuids();
+
+ if (uuids != null) {
+ for (ParcelUuid parcelUuid : bluetoothDevice.getUuids()) {
+ stringBuilder.append("Service: " + parcelUuid.getUuid().toString());
+ stringBuilder.append(LOG_NEW_LINE);
+ }
+ }
+
+ stringBuilder.append(LOG_NEW_LINE);
+ stringBuilder.append(LOG_SEPARATOR);
+ stringBuilder.append(LOG_NEW_LINE);
+
+ Log.d(TAG, stringBuilder.toString());
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/exceptions/BleException.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/exceptions/BleException.java
new file mode 100644
index 0000000..90ea0d5
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/exceptions/BleException.java
@@ -0,0 +1,39 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base.exceptions;
+
+public class BleException extends Exception {
+
+ public BleException(String message) {
+ super(message);
+ }
+
+ public static class BluetoothDeviceUnpairedException extends BleException {
+ public BluetoothDeviceUnpairedException() {
+ super("The requested device got unpaired.");
+ }
+ }
+
+ public static class BluetoothNotEnabledException extends BleException {
+ public BluetoothNotEnabledException() {
+ super("Bluetooth is Off.");
+ }
+ }
+
+ public static class BluetoothAccessNotGrantedException extends BleException {
+ public BluetoothAccessNotGrantedException() {
+ super("Bluetooth access not granted.");
+ }
+ }
+
+ public static class BluetoothNotSupportedException extends BleException {
+ public BluetoothNotSupportedException() {
+ super("The current device doesn't support Bluetooth.");
+ }
+ }
+
+ public static class PairingToBTDeviceException extends BleException {
+ public PairingToBTDeviceException() {
+ super("Couldn't pair to the selected Bluetooth device. Check if it is in pairing "
+ + "mode and in the appropriate range.");
+ }
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperation.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperation.java
new file mode 100644
index 0000000..bfb78e2
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperation.java
@@ -0,0 +1,82 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base.model;
+
+import java.util.UUID;
+
+public final class BleOperation {
+ private static int sTemporaryId = 0;
+
+ /**
+ * Identify temporarily the BLE Operation until we get the final Characteristic Instance ID.
+ */
+ private int id;
+
+ private UUID service;
+ private UUID charUuid;
+
+ // Only used for characteristic write
+ private byte[] data;
+
+ // Only used for characteristic notification subscription
+ private boolean enable;
+
+ @BleOperationType
+ private int type;
+
+ private boolean highPriority = false;
+
+ private BleOperation(UUID service, UUID charUuid, byte[] data, boolean enable, int type,
+ boolean highPriority) {
+ this.id = sTemporaryId++;
+ this.service = service;
+ this.charUuid = charUuid;
+ this.data = data;
+ this.enable = enable;
+ this.type = type;
+ this.highPriority = highPriority;
+ }
+
+ public static BleOperation newWriteOperation(UUID service, UUID charUuid, byte[] data,
+ boolean highPriority) {
+ return new BleOperation(service, charUuid, data, false, BleOperationType.TYPE_WRITE,
+ highPriority);
+ }
+
+ public static BleOperation newReadOperation(UUID service, UUID charUuid, boolean highPriority) {
+ return new BleOperation(service, charUuid, null, false, BleOperationType.TYPE_READ,
+ highPriority);
+ }
+
+ public static BleOperation newSubscribeOperation(UUID service, UUID charUuid, boolean enable,
+ boolean highPriority) {
+ return new BleOperation(service, charUuid, null, enable, BleOperationType.TYPE_SUBSCRIBE,
+ highPriority);
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public UUID getService() {
+ return service;
+ }
+
+ public UUID getCharUuid() {
+ return charUuid;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public boolean isHighPriority() {
+ return highPriority;
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperationType.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperationType.java
new file mode 100644
index 0000000..f190c8b
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/base/model/BleOperationType.java
@@ -0,0 +1,22 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.base.model;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.model.BleOperationType
+ .TYPE_READ;
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.model.BleOperationType
+ .TYPE_SUBSCRIBE;
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.base.model.BleOperationType
+ .TYPE_WRITE;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+@Retention(SOURCE)
+@IntDef({TYPE_SUBSCRIBE, TYPE_READ, TYPE_WRITE})
+public @interface BleOperationType {
+ int TYPE_SUBSCRIBE = 0;
+ int TYPE_READ = 1;
+ int TYPE_WRITE = 2;
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/MyBluetoothDevicesConstants.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/MyBluetoothDevicesConstants.java
new file mode 100644
index 0000000..2954ad0
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/MyBluetoothDevicesConstants.java
@@ -0,0 +1,21 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+public final class MyBluetoothDevicesConstants {
+
+ private MyBluetoothDevicesConstants() {
+
+ }
+
+ public static final List DEVICES_SUPPORTED = Arrays.asList("My Own BT Device V1",
+ "My Own BT Device V2");
+
+ private static final String MY_OWN_DEVICE_SERVICE = "52401523-f97c-7f90-0e7f-6c6f4e36db1c";
+ private static final String WRITE_READ_ID = "52401524-f97c-7f90-0e7f-6c6f4e36db1c";
+
+ public static final UUID UUID_MY_OWN_DEVICE_SERVICE = UUID.fromString(MY_OWN_DEVICE_SERVICE);
+ public static final UUID UUID_WRITE_READ_ID = UUID.fromString(WRITE_READ_ID);
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManager.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManager.java
new file mode 100644
index 0000000..bfd78c5
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManager.java
@@ -0,0 +1,11 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices.devices.myDevice;
+
+import io.reactivex.Completable;
+import io.reactivex.Single;
+
+public interface MyOwnBluetoothDeviceManager {
+ // Operations
+ Completable writeInt(int value);
+
+ Single readValue(int id);
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManagerImpl.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManagerImpl.java
new file mode 100644
index 0000000..2d7a1d8
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/devices/myDevice/MyOwnBluetoothDeviceManagerImpl.java
@@ -0,0 +1,42 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices.devices.myDevice;
+
+
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices
+ .MyBluetoothDevicesConstants.UUID_MY_OWN_DEVICE_SERVICE;
+import static co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices
+ .MyBluetoothDevicesConstants.UUID_WRITE_READ_ID;
+
+import android.content.Context;
+
+import co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothDeviceManager;
+import co.lateralview.myapp.infraestructure.manager.bluetooth.base.BluetoothManager;
+import io.reactivex.Completable;
+import io.reactivex.Single;
+
+public class MyOwnBluetoothDeviceManagerImpl extends BluetoothDeviceManager implements
+ MyOwnBluetoothDeviceManager {
+ private static final String TAG = "MyOwnBluetoothDeviceManagerImpl";
+
+ public MyOwnBluetoothDeviceManagerImpl(BluetoothManager bluetoothManager, Context context,
+ String deviceMacAddress) {
+ super(bluetoothManager, context, deviceMacAddress);
+ }
+
+ @Override
+ public Completable writeInt(int value) {
+ // Create the packet according your needs
+ byte[] packet = new byte[90];
+ packet[0] = (byte) value;
+
+ return write(UUID_MY_OWN_DEVICE_SERVICE, UUID_WRITE_READ_ID, packet, false);
+ }
+
+ @Override
+ public Single readValue(int id) {
+ // Create the packet according your needs
+
+ return read(UUID_MY_OWN_DEVICE_SERVICE, UUID_WRITE_READ_ID, true)
+ // TODO Get Value from BluetoothGattCharacteristic
+ .map(bluetoothGattCharacteristic -> bluetoothGattCharacteristic.getStringValue(0));
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/exceptions/MyOwnBluetoothException.java b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/exceptions/MyOwnBluetoothException.java
new file mode 100644
index 0000000..ea09281
--- /dev/null
+++ b/app/src/main/java/co/lateralview/myapp/infraestructure/manager/bluetooth/myBtDevices/exceptions/MyOwnBluetoothException.java
@@ -0,0 +1,14 @@
+package co.lateralview.myapp.infraestructure.manager.bluetooth.myBtDevices.exceptions;
+
+public class MyOwnBluetoothException extends Exception {
+
+ public MyOwnBluetoothException(String message) {
+ super(message);
+ }
+
+ public static class DeviceNotSupportedException extends MyOwnBluetoothException {
+ public DeviceNotSupportedException() {
+ super("The selected device is not in the whitelist of supported devices.");
+ }
+ }
+}
diff --git a/app/src/main/java/co/lateralview/myapp/ui/util/RxSchedulersUtils.java b/app/src/main/java/co/lateralview/myapp/ui/util/RxSchedulersUtils.java
index 9842c4a..5eb1ff8 100644
--- a/app/src/main/java/co/lateralview/myapp/ui/util/RxSchedulersUtils.java
+++ b/app/src/main/java/co/lateralview/myapp/ui/util/RxSchedulersUtils.java
@@ -1,11 +1,15 @@
package co.lateralview.myapp.ui.util;
+import java.util.List;
+
import io.reactivex.CompletableTransformer;
import io.reactivex.MaybeTransformer;
import io.reactivex.ObservableTransformer;
import io.reactivex.SingleTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.CompositeDisposable;
+import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public final class RxSchedulersUtils {
@@ -33,4 +37,91 @@ public static CompletableTransformer applyCompletableSchedulers() {
return upstream -> upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
+
+ // Useful methods to quickly dispose subscriptions
+
+ public static void safeDispose(CompositeDisposable compositeDisposable) {
+ if (compositeDisposable != null && !compositeDisposable.isDisposed()) {
+ compositeDisposable.dispose();
+ }
+ }
+
+ public static void safeDispose(Disposable disposable) {
+ if (disposable != null && !disposable.isDisposed()) {
+ disposable.dispose();
+ }
+ }
+
+ public static void safeDispose(List disposableList) {
+ if (disposableList != null && !disposableList.isEmpty()) {
+ for (Disposable disposable : disposableList) {
+ disposable.dispose();
+ }
+ }
+ }
+
+ // Observe observable stream in the UI Thread
+
+ public static ObservableTransformer observeObservableOnUi() {
+ return upstream -> upstream.observeOn(AndroidSchedulers.mainThread());
+ }
+
+ public static MaybeTransformer observeMaybeOnUi() {
+ return upstream -> upstream.observeOn(AndroidSchedulers.mainThread());
+ }
+
+ public static SingleTransformer observeSingleOnUi() {
+ return upstream -> upstream.observeOn(AndroidSchedulers.mainThread());
+ }
+
+ public static CompletableTransformer observeCompletableOnUi() {
+ return upstream -> upstream.observeOn(AndroidSchedulers.mainThread());
+ }
+
+ // Observe observable stream in the IO Thread in case we don't need to update the UI
+
+ public static ObservableTransformer observeObservableOnIO() {
+ return upstream -> upstream.observeOn(Schedulers.io());
+ }
+
+ public static MaybeTransformer observeMaybeOnIO() {
+ return upstream -> upstream.observeOn(Schedulers.io());
+ }
+
+ public static SingleTransformer observeSingleOnIO() {
+ return upstream -> upstream.observeOn(Schedulers.io());
+ }
+
+ public static CompletableTransformer observeCompletableOnIO() {
+ return upstream -> upstream.observeOn(Schedulers.io());
+ }
+
+ // Subscribe observable on IO Thread to not block UI Thread
+
+ public static ObservableTransformer subscribeObservableOnIO() {
+ return upstream -> upstream.subscribeOn(Schedulers.io());
+ }
+
+ public static MaybeTransformer subscribeMaybeOnIO() {
+ return upstream -> upstream.subscribeOn(Schedulers.io());
+ }
+
+ public static SingleTransformer subscribeSingleOnIO() {
+ return upstream -> upstream.subscribeOn(Schedulers.io());
+ }
+
+ public static CompletableTransformer subscribeCompletableOnIO() {
+ return upstream -> upstream.subscribeOn(Schedulers.io());
+ }
+
+ // Subscribe observable on Single Thread to not block UI Thread and run them sequentially
+
+
+ public static SingleTransformer subscribeSingleOnSingle() {
+ return upstream -> upstream.subscribeOn(Schedulers.single());
+ }
+
+ public static CompletableTransformer subscribeCompletableOnSingle() {
+ return upstream -> upstream.subscribeOn(Schedulers.single());
+ }
}