content: // Original source code: https://github.com/StevenRudenko/BleSensorTag. MIT License (Steven Rudenko)
  
  package com.adafruit.bluefruit.le.connect.ble;
  
  import android.bluetooth.BluetoothGatt;
  import android.bluetooth.BluetoothGattCallback;
  import android.bluetooth.BluetoothGattCharacteristic;
  import android.bluetooth.BluetoothGattDescriptor;
  import android.bluetooth.BluetoothGattService;
  import android.bluetooth.BluetoothProfile;
  import android.util.Log;
  
  import java.util.LinkedList;
  import java.util.UUID;
  
  // Encapsulate a list of actions to execute. Actions should be queued and executed sequentially to avoid problems
  class BleGattExecutor extends BluetoothGattCallback {
      // Log
      private final static String TAG = BleGattExecutor.class.getSimpleName();
  
      // Constants
      private static String CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
  
      interface ServiceAction {
          ServiceAction NULL = new ServiceAction() {
              @Override
              public boolean execute(BluetoothGatt bluetoothGatt) {
                  // it is null action. do nothing.
                  return true;
              }
          };
  
          /**
           * Executes action.
           *
           * @param bluetoothGatt
           * @return true - if action was executed instantly. false if action is waiting for feedback.
           */
          boolean execute(BluetoothGatt bluetoothGatt);
      }
  
      private final LinkedList<BleGattExecutor.ServiceAction> mQueue = new LinkedList<ServiceAction>();        // list of actions to execute
      private volatile ServiceAction mCurrentAction;
  
      protected void read(BluetoothGattService gattService, String characteristicUUID, String descriptorUUID) {
          ServiceAction action = serviceReadAction(gattService, characteristicUUID, descriptorUUID);
          mQueue.add(action);
      }
  
      private BleGattExecutor.ServiceAction serviceReadAction(final BluetoothGattService gattService, final String characteristicUuidString, final String descriptorUuidString) {
          return new BleGattExecutor.ServiceAction() {
              @Override
              public boolean execute(BluetoothGatt bluetoothGatt) {
                  final UUID characteristicUuid = UUID.fromString(characteristicUuidString);
                  final BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(characteristicUuid);
                  if (characteristic != null) {
                      if (descriptorUuidString == null) {
                          // Read Characteristic
                          if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0) {
                              bluetoothGatt.readCharacteristic(characteristic);
                              return false;
                          } else {
                              Log.w(TAG, "read: characteristic not readable: " + characteristicUuidString);
                              return true;
                          }
                      } else {
                          // Read Descriptor
                          final UUID descriptorUuid = UUID.fromString(descriptorUuidString);
                          final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUuid);
                          if (descriptor != null) {
                              bluetoothGatt.readDescriptor(descriptor);
                              return false;
                          } else {
                              Log.w(TAG, "read: descriptor not found: " + descriptorUuidString);
                              return true;
                          }
                      }
                  } else {
                      Log.w(TAG, "read: characteristic not found: " + characteristicUuidString);
                      return true;
                  }
              }
          };
      }
  
      void enableNotification(BluetoothGattService gattService, String characteristicUUID, boolean enable) {
          ServiceAction action = serviceNotifyAction(gattService, characteristicUUID, enable);
          mQueue.add(action);
      }
  
      private BleGattExecutor.ServiceAction serviceNotifyAction(final BluetoothGattService gattService, final String characteristicUuidString, final boolean enable) {
          return new BleGattExecutor.ServiceAction() {
              @Override
              public boolean execute(BluetoothGatt bluetoothGatt) {
                  if (characteristicUuidString != null) {
                      final UUID characteristicUuid = UUID.fromString(characteristicUuidString);
                      final BluetoothGattCharacteristic dataCharacteristic = gattService.getCharacteristic(characteristicUuid);
  
                      if (dataCharacteristic == null) {
                          Log.w(TAG, "Characteristic with UUID " + characteristicUuidString + " not found");
                          return true;
                      }
  
                      final UUID clientCharacteristicConfiguration = UUID.fromString(CHARACTERISTIC_CONFIG);
                      final BluetoothGattDescriptor config = dataCharacteristic.getDescriptor(clientCharacteristicConfiguration);
                      if (config == null)
                          return true;
  
                      // enableNotification/disable locally
                      bluetoothGatt.setCharacteristicNotification(dataCharacteristic, enable);
                      // enableNotification/disable remotely
                      config.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                      bluetoothGatt.writeDescriptor(config);
  
                      return false;
                  } else {
                      Log.w(TAG, "Characteristic UUID is null");
                      return true;
                  }
              }
          };
      }
  
      void enableIndication(BluetoothGattService gattService, String characteristicUUID, boolean enable) {
          ServiceAction action = serviceIndicateAction(gattService, characteristicUUID, enable);
          mQueue.add(action);
      }
  
      private BleGattExecutor.ServiceAction serviceIndicateAction(final BluetoothGattService gattService, final String characteristicUuidString, final boolean enable) {
          return new BleGattExecutor.ServiceAction() {
              @Override
              public boolean execute(BluetoothGatt bluetoothGatt) {
                  if (characteristicUuidString != null) {
                      final UUID characteristicUuid = UUID.fromString(characteristicUuidString);
                      final BluetoothGattCharacteristic dataCharacteristic = gattService.getCharacteristic(characteristicUuid);
  
                      if (dataCharacteristic == null) {
                          Log.w(TAG, "Characteristic with UUID " + characteristicUuidString + " not found");
                          return true;
                      }
  
                      final UUID clientCharacteristicConfiguration = UUID.fromString(CHARACTERISTIC_CONFIG);
                      final BluetoothGattDescriptor config = dataCharacteristic.getDescriptor(clientCharacteristicConfiguration);
                      if (config == null)
                          return true;
  
                      // enableNotification/disable remotely
                      config.setValue(enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                      bluetoothGatt.writeDescriptor(config);
  
                      return false;
                  } else {
                      Log.w(TAG, "Characteristic UUID is null");
                      return true;
                  }
              }
          };
      }
  
  
      void write(BluetoothGattService gattService, String uuid, byte[] value) {
          ServiceAction action = serviceWriteAction(gattService, uuid, value);
          mQueue.add(action);
      }
  
  
      private BleGattExecutor.ServiceAction serviceWriteAction(final BluetoothGattService gattService, final String uuid, final byte[] value) {
          return new BleGattExecutor.ServiceAction() {
              @Override
              public boolean execute(BluetoothGatt bluetoothGatt) {
                  final UUID characteristicUuid = UUID.fromString(uuid);
                  final BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(characteristicUuid);
                  if (characteristic != null) {
                      characteristic.setValue(value);
                      bluetoothGatt.writeCharacteristic(characteristic);
                      return false;
                  } else {
                      Log.w(TAG, "write: characteristic not found: " + uuid);
                      return true;
                  }
              }
          };
      }
  
      protected void clear() {
          mCurrentAction = null;
          mQueue.clear();
      }
  
      void execute(BluetoothGatt gatt) {
          if (mCurrentAction == null) {
              while (!mQueue.isEmpty()) {
                  final BleGattExecutor.ServiceAction action = mQueue.pop();
                  mCurrentAction = action;
                  if (!action.execute(gatt))
                      break;
                  mCurrentAction = null;
              }
          }
      }
  
      @Override
      public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
          super.onDescriptorRead(gatt, descriptor, status);
  
          mCurrentAction = null;
          execute(gatt);
      }
  
      @Override
      public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
          super.onDescriptorWrite(gatt, descriptor, status);
  
          mCurrentAction = null;
          execute(gatt);
      }
  
      @Override
      public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
          super.onCharacteristicWrite(gatt, characteristic, status);
  
          mCurrentAction = null;
          execute(gatt);
      }
  
      @Override
      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
          if (newState == BluetoothProfile.STATE_DISCONNECTED) {
              mQueue.clear();
              mCurrentAction = null;
          }
      }
  
      @Override
      public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
          super.onCharacteristicRead(gatt, characteristic, status);
  
          mCurrentAction = null;
          execute(gatt);
      }
  
      @Override
      public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
      }
  
  
      // Helper function to create a Gatt Executor with a custom listener
      static BleGattExecutor createExecutor(final BleExecutorListener listener) {
          return new BleGattExecutor() {
              @Override
              public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                  super.onConnectionStateChange(gatt, status, newState);
                  listener.onConnectionStateChange(gatt, status, newState);
              }
  
              @Override
              public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                  super.onServicesDiscovered(gatt, status);
                  listener.onServicesDiscovered(gatt, status);
              }
  
              @Override
              public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                  super.onCharacteristicRead(gatt, characteristic, status);
                  listener.onCharacteristicRead(gatt, characteristic, status);
              }
  
              @Override
              public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                  super.onCharacteristicChanged(gatt, characteristic);
                  listener.onCharacteristicChanged(gatt, characteristic);
              }
  
              @Override
              public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                  super.onDescriptorRead(gatt, descriptor, status);
                  listener.onDescriptorRead(gatt, descriptor, status);
              }
  
              @Override
              public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
                  super.onReadRemoteRssi(gatt, rssi, status);
                  listener.onReadRemoteRssi(gatt, rssi, status);
              }
          };
      }
  
      interface BleExecutorListener {
  
          void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);
  
          void onServicesDiscovered(BluetoothGatt gatt, int status);
  
          void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status);
  
          void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
  
          void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status);
  
          void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status);
  
      }
  }
  