package com.bleplx.adapter;

import static com.bleplx.adapter.utils.Constants.BluetoothState;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.nfc.Tag;
import android.os.Build;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
import android.util.Log;
import android.util.SparseArray;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.bleplx.adapter.errors.BleError;
import com.bleplx.adapter.errors.BleErrorCode;
import com.bleplx.adapter.errors.BleErrorUtils;
import com.bleplx.adapter.errors.ErrorConverter;
import com.bleplx.adapter.exceptions.CannotMonitorCharacteristicException;
import com.bleplx.adapter.utils.Base64Converter;
import com.bleplx.adapter.utils.Constants;
import com.bleplx.adapter.utils.DisposableMap;
import com.bleplx.adapter.utils.IdGenerator;
import com.bleplx.adapter.utils.LogLevel;
import com.bleplx.adapter.utils.RefreshGattCustomOperation;
import com.bleplx.adapter.utils.SafeExecutor;
import com.bleplx.adapter.utils.ServiceFactory;
import com.bleplx.adapter.utils.UUIDConverter;
import com.bleplx.adapter.utils.mapper.RxBleDeviceToDeviceMapper;
import com.bleplx.adapter.utils.mapper.RxScanResultToScanResultMapper;
import com.polidea.rxandroidble2.NotificationSetupMode;
import com.polidea.rxandroidble2.RxBleAdapterStateObservable;
import com.polidea.rxandroidble2.RxBleClient;
import com.polidea.rxandroidble2.RxBleConnection;
import com.polidea.rxandroidble2.RxBleDevice;
import com.polidea.rxandroidble2.internal.RxBleLog;
import com.polidea.rxandroidble2.scan.ScanFilter;
import com.polidea.rxandroidble2.scan.ScanSettings;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;

public class BleModule implements BleAdapter {

  private final ErrorConverter errorConverter = new ErrorConverter();

  @Nullable
  private RxBleClient rxBleClient;

  private final HashMap<String, Device> discoveredDevices = new HashMap<>();

  private final HashMap<String, Device> connectedDevices = new HashMap<>();

  private final HashMap<String, RxBleConnection> activeConnections = new HashMap<>();

  private final SparseArray<Service> discoveredServices = new SparseArray<>();

  private final SparseArray<Characteristic> discoveredCharacteristics = new SparseArray<>();

  private final SparseArray<Descriptor> discoveredDescriptors = new SparseArray<>();

  private final DisposableMap pendingTransactions = new DisposableMap();

  private final DisposableMap connectingDevices = new DisposableMap();

  private final BluetoothManager bluetoothManager;

  private final BluetoothAdapter bluetoothAdapter;

  private final Context context;

  @Nullable
  private Disposable scanSubscription;

  @Nullable
  private Disposable adapterStateChangesSubscription;

  private final RxBleDeviceToDeviceMapper rxBleDeviceToDeviceMapper = new RxBleDeviceToDeviceMapper();

  private final RxScanResultToScanResultMapper rxScanResultToScanResultMapper = new RxScanResultToScanResultMapper();

  private final ServiceFactory serviceFactory = new ServiceFactory();

  private int currentLogLevel = RxBleLog.NONE;

  FileInputStream fis;
  ParcelFileDescriptor hex;

  private int file_length = 0;
  private byte frame_number = 0;
  private Timer timer;
  private int updata_state = 0;
  private boolean mServiceGetted = true;
  private byte my_command = 0;

  private boolean fame_miss = false;
  private boolean fame_finish = false;
  private int tx_data_add = 0;
  private int fame_tx_sp = 0;
  private int fame_length = 43;
  private final byte[] fame_data = new byte[45];
  private int err_state = 0;
  private int updata_over_time = 0;
  private int ble_delay_time = 0;
  private boolean mConnected = false;
  private boolean ota_ing = false;
  private String ota_device_id;

  public BleModule(Context context) {
    this.context = context;
    bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
    bluetoothAdapter = bluetoothManager.getAdapter();
  }

  // Custom -----------------------------------------------------------------------------------
  private OnErrorCallback otaErrorCallback;
  private OnSuccessCallback<OTACallback> otaCallback;
  @Override
  public void ota(String deviceIdentifier, String downloadUrl, int command, OnSuccessCallback<OTACallback> onSuccessCallback, OnErrorCallback onErrorCallback) {
    // 根据 downloadUrl 下载固件文件
    FirmwareDownloadTask task = new FirmwareDownloadTask();
    task.setmContext(context);
    otaErrorCallback = onErrorCallback;
    otaCallback = onSuccessCallback;
    ota_device_id = deviceIdentifier;

    task.setOnLenCallback(new OnSuccessCallback<Integer>() {

      @Override
      public void onSuccess(Integer data) {
//        file_length = data;
//        Log.d("-- custom", "固件字节长度:  " + file_length);
      }
    });
    task.setOnSuccessCallback(new OnSuccessCallback<Uri>() {
      @Override
      public void onSuccess(Uri uri) {
        try {
          // 开始
          updata_over_time = 0;
          updata_state = 1;
          tx_data_add = 0;
          my_command = (byte) command;

          hex = context.getContentResolver().openFileDescriptor(uri, "r");

          fis = new FileInputStream(hex.getFileDescriptor());

          file_length = fis.available();

          ota_ing = true;
          timer = new Timer();
          // 这个是获取信息的频率
//          timer.schedule(new BleModule.EbikeGetData(), 2000, 250);
          // 这个是ota的频率
          timer.schedule(new BleModule.EbikeGetData(), 500, 2000);
          otaCallback.onSuccess(new OTACallback(0, null));

        } catch (Exception e) {
          ota_ing = false;
          BleError bleError = new BleError(BleErrorCode.OTAFirmwareErrorFileOpenFailed, "固件文件打开失败", null);
          bleError.internalMessage = e.getMessage();
          onErrorCallback.onError(bleError);
        }

      }
    });
    task.setOnErrorCallback(onErrorCallback);
    task.execute(downloadUrl);

  }


  private class EbikeGetData extends TimerTask {
    byte xor_tmp = 0;
    int i = 0;

    public  EbikeGetData() {
      super();
    }

    @SuppressLint("MissingPermission")
    @Override
    public void run() {
      byte[] buff = new byte[43];

      if (mServiceGetted && ota_ing) {
        switch (updata_state) {
          case 0: {                    // 数据读取指令
            buff[0] = (byte) 0xaa;
            buff[1] = (byte) 0x55;
            buff[2] = frame_number;
            buff[3] = (byte) 0x0d;
            buff[4] = (byte) 0x0d;

            for(int i = 5; i < 18 ; i++)
            { buff[i] = 0;}

            for(int i = 0; i < 18 ; i++)
            { xor_tmp ^= buff[i];}

            buff[18] = xor_tmp;
            buff[19] = (byte) 0x0d;

            // 发送指令
//            my_characteristic_1.setValue(buff);
//            mBluetoothLeService.mBluetoothGatt.writeCharacteristic(my_characteristic_1);
            innerWriteCharacteristicForDevice(ota_device_id, buff);

            frame_number ++;
          }
          break;
          case 1: {                   // 更新ROM指令
            buff[0] = (byte) 0xaa;
            buff[1] = (byte) 0x55;
            buff[2] = frame_number;
            buff[3] = (byte) 0x0d;
            buff[4] = my_command;
            buff[5] = (byte) 0xa0;
            buff[6] = (byte) 0x0a;

            for(int i = 7; i < 18 ; i++)
            { buff[i] = 0;}

            for(int i = 0; i < 18 ; i++)
            { xor_tmp ^= buff[i];}

            buff[18] = xor_tmp;
            buff[19] = (byte) 0x0d;

            // 发送指令
//            my_characteristic_1.setValue(buff);
//            mBluetoothLeService.mBluetoothGatt.writeCharacteristic(my_characteristic_1);
              innerWriteCharacteristicForDevice(ota_device_id, buff);

            frame_number ++;
            //my_command = 0;
          }
          break;
          case 2: {                   // Linked 指令
            if (mServiceGetted) {
              String s_buff = "Linking";
//              my_characteristic_1.setValue(s_buff);
//              mBluetoothLeService.mBluetoothGatt.writeCharacteristic(my_characteristic_1);
              byte[] byteArray = new byte[s_buff.length()];
              for (int i = 0; i < s_buff.length(); i++) {
                byteArray[i] = (byte) s_buff.charAt(i);
              }
              innerWriteCharacteristicForDevice(ota_device_id, byteArray);
            }
          }
          break;
          case 3: {                   // UPDATA 指令
            if (mServiceGetted) {
              String s_buff = "UPDATA";
//              my_characteristic_1.setValue(s_buff);
//              mBluetoothLeService.mBluetoothGatt.writeCharacteristic(my_characteristic_1);

              byte[] byteArray = new byte[s_buff.length()];
              for (int i = 0; i < s_buff.length(); i++) {
                byteArray[i] = (byte) s_buff.charAt(i);
              }
              innerWriteCharacteristicForDevice(ota_device_id, byteArray);

              updata_state = 4;
              fame_finish = false;
              timer.cancel();
              timer = new Timer();
              timer.schedule(new BleModule.EbikeGetData(), 1000, 25);
              otaCallback.onSuccess(new OTACallback(3, null));
            }
          }
          break;
          case 4: {    // 发送 ROM 数据
            if(fame_finish == false) {
              Log.d("-- custom", "准备发送文件长度:  " + file_length + ", 当前已发送文件长度:  " + tx_data_add);
              if (file_length > tx_data_add) {
                if ((fame_tx_sp == 0) && (!fame_miss)) {
                  try {
                    for (i = 0; i < fame_length; i++) {
                      fame_data[i] = (byte) fis.read();
                      tx_data_add++;
                      //文件错误
                      if ((fame_data[0] != ':') || (fame_data[1] > '1')) {
                        updata_state = 0;
                        err_state = 4;
                        // 文件错误
                        timer.cancel();
                        otaCallback.onSuccess(new OTACallback(11, null));
                        ota_ing = false;
                        return;
                      } else {
                        if (i == 2) {
                          if ((fame_data[2] > 47) && (fame_data[2] < 58)) {
                            fame_length = (fame_data[1] - 48) * 32 + (fame_data[2] - 48) * 2 + 13;
                          } else if ((fame_data[2] > 64) && (fame_data[2] < 71)) {
                            fame_length = (fame_data[1] - 48) * 32 + (fame_data[2] - 55) * 2 + 13;
                          }
                        }
                      }
                    }
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
                }

                if (fame_miss) {
                  Log.d("-- custom", "文件发送miss，重新发上一帧");
                }

                fame_miss = false;
                fame_finish = true;
                Log.d("-- custom", "准备发送数据:" + Arrays.toString(fame_data));
//                my_characteristic_1.setValue(fame_data);
//                mBluetoothLeService.mBluetoothGatt.writeCharacteristic(my_characteristic_1);
                innerWriteCharacteristicForDevice(ota_device_id, fame_data);
//
                Log.i("-- custom", updata_state +"," + updata_over_time);
                Log.d("-- custom", "progress："  + tx_data_add * 100.0 / file_length);
              }
            }
          }
          break;
        }

      }

//      if (mConnected) {
        //超时判断
        if(updata_state == 4) {
          if (updata_over_time > 80) {
            updata_state = 0;
            err_state = 1;
            // 超时了
            timer.cancel();
            otaCallback.onSuccess(new OTACallback(10, null));
            ota_ing = false;
          } else if (updata_over_time < 300) {
            updata_over_time++;
          }
        }
//      }else{
//        if (ble_delay_time > 500) {
//          updata_state = 0;
//          err_state = 3;
//        } else if (ble_delay_time < 500) {
//          ble_delay_time++;
//        }
//      }

    }

  }

  @SuppressLint("SetTextI18n")
  private void displayData(byte[] data) {
    short data_tmp = 0;
    int anumber = 0;
    int i;
    float number = 0;
    int[] idata = new int[32];
    byte xor_tmp = 0;
    String RX_DATA;

    if (data != null) {
      Data_charge(data, idata);
      //mDataField.setText(data);
      updata_over_time = 0;
      RX_DATA = new String(data);
      Log.i("rx data =  ", RX_DATA + "," + updata_state);
      switch (updata_state) {
        case 0: {
          if ((data[0] == (byte) 0x55) && (data[1] == (byte) 0xaa) && (data[3] == (byte) 0x0d)) {
            for (i = 0; i < 18; i++) {
              xor_tmp ^= data[i];
            }
            if (xor_tmp == data[18]) {
              if (data[4] == (byte) 0x0D) {
                data_tmp = (short) (idata[6]);
                number = ((float) data_tmp) / 10;
                Log.d("-- custom", "HardWareVersion:  " + Float.toString(number));

                data_tmp = (short) (idata[5]);
                number = (float) data_tmp;
                Log.d("-- custom", "SoftWareVersion:  " + Float.toString(number));

              }
            }
          } else {
            anumber = 0;
            for (i = 0; i < data.length; i++) {
              switch (anumber) {
                case 0: { if (data[i] == 'R') { anumber++;}}
                break;
                case 1: { if (data[i] == 'e') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 'a') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == 'd') { anumber++;} else { anumber = 0;}}
                break;
                case 4: { if (data[i] == 'y') {
                  anumber++;
//                  mtextView11.setText("Ready!");
//                  mtextView10.setText("模块设备已就绪，等待更新！");
                  Log.d("-- custom", "Ready!");

                } else { anumber = 0;}
                }
                break;
                default: { return;}
              }
            }
          }
        }
        break;
        case 1: {
          anumber = 0;
          for (i = 0; i < data.length; i++) {
            switch (anumber) {
              case 0: { if (data[i] == 'R') { anumber++;}}
              break;
              case 1: { if (data[i] == 'e') { anumber++;} else { anumber = 0;}}
              break;
              case 2: { if (data[i] == 'a') { anumber++;} else { anumber = 0;}}
              break;
              case 3: { if (data[i] == 'd') { anumber++;} else { anumber = 0;}}
              break;
              case 4: { if (data[i] == 'y') {
                anumber++;
                updata_state = 2;
//                mtextView11.setText("Ready!");
                Log.d("-- custom", "Ready!");
                otaCallback.onSuccess(new OTACallback(1, null));
                my_command = 0;
//                if (!ReciverDone) {
//                  mbuttom10.setEnabled(true);
//                  ReciverDone = true;
//                }
              } else { anumber = 0;}
              }
              break;
              default: { return;}
            }
          }
        }
        break;
        case 2: {
          anumber = 0;
          for (i = 0; i < data.length; i++) {
            switch (anumber) {
              case 0: { if (data[i] == 'L') { anumber++;}}
              break;
              case 1: { if (data[i] == 'i') { anumber++; } else { anumber = 0;}}
              break;
              case 2: { if (data[i] == 'n') { anumber++; } else { anumber = 0;}}
              break;
              case 3: { if (data[i] == 'k') { anumber++; } else { anumber = 0;}}
              break;
              case 4: { if (data[i] == 'e') { anumber++; } else { anumber = 0;}}
              break;
              case 5: { if (data[i] == 'd') {
                anumber++;
                updata_state = 3;
//                mtextView11.setText("Linked!");
                Log.d("-- custom", "Linked!");
                otaCallback.onSuccess(new OTACallback(2, null));
//                if (!ReciverDone) {
//                  mbuttom10.setEnabled(true);
//                  ReciverDone = true;
//                }
              } else { anumber = 0;}
              }
              break;
              default: { return;}
            }
          }
        }
        break;
        case 4: {
          anumber = 1;
          fame_finish = false;
          if(data[0] == 'T'){
            for (i = 1; i < data.length; i++) {
              switch (anumber) {
                case 1: { if (data[i] == 'a') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 'k') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == 'e') { anumber++;
                  updata_state = 4;
//                  mtextView11.setText("Take!");
                  Log.d("-- custom", "Take!");
                  otaCallback.onSuccess(new OTACallback(5, null));
                } else { anumber = 0;}
                }
                break;
                default: { return;}
              }
            }
          } else if(data[0] == 'D'){
            for (i = 1; i < data.length; i++) {
              switch (anumber) {
                case 1: { if (data[i] == 'o') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 'n') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == 'e') { anumber++;
                  updata_state = 4;
//                  mtextView11.setText("Done!");
//                  mtextView30.setText(tx_data_add + " Byte / " + file_length + " Byte");
                  Log.d("-- custom", "Done!");
                  otaCallback.onSuccess(new OTACallback(4, tx_data_add + "-" + file_length));
                } else { anumber = 0;}
                }
                break;
                default: { return;}
              }
            }
          } else if(data[0] == 'M'){
            for (i = 1; i < data.length; i++) {
              switch (anumber) {
                case 1: { if (data[i] == 'i') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 's') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == 's') { anumber++;
                  updata_state = 4;
                  fame_miss = true;
//                  mtextView11.setText("Miss!");
                  Log.d("-- custom", "Miss!");
//                  otaCallback.onSuccess(new OTACallback(6, null));
                } else { anumber = 0; }
                }
                break;
                default: { return; }
              }
            }
          } else if(data[0] == 'E') {
            for (i = 1; i < data.length; i++) {
              switch (anumber) {
                case 1: { if (data[i] == 'R') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 'R') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == ':') { anumber++;} else { anumber = 0;}}
                break;
                case 4: { if (data[i] == '0') { anumber++;} else { anumber = 0;}}
                break;
                case 5: {
                  if (data[i] == '1') {
//                    mtextView10.setText("传输数据错误");
                    Log.d("-- custom", "传输数据错误!");
                    otaCallback.onSuccess(new OTACallback(12, null));
                  } else {
//                    mtextView10.setText("驱动版本错误");
                    Log.d("-- custom", "驱动版本错误!");
                    otaCallback.onSuccess(new OTACallback(13, null));
                  }

                  updata_state = 0;
                  anumber++;
                  timer.cancel();
//                  timer = new Timer();
//                  timer.schedule(new EbikeOTA.EbikeGetData(), 2000, 500);
                  // 回调更新失败
                  tx_data_add = 0;
                  ota_ing = false;

                  try {
                    fis.close();
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
                }
                break;
                default: { return;}
              }
            }
          } else if(data[0] == 'F') {
            for (i = 1; i < data.length; i++) {
              switch (anumber) {
                case 1: { if (data[i] == 'i') { anumber++;} else { anumber = 0;}}
                break;
                case 2: { if (data[i] == 'n') { anumber++;} else { anumber = 0;}}
                break;
                case 3: { if (data[i] == 'i') { anumber++;} else { anumber = 0;}}
                break;
                case 4: { if (data[i] == 's') { anumber++;} else { anumber = 0;}}
                break;
                case 5: { if (data[i] == 'h') {
                  Log.d("-- custom", "Finish!");
                  Log.d("-- custom", "驱动更新成功!");
                  updata_state = 0;
                  //mbuttom10.setEnabled(true);
                  anumber++;
                  timer.cancel();
//                  timer = new Timer();
//                  timer.schedule(new EbikeOTA.EbikeGetData(), 2000, 500);
                  // 回调 成功结果
                  otaCallback.onSuccess(new OTACallback(200, null));
                  ota_ing = false;

                  try {
                    fis.close();
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
                }
                }
                break;
                default: {
                  return;
                }
              }
            }
          }
        }
        break;
      }
    }
  }

  private void Data_charge(byte[] data, int[] idata) {
    if (data != null) {
      for (int i = 0; i < data.length; i++) {
        if (data[i] < 0)
        { idata[i] = data[i] + 256;}
        else
        { idata[i] = data[i];}
      }
    }
  }

  @Override
  public void createClient(String restoreStateIdentifier,
                           OnEventCallback<String> onAdapterStateChangeCallback,
                           OnEventCallback<Integer> onStateRestored) {
    rxBleClient = RxBleClient.create(context);
    adapterStateChangesSubscription = monitorAdapterStateChanges(context, onAdapterStateChangeCallback);

    // We need to send signal that BLE Module starts without restored state
    if (restoreStateIdentifier != null) {
      onStateRestored.onEvent(null);
    }
  }

  @Override
  public void destroyClient() {
    if (adapterStateChangesSubscription != null) {
      adapterStateChangesSubscription.dispose();
      adapterStateChangesSubscription = null;
    }
    if (scanSubscription != null && !scanSubscription.isDisposed()) {
      scanSubscription.dispose();
      scanSubscription = null;
    }
    pendingTransactions.removeAllSubscriptions();
    connectingDevices.removeAllSubscriptions();

    discoveredServices.clear();
    discoveredCharacteristics.clear();
    discoveredDescriptors.clear();
    connectedDevices.clear();
    activeConnections.clear();
    discoveredDevices.clear();

    rxBleClient = null;
    IdGenerator.clear();
  }


  @Override
  public void enable(final String transactionId,
                     final OnSuccessCallback<Void> onSuccessCallback,
                     final OnErrorCallback onErrorCallback) {
    changeAdapterState(
      RxBleAdapterStateObservable.BleAdapterState.STATE_ON,
      transactionId,
      onSuccessCallback,
      onErrorCallback);
  }

  @Override
  public void disable(final String transactionId,
                      final OnSuccessCallback<Void> onSuccessCallback,
                      final OnErrorCallback onErrorCallback) {
    changeAdapterState(
      RxBleAdapterStateObservable.BleAdapterState.STATE_OFF,
      transactionId,
      onSuccessCallback,
      onErrorCallback);
  }

  @BluetoothState
  @Override
  public String getCurrentState() {
    if (!supportsBluetoothLowEnergy()) return BluetoothState.UNSUPPORTED;
    if (bluetoothManager == null) return BluetoothState.POWERED_OFF;
    return mapNativeAdapterStateToLocalBluetoothState(bluetoothAdapter.getState());
  }

  @Override
  public void startDeviceScan(String[] filteredUUIDs,
                              int scanMode,
                              int callbackType,
                              boolean legacyScan,
                              OnEventCallback<ScanResult> onEventCallback,
                              OnErrorCallback onErrorCallback) {
    UUID[] uuids = null;

    if (filteredUUIDs != null) {
      uuids = UUIDConverter.convert(filteredUUIDs);
      if (uuids == null) {
        onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(filteredUUIDs));
        return;
      }
    }

    safeStartDeviceScan(uuids, scanMode, callbackType, legacyScan, onEventCallback, onErrorCallback);
  }

  @Override
  public void stopDeviceScan() {
    if (scanSubscription != null) {
      scanSubscription.dispose();
      scanSubscription = null;
    }
  }

  @Override
  public void requestConnectionPriorityForDevice(String deviceIdentifier,
                                                 int connectionPriority,
                                                 final String transactionId,
                                                 final OnSuccessCallback<Device> onSuccessCallback,
                                                 final OnErrorCallback onErrorCallback) {
    final Device device;
    try {
      device = getDeviceById(deviceIdentifier);
    } catch (BleError error) {
      onErrorCallback.onError(error);
      return;
    }

    final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Device> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .requestConnectionPriority(connectionPriority, 1, TimeUnit.MILLISECONDS)
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      }).subscribe((Action) () -> {
        safeExecutor.success(device);
        pendingTransactions.removeSubscription(transactionId);
      }, throwable -> {
        safeExecutor.error(errorConverter.toError(throwable));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  @Override
  public void readRSSIForDevice(String deviceIdentifier,
                                final String transactionId,
                                final OnSuccessCallback<Device> onSuccessCallback,
                                final OnErrorCallback onErrorCallback) {
    final Device device;
    try {
      device = getDeviceById(deviceIdentifier);
    } catch (BleError error) {
      onErrorCallback.onError(error);
      return;
    }
    final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Device> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .readRssi()
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(rssi -> {
        device.setRssi(rssi);
        safeExecutor.success(device);
        pendingTransactions.removeSubscription(transactionId);
      }, error -> {
        safeExecutor.error(errorConverter.toError(error));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  @Override
  public void requestMTUForDevice(String deviceIdentifier, int mtu,
                                  final String transactionId,
                                  final OnSuccessCallback<Device> onSuccessCallback,
                                  final OnErrorCallback onErrorCallback) {
    final Device device;
    try {
      device = getDeviceById(deviceIdentifier);
    } catch (BleError error) {
      onErrorCallback.onError(error);
      return;
    }

    final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Device> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .requestMtu(mtu)
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      }).subscribe(outputMtu -> {
        device.setMtu(outputMtu);
        safeExecutor.success(device);
        pendingTransactions.removeSubscription(transactionId);
      }, error -> {
        safeExecutor.error(errorConverter.toError(error));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  @Override
  public void getKnownDevices(String[] deviceIdentifiers,
                              OnSuccessCallback<Device[]> onSuccessCallback,
                              OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to get known devices", null));
      return;
    }

    List<Device> knownDevices = new ArrayList<>();
    for (final String deviceId : deviceIdentifiers) {
      if (deviceId == null) {
        onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(deviceIdentifiers));
        return;
      }

      final Device device = discoveredDevices.get(deviceId);
      if (device != null) {
        knownDevices.add(device);
      }
    }

    onSuccessCallback.onSuccess(knownDevices.toArray(new Device[knownDevices.size()]));
  }

  @Override
  public void getConnectedDevices(String[] serviceUUIDs,
                                  OnSuccessCallback<Device[]> onSuccessCallback,
                                  OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to get connected devices", null));
      return;
    }

    if (serviceUUIDs.length == 0) {
      onSuccessCallback.onSuccess(new Device[0]);
      return;
    }

    UUID[] uuids = new UUID[serviceUUIDs.length];
    for (int i = 0; i < serviceUUIDs.length; i++) {
      UUID uuid = UUIDConverter.convert(serviceUUIDs[i]);

      if (uuid == null) {
        onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(serviceUUIDs));
        return;
      }

      uuids[i] = uuid;
    }

    List<Device> localConnectedDevices = new ArrayList<>();
    for (Device device : connectedDevices.values()) {
      for (UUID uuid : uuids) {
        if (device.getServiceByUUID(uuid) != null) {
          localConnectedDevices.add(device);
          break;
        }
      }
    }

    onSuccessCallback.onSuccess(localConnectedDevices.toArray(new Device[localConnectedDevices.size()]));

  }

  @Override
  public void connectToDevice(String deviceIdentifier,
                              ConnectionOptions connectionOptions,
                              OnSuccessCallback<Device> onSuccessCallback,
                              OnEventCallback<ConnectionState> onConnectionStateChangedCallback,
                              OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to connect to device", null));
      return;
    }

    final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier);
    if (device == null) {
      onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier));
      return;
    }

    safeConnectToDevice(
      device,
      connectionOptions.getAutoConnect(),
      connectionOptions.getRequestMTU(),
      connectionOptions.getRefreshGattMoment(),
      connectionOptions.getTimeoutInMillis(),
      connectionOptions.getConnectionPriority(),
      onSuccessCallback, onConnectionStateChangedCallback, onErrorCallback);
  }

  @Override
  public void cancelDeviceConnection(String deviceIdentifier,
                                     OnSuccessCallback<Device> onSuccessCallback,
                                     OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to cancel device connection", null));
      return;
    }

    final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier);

    if (connectingDevices.removeSubscription(deviceIdentifier) && device != null) {
      onSuccessCallback.onSuccess(rxBleDeviceToDeviceMapper.map(device, null));
    } else {
      if (device == null) {
        onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier));
      } else {
        onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceIdentifier));
      }
    }
  }

  @Override
  public void isDeviceConnected(String deviceIdentifier,
                                OnSuccessCallback<Boolean> onSuccessCallback,
                                OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to check if device is connected", null));
      return;
    }

    try {
      final RxBleDevice device = rxBleClient.getBleDevice(deviceIdentifier);
      if (device == null) {
        onErrorCallback.onError(BleErrorUtils.deviceNotFound(deviceIdentifier));
        return;
      }

      boolean connected = device.getConnectionState()
        .equals(RxBleConnection.RxBleConnectionState.CONNECTED);
      onSuccessCallback.onSuccess(connected);
    } catch (Exception e) {
      RxBleLog.e(e, "Error while checking if device is connected");
      onErrorCallback.onError(errorConverter.toError(e));
    }
  }

  @Override
  public void discoverAllServicesAndCharacteristicsForDevice(String deviceIdentifier,
                                                             String transactionId,
                                                             OnSuccessCallback<Device> onSuccessCallback,
                                                             OnErrorCallback onErrorCallback) {
    final Device device;
    try {
      device = getDeviceById(deviceIdentifier);
    } catch (BleError error) {
      onErrorCallback.onError(error);
      return;
    }

    safeDiscoverAllServicesAndCharacteristicsForDevice(device, transactionId, onSuccessCallback, onErrorCallback);
  }

  @Override
  public List<Service> getServicesForDevice(String deviceIdentifier) throws BleError {
    final Device device = getDeviceById(deviceIdentifier);
    final List<Service> services = device.getServices();
    if (services == null) {
      throw BleErrorUtils.deviceServicesNotDiscovered(device.getId());
    }
    return services;
  }

  @Override
  public List<Characteristic> getCharacteristicsForDevice(String deviceIdentifier,
                                                          String serviceUUID) throws BleError {
    final UUID convertedServiceUUID = UUIDConverter.convert(serviceUUID);
    if (convertedServiceUUID == null) {
      throw BleErrorUtils.invalidIdentifiers(serviceUUID);
    }

    final Device device = getDeviceById(deviceIdentifier);

    final Service service = device.getServiceByUUID(convertedServiceUUID);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(serviceUUID);
    }

    return service.getCharacteristics();
  }

  @Override
  public List<Characteristic> getCharacteristicsForService(int serviceIdentifier) throws BleError {
    Service service = discoveredServices.get(serviceIdentifier);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier));
    }
    return service.getCharacteristics();
  }

  @Override
  public List<Descriptor> descriptorsForDevice(final String deviceIdentifier,
                                               final String serviceUUID,
                                               final String characteristicUUID) throws BleError {
    final UUID[] uuids = UUIDConverter.convert(serviceUUID, characteristicUUID);
    if (uuids == null) {
      throw BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID);
    }

    Device device = getDeviceById(deviceIdentifier);

    final Service service = device.getServiceByUUID(uuids[0]);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(serviceUUID);
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(uuids[1]);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(characteristicUUID);
    }

    return characteristic.getDescriptors();
  }

  @Override
  public List<Descriptor> descriptorsForService(final int serviceIdentifier,
                                                final String characteristicUUID) throws BleError {
    final UUID uuid = UUIDConverter.convert(characteristicUUID);
    if (uuid == null) {
      throw BleErrorUtils.invalidIdentifiers(characteristicUUID);
    }

    Service service = discoveredServices.get(serviceIdentifier);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier));
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(uuid);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(characteristicUUID);
    }

    return characteristic.getDescriptors();
  }

  @Override
  public List<Descriptor> descriptorsForCharacteristic(final int characteristicIdentifier) throws BleError {
    Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier));
    }

    return characteristic.getDescriptors();
  }

  @Override
  public void readCharacteristicForDevice(String deviceIdentifier,
                                          String serviceUUID,
                                          String characteristicUUID,
                                          String transactionId,
                                          OnSuccessCallback<Characteristic> onSuccessCallback,
                                          OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback);
  }

  @Override
  public void readCharacteristicForService(int serviceIdentifier,
                                           String characteristicUUID,
                                           String transactionId,
                                           OnSuccessCallback<Characteristic> onSuccessCallback,
                                           OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      serviceIdentifier, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback);
  }

  @Override
  public void readCharacteristic(int characteristicIdentifier,
                                 String transactionId,
                                 OnSuccessCallback<Characteristic> onSuccessCallback,
                                 OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeReadCharacteristicForDevice(characteristic, transactionId, onSuccessCallback, onErrorCallback);
  }

  @Override
  public void writeCharacteristicForDevice(String deviceIdentifier,
                                           String serviceUUID,
                                           String characteristicUUID,
                                           String valueBase64,
                                           boolean withResponse,
                                           String transactionId,
                                           OnSuccessCallback<Characteristic> onSuccessCallback,
                                           OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    writeCharacteristicWithValue(
      characteristic,
      valueBase64,
      withResponse,
      transactionId,
      onSuccessCallback,
      onErrorCallback);
  }

  // -- custom 内部自己使用的发送指令的代码
  private void innerWriteCharacteristicForDevice(String deviceIdentifier, byte[] buff) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      deviceIdentifier, "ff00", "ff02", null);
    if (characteristic == null) {
      return;
    }
    characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
    UUID uuid = UUID.randomUUID();
    String uuidString = uuid.toString();
    safeWriteCharacteristicForDevice(characteristic, buff, uuidString, null, null);
  }

  @Override
  public void writeCharacteristicForService(int serviceIdentifier,
                                            String characteristicUUID,
                                            String valueBase64,
                                            boolean withResponse,
                                            String transactionId,
                                            OnSuccessCallback<Characteristic> onSuccessCallback,
                                            OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      serviceIdentifier, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    writeCharacteristicWithValue(
      characteristic,
      valueBase64,
      withResponse,
      transactionId,
      onSuccessCallback,
      onErrorCallback);
  }

  @Override
  public void writeCharacteristic(int characteristicIdentifier,
                                  String valueBase64,
                                  boolean withResponse,
                                  String transactionId,
                                  OnSuccessCallback<Characteristic> onSuccessCallback,
                                  OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    writeCharacteristicWithValue(
      characteristic,
      valueBase64,
      withResponse,
      transactionId,
      onSuccessCallback,
      onErrorCallback
    );
  }

  @Override
  public void monitorCharacteristicForDevice(String deviceIdentifier,
                                             String serviceUUID,
                                             String characteristicUUID,
                                             String transactionId,
                                             OnEventCallback<Characteristic> onEventCallback,
                                             OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      deviceIdentifier, serviceUUID, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeMonitorCharacteristicForDevice(characteristic, transactionId, onEventCallback, onErrorCallback);
  }

  @Override
  public void monitorCharacteristicForService(int serviceIdentifier,
                                              String characteristicUUID,
                                              String transactionId,
                                              OnEventCallback<Characteristic> onEventCallback,
                                              OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(
      serviceIdentifier, characteristicUUID, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeMonitorCharacteristicForDevice(characteristic, transactionId, onEventCallback, onErrorCallback);
  }

  @Override
  public void monitorCharacteristic(int characteristicIdentifier, String transactionId,
                                    OnEventCallback<Characteristic> onEventCallback,
                                    OnErrorCallback onErrorCallback) {
    final Characteristic characteristic = getCharacteristicOrEmitError(characteristicIdentifier, onErrorCallback);
    if (characteristic == null) {
      return;
    }

    safeMonitorCharacteristicForDevice(characteristic, transactionId, onEventCallback, onErrorCallback);
  }

  @Override
  public void readDescriptorForDevice(final String deviceId,
                                      final String serviceUUID,
                                      final String characteristicUUID,
                                      final String descriptorUUID,
                                      final String transactionId,
                                      OnSuccessCallback<Descriptor> successCallback,
                                      OnErrorCallback errorCallback) {

    try {
      Descriptor descriptor = getDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID);
      safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void readDescriptorForService(final int serviceIdentifier,
                                       final String characteristicUUID,
                                       final String descriptorUUID,
                                       final String transactionId,
                                       OnSuccessCallback<Descriptor> successCallback,
                                       OnErrorCallback errorCallback) {
    try {
      Descriptor descriptor = getDescriptor(serviceIdentifier, characteristicUUID, descriptorUUID);
      safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void readDescriptorForCharacteristic(final int characteristicIdentifier,
                                              final String descriptorUUID,
                                              final String transactionId,
                                              OnSuccessCallback<Descriptor> successCallback,
                                              OnErrorCallback errorCallback) {

    try {
      Descriptor descriptor = getDescriptor(characteristicIdentifier, descriptorUUID);
      safeReadDescriptorForDevice(descriptor, transactionId, successCallback, errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void readDescriptor(final int descriptorIdentifier,
                             final String transactionId,
                             OnSuccessCallback<Descriptor> onSuccessCallback,
                             OnErrorCallback onErrorCallback) {
    try {
      Descriptor descriptor = getDescriptor(descriptorIdentifier);
      safeReadDescriptorForDevice(descriptor, transactionId, onSuccessCallback, onErrorCallback);
    } catch (BleError error) {
      onErrorCallback.onError(error);
    }
  }

  private void safeReadDescriptorForDevice(final Descriptor descriptor,
                                           final String transactionId,
                                           OnSuccessCallback<Descriptor> onSuccessCallback,
                                           OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = getConnectionOrEmitError(descriptor.getDeviceId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Descriptor> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .readDescriptor(descriptor.getNativeDescriptor())
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(bytes -> {
        descriptor.logValue("Read from", bytes);
        descriptor.setValue(bytes);
        safeExecutor.success(new Descriptor(descriptor));
        pendingTransactions.removeSubscription(transactionId);
      }, error -> {
        safeExecutor.error(errorConverter.toError(error));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  @Override
  public void writeDescriptorForDevice(final String deviceId,
                                       final String serviceUUID,
                                       final String characteristicUUID,
                                       final String descriptorUUID,
                                       final String valueBase64,
                                       final String transactionId,
                                       OnSuccessCallback<Descriptor> successCallback,
                                       OnErrorCallback errorCallback) {
    try {
      Descriptor descriptor = getDescriptor(deviceId, serviceUUID, characteristicUUID, descriptorUUID);
      safeWriteDescriptorForDevice(
        descriptor,
        valueBase64,
        transactionId,
        successCallback,
        errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void writeDescriptorForService(final int serviceIdentifier,
                                        final String characteristicUUID,
                                        final String descriptorUUID,
                                        final String valueBase64,
                                        final String transactionId,
                                        OnSuccessCallback<Descriptor> successCallback,
                                        OnErrorCallback errorCallback) {
    try {
      Descriptor descriptor = getDescriptor(serviceIdentifier, characteristicUUID, descriptorUUID);
      safeWriteDescriptorForDevice(
        descriptor,
        valueBase64,
        transactionId,
        successCallback,
        errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void writeDescriptorForCharacteristic(final int characteristicIdentifier,
                                               final String descriptorUUID,
                                               final String valueBase64,
                                               final String transactionId,
                                               OnSuccessCallback<Descriptor> successCallback,
                                               OnErrorCallback errorCallback) {
    try {
      Descriptor descriptor = getDescriptor(characteristicIdentifier, descriptorUUID);
      safeWriteDescriptorForDevice(
        descriptor,
        valueBase64,
        transactionId,
        successCallback,
        errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }
  }

  @Override
  public void writeDescriptor(final int descriptorIdentifier,
                              final String valueBase64,
                              final String transactionId,
                              OnSuccessCallback<Descriptor> successCallback,
                              OnErrorCallback errorCallback) {
    try {
      Descriptor descriptor = getDescriptor(descriptorIdentifier);
      safeWriteDescriptorForDevice(
        descriptor,
        valueBase64,
        transactionId,
        successCallback,
        errorCallback);
    } catch (BleError error) {
      errorCallback.onError(error);
    }

  }

  private void safeWriteDescriptorForDevice(final Descriptor descriptor,
                                            final String valueBase64,
                                            final String transactionId,
                                            OnSuccessCallback<Descriptor> successCallback,
                                            OnErrorCallback errorCallback) {
    BluetoothGattDescriptor nativeDescriptor = descriptor.getNativeDescriptor();

    if (nativeDescriptor.getUuid().equals(Constants.CLIENT_CHARACTERISTIC_CONFIG_UUID)) {
      errorCallback.onError(BleErrorUtils.descriptorWriteNotAllowed(UUIDConverter.fromUUID(nativeDescriptor.getUuid())));
      return;
    }

    final RxBleConnection connection = getConnectionOrEmitError(descriptor.getDeviceId(), errorCallback);
    if (connection == null) {
      return;
    }

    final byte[] value;
    try {
      value = Base64Converter.decode(valueBase64);
    } catch (Throwable e) {
      String uuid = UUIDConverter.fromUUID(nativeDescriptor.getUuid());
      errorCallback.onError(BleErrorUtils.invalidWriteDataForDescriptor(valueBase64, uuid));
      return;
    }

    final SafeExecutor<Descriptor> safeExecutor = new SafeExecutor<>(successCallback, errorCallback);

    final Disposable subscription = connection
      .writeDescriptor(nativeDescriptor, value)
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(() -> {
        descriptor.logValue("Write to", value);
        descriptor.setValue(value);
        safeExecutor.success(new Descriptor(descriptor));
        pendingTransactions.removeSubscription(transactionId);
      }, error -> {
        safeExecutor.error(errorConverter.toError(error));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  // Mark: Descriptors getters -------------------------------------------------------------------

  private Descriptor getDescriptor(@NonNull final String deviceId,
                                   @NonNull final String serviceUUID,
                                   @NonNull final String characteristicUUID,
                                   @NonNull final String descriptorUUID) throws BleError {
    final UUID[] UUIDs = UUIDConverter.convert(serviceUUID, characteristicUUID, descriptorUUID);
    if (UUIDs == null) {
      throw BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID, descriptorUUID);
    }

    final Device device = connectedDevices.get(deviceId);
    if (device == null) {
      throw BleErrorUtils.deviceNotConnected(deviceId);
    }

    final Service service = device.getServiceByUUID(UUIDs[0]);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(serviceUUID);
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[1]);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(characteristicUUID);
    }

    final Descriptor descriptor = characteristic.getDescriptorByUUID(UUIDs[2]);
    if (descriptor == null) {
      throw BleErrorUtils.descriptorNotFound(descriptorUUID);
    }

    return descriptor;
  }

  private Descriptor getDescriptor(final int serviceIdentifier,
                                   @NonNull final String characteristicUUID,
                                   @NonNull final String descriptorUUID) throws BleError {
    final UUID[] UUIDs = UUIDConverter.convert(characteristicUUID, descriptorUUID);
    if (UUIDs == null) {
      throw BleErrorUtils.invalidIdentifiers(characteristicUUID, descriptorUUID);
    }

    final Service service = discoveredServices.get(serviceIdentifier);
    if (service == null) {
      throw BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier));
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[0]);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(characteristicUUID);
    }

    final Descriptor descriptor = characteristic.getDescriptorByUUID(UUIDs[1]);
    if (descriptor == null) {
      throw BleErrorUtils.descriptorNotFound(descriptorUUID);
    }

    return descriptor;
  }

  private Descriptor getDescriptor(final int characteristicIdentifier,
                                   @NonNull final String descriptorUUID) throws BleError {
    final UUID uuid = UUIDConverter.convert(descriptorUUID);
    if (uuid == null) {
      throw BleErrorUtils.invalidIdentifiers(descriptorUUID);
    }

    final Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier);
    if (characteristic == null) {
      throw BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier));
    }

    final Descriptor descriptor = characteristic.getDescriptorByUUID(uuid);
    if (descriptor == null) {
      throw BleErrorUtils.descriptorNotFound(descriptorUUID);
    }

    return descriptor;
  }

  private Descriptor getDescriptor(final int descriptorIdentifier) throws BleError {

    final Descriptor descriptor = discoveredDescriptors.get(descriptorIdentifier);
    if (descriptor == null) {
      throw BleErrorUtils.descriptorNotFound(Integer.toString(descriptorIdentifier));
    }

    return descriptor;
  }

  @Override
  public void cancelTransaction(String transactionId) {
    pendingTransactions.removeSubscription(transactionId);
  }

  public void setLogLevel(String logLevel) {
    currentLogLevel = LogLevel.toLogLevel(logLevel);
    RxBleLog.setLogLevel(currentLogLevel);
  }

  @Override
  public String getLogLevel() {
    return LogLevel.fromLogLevel(currentLogLevel);
  }

  private Disposable monitorAdapterStateChanges(Context context,
                                                final OnEventCallback<String> onAdapterStateChangeCallback) {
    if (!supportsBluetoothLowEnergy()) {
      return null;
    }

    return new RxBleAdapterStateObservable(context)
      .map(this::mapRxBleAdapterStateToLocalBluetoothState)
      .subscribe(onAdapterStateChangeCallback::onEvent);
  }

  private boolean supportsBluetoothLowEnergy() {
    return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
  }

  @BluetoothState
  private String mapRxBleAdapterStateToLocalBluetoothState(
    RxBleAdapterStateObservable.BleAdapterState rxBleAdapterState
  ) {
    if (rxBleAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_ON) {
      return BluetoothState.POWERED_ON;
    } else if (rxBleAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_OFF) {
      return BluetoothState.POWERED_OFF;
    } else {
      return BluetoothState.RESETTING;
    }
  }

  @SuppressLint("MissingPermission")
  private void changeAdapterState(final RxBleAdapterStateObservable.BleAdapterState desiredAdapterState,
                                  final String transactionId,
                                  final OnSuccessCallback<Void> onSuccessCallback,
                                  final OnErrorCallback onErrorCallback) {
    if (bluetoothManager == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothStateChangeFailed, "BluetoothManager is null", null));
      return;
    }

    final SafeExecutor<Void> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = new RxBleAdapterStateObservable(context)
      .takeUntil(actualAdapterState -> desiredAdapterState == actualAdapterState)
      .firstOrError()
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(state -> {
        safeExecutor.success(null);
        pendingTransactions.removeSubscription(transactionId);
      }, error -> {
        safeExecutor.error(errorConverter.toError(error));
        pendingTransactions.removeSubscription(transactionId);
      });


    boolean desiredAndInitialStateAreSame = false;
    try {
      if (desiredAdapterState == RxBleAdapterStateObservable.BleAdapterState.STATE_ON) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
          if (context instanceof Activity) {
            ((Activity) context).startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
            desiredAndInitialStateAreSame = true;
          }
        } else {
          desiredAndInitialStateAreSame = !bluetoothAdapter.enable();
        }
      } else {
        desiredAndInitialStateAreSame = !bluetoothAdapter.disable();
      }
    } catch (SecurityException e) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        onErrorCallback.onError(new BleError(
          BleErrorCode.BluetoothUnauthorized,
          "Method requires BLUETOOTH_CONNECT permission",
          null)
        );
      } else {
        onErrorCallback.onError(new BleError(
          BleErrorCode.BluetoothUnauthorized,
          "Method requires BLUETOOTH_ADMIN permission",
          null)
        );
      }
    } catch (Exception e) {
      onErrorCallback.onError(new BleError(
        BleErrorCode.BluetoothStateChangeFailed,
        String.format("Couldn't set bluetooth adapter state because of: %s", e.getMessage() != null ? e.getMessage() : "unknown error"),
        null)
      );
    }
    if (desiredAndInitialStateAreSame) {
      subscription.dispose();
      onErrorCallback.onError(new BleError(
        BleErrorCode.BluetoothStateChangeFailed,
        String.format("Couldn't set bluetooth adapter state to %s", desiredAdapterState.toString()),
        null));
    } else {
      pendingTransactions.replaceSubscription(transactionId, subscription);
    }
  }

  @BluetoothState
  private String mapNativeAdapterStateToLocalBluetoothState(int adapterState) {
    switch (adapterState) {
      case BluetoothAdapter.STATE_OFF:
        return BluetoothState.POWERED_OFF;
      case BluetoothAdapter.STATE_ON:
        return BluetoothState.POWERED_ON;
      case BluetoothAdapter.STATE_TURNING_OFF:
        // fallthrough
      case BluetoothAdapter.STATE_TURNING_ON:
        return BluetoothState.RESETTING;
      default:
        return BluetoothState.UNKNOWN;
    }
  }

  private void safeStartDeviceScan(final UUID[] uuids,
                                   final int scanMode,
                                   final int callbackType,
                                   final boolean legacyScan,
                                   final OnEventCallback<ScanResult> onEventCallback,
                                   final OnErrorCallback onErrorCallback) {
    if (rxBleClient == null) {
      onErrorCallback.onError(new BleError(BleErrorCode.BluetoothManagerDestroyed, "BleManager not created when tried to start device scan", null));
      return;
    }

    ScanSettings scanSettings = new ScanSettings.Builder()
      .setScanMode(scanMode)
      .setCallbackType(callbackType)
      .setLegacy(legacyScan)
      .build();

    int length = uuids == null ? 0 : uuids.length;
    ScanFilter[] filters = new ScanFilter[length];
    for (int i = 0; i < length; i++) {
      filters[i] = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(uuids[i].toString())).build();
    }

    scanSubscription = rxBleClient
      .scanBleDevices(scanSettings, filters)
      .subscribe(scanResult -> {
        String deviceId = scanResult.getBleDevice().getMacAddress();
        if (!discoveredDevices.containsKey(deviceId)) {
          discoveredDevices.put(deviceId, rxBleDeviceToDeviceMapper.map(scanResult.getBleDevice(), null));
        }
        onEventCallback.onEvent(rxScanResultToScanResultMapper.map(scanResult));
      }, throwable -> onErrorCallback.onError(errorConverter.toError(throwable)));
  }

  @NonNull
  private Device getDeviceById(@NonNull final String deviceId) throws BleError {
    final Device device = connectedDevices.get(deviceId);
    if (device == null) {
      throw BleErrorUtils.deviceNotConnected(deviceId);
    }
    return device;
  }

  @Nullable
  private RxBleConnection getConnectionOrEmitError(@NonNull final String deviceId,
                                                   @NonNull OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = activeConnections.get(deviceId);
    if (connection == null) {
      onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceId));
      return null;
    }
    return connection;
  }

  private void safeConnectToDevice(final RxBleDevice device,
                                   final boolean autoConnect,
                                   final int requestMtu,
                                   final RefreshGattMoment refreshGattMoment,
                                   final Long timeout,
                                   final int connectionPriority,
                                   final OnSuccessCallback<Device> onSuccessCallback,
                                   final OnEventCallback<ConnectionState> onConnectionStateChangedCallback,
                                   final OnErrorCallback onErrorCallback) {

    final SafeExecutor<Device> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);
    Observable<RxBleConnection> connect = device
      .establishConnection(autoConnect)
      .doOnSubscribe(disposable -> onConnectionStateChangedCallback.onEvent(ConnectionState.CONNECTING))
      .doFinally(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        onDeviceDisconnected(device);
        onConnectionStateChangedCallback.onEvent(ConnectionState.DISCONNECTED);
      });

    if (refreshGattMoment == RefreshGattMoment.ON_CONNECTED) {
      connect = connect.flatMap(rxBleConnection -> rxBleConnection
        .queue(new RefreshGattCustomOperation())
        .map(refreshGattSuccess -> rxBleConnection));
    }

    if (connectionPriority > 0) {
      connect = connect.flatMap(rxBleConnection -> rxBleConnection
        .requestConnectionPriority(connectionPriority, 1, TimeUnit.MILLISECONDS)
        .andThen(Observable.just(rxBleConnection))
      );
    }

    if (requestMtu > 0) {
      connect = connect.flatMap(rxBleConnection ->
        rxBleConnection.requestMtu(requestMtu)
          .map(integer -> rxBleConnection)
          .toObservable()
      );
    }

    if (timeout != null) {
      connect = connect.timeout(timeout, TimeUnit.MILLISECONDS);
    }


    final Disposable subscription = connect
      .subscribe(rxBleConnection -> {
        Device localDevice = rxBleDeviceToDeviceMapper.map(device, rxBleConnection);
        onConnectionStateChangedCallback.onEvent(ConnectionState.CONNECTED);
        cleanServicesAndCharacteristicsForDevice(localDevice);
        connectedDevices.put(device.getMacAddress(), localDevice);
        activeConnections.put(device.getMacAddress(), rxBleConnection);
        safeExecutor.success(localDevice);
      }, error -> {
        BleError bleError = errorConverter.toError(error);
        safeExecutor.error(bleError);
        onDeviceDisconnected(device);
      });

    connectingDevices.replaceSubscription(device.getMacAddress(), subscription);
  }

  private void onDeviceDisconnected(RxBleDevice rxDevice) {
    activeConnections.remove(rxDevice.getMacAddress());
    Device device = connectedDevices.remove(rxDevice.getMacAddress());
    if (device == null) {
      return;
    }

    cleanServicesAndCharacteristicsForDevice(device);
    connectingDevices.removeSubscription(device.getId());
  }

  private void safeDiscoverAllServicesAndCharacteristicsForDevice(final Device device,
                                                                  final String transactionId,
                                                                  final OnSuccessCallback<Device> onSuccessCallback,
                                                                  final OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = getConnectionOrEmitError(device.getId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Device> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .discoverServices()
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(rxBleDeviceServices -> {
        ArrayList<Service> services = new ArrayList<>();
        for (BluetoothGattService gattService : rxBleDeviceServices.getBluetoothGattServices()) {
          Service service = serviceFactory.create(device.getId(), gattService);
          discoveredServices.put(service.getId(), service);
          services.add(service);

          for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
            Characteristic characteristic = new Characteristic(service, gattCharacteristic);
            discoveredCharacteristics.put(characteristic.getId(), characteristic);

            for (BluetoothGattDescriptor gattDescriptor : gattCharacteristic.getDescriptors()) {
              Descriptor descriptor = new Descriptor(characteristic, gattDescriptor);
              discoveredDescriptors.put(descriptor.getId(), descriptor);
            }
          }
        }
        device.setServices(services);
        // Moved from onSuccess method from old RxJava1 implementation
        safeExecutor.success(device);
        pendingTransactions.removeSubscription(transactionId);
      }, throwable -> {
        safeExecutor.error(errorConverter.toError(throwable));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  private void safeReadCharacteristicForDevice(final Characteristic characteristic,
                                               final String transactionId,
                                               final OnSuccessCallback<Characteristic> onSuccessCallback,
                                               final OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Characteristic> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .readCharacteristic(characteristic.gattCharacteristic)
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(bytes -> {
        characteristic.logValue("Read from", bytes);
        characteristic.setValue(bytes);
        safeExecutor.success(new Characteristic(characteristic));
        pendingTransactions.removeSubscription(transactionId);
      }, throwable -> {
        safeExecutor.error(errorConverter.toError(throwable));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  private void writeCharacteristicWithValue(final Characteristic characteristic,
                                            final String valueBase64,
                                            final Boolean response,
                                            final String transactionId,
                                            OnSuccessCallback<Characteristic> onSuccessCallback,
                                            OnErrorCallback onErrorCallback) {
    final byte[] value;
    try {
      value = Base64Converter.decode(valueBase64);
    } catch (Throwable error) {
      onErrorCallback.onError(
        BleErrorUtils.invalidWriteDataForCharacteristic(valueBase64,
          UUIDConverter.fromUUID(characteristic.getUuid())));
      return;
    }

    characteristic.setWriteType(response ?
      BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT :
      BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

    safeWriteCharacteristicForDevice(
      characteristic,
      value,
      transactionId,
      onSuccessCallback,
      onErrorCallback);
  }

  private void safeWriteCharacteristicForDevice(final Characteristic characteristic,
                                                final byte[] value,
                                                final String transactionId,
                                                final OnSuccessCallback<Characteristic> onSuccessCallback,
                                                final OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Characteristic> safeExecutor = new SafeExecutor<>(onSuccessCallback, onErrorCallback);

    final Disposable subscription = connection
      .writeCharacteristic(characteristic.gattCharacteristic, value)
      .doOnDispose(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .subscribe(bytes -> {
        characteristic.logValue("Write to", bytes);
        characteristic.setValue(bytes);
        safeExecutor.success(new Characteristic(characteristic));
        pendingTransactions.removeSubscription(transactionId);
      }, throwable -> {
        safeExecutor.error(errorConverter.toError(throwable));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  private void safeMonitorCharacteristicForDevice(final Characteristic characteristic,
                                                  final String transactionId,
                                                  final OnEventCallback<Characteristic> onEventCallback,
                                                  final OnErrorCallback onErrorCallback) {
    final RxBleConnection connection = getConnectionOrEmitError(characteristic.getDeviceId(), onErrorCallback);
    if (connection == null) {
      return;
    }

    final SafeExecutor<Void> safeExecutor = new SafeExecutor<>(null, onErrorCallback);

    final Disposable subscription = Observable.defer(() -> {
        BluetoothGattDescriptor cccDescriptor = characteristic.getGattDescriptor(Constants.CLIENT_CHARACTERISTIC_CONFIG_UUID);
        NotificationSetupMode setupMode = cccDescriptor != null
          ? NotificationSetupMode.QUICK_SETUP
          : NotificationSetupMode.COMPAT;
        if (characteristic.isNotifiable()) {
          return connection.setupNotification(characteristic.gattCharacteristic, setupMode);
        }

        if (characteristic.isIndicatable()) {
          return connection.setupIndication(characteristic.gattCharacteristic, setupMode);
        }

        return Observable.error(new CannotMonitorCharacteristicException(characteristic));
      })
      .flatMap(observable -> observable)
      .toFlowable(BackpressureStrategy.BUFFER)
      .observeOn(Schedulers.computation())
      .doOnCancel(() -> {
        safeExecutor.error(BleErrorUtils.cancelled());
        pendingTransactions.removeSubscription(transactionId);
      })
      .doOnComplete(() -> pendingTransactions.removeSubscription(transactionId))
      .subscribe(bytes -> {
        // -- custom 处理OTA逻辑
        if (ota_ing) {
          displayData(bytes);
        } else {
          characteristic.logValue("Notification from", bytes);
          characteristic.setValue(bytes);
          onEventCallback.onEvent(new Characteristic(characteristic));
        }
      }, throwable -> {
        safeExecutor.error(errorConverter.toError(throwable));
        pendingTransactions.removeSubscription(transactionId);
      });

    pendingTransactions.replaceSubscription(transactionId, subscription);
  }

  @Nullable
  private Characteristic getCharacteristicOrEmitError(@NonNull final String deviceId,
                                                      @NonNull final String serviceUUID,
                                                      @NonNull final String characteristicUUID,
                                                      @NonNull final OnErrorCallback onErrorCallback) {

    final UUID[] UUIDs = UUIDConverter.convert(serviceUUID, characteristicUUID);
    if (UUIDs == null) {
      onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(serviceUUID, characteristicUUID));
      return null;
    }

    final Device device = connectedDevices.get(deviceId);
    if (device == null) {
      onErrorCallback.onError(BleErrorUtils.deviceNotConnected(deviceId));
      return null;
    }

    final Service service = device.getServiceByUUID(UUIDs[0]);
    if (service == null) {
      onErrorCallback.onError(BleErrorUtils.serviceNotFound(serviceUUID));
      return null;
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(UUIDs[1]);
    if (characteristic == null) {
      onErrorCallback.onError(BleErrorUtils.characteristicNotFound(characteristicUUID));
      return null;
    }

    return characteristic;
  }

  @Nullable
  private Characteristic getCharacteristicOrEmitError(final int serviceIdentifier,
                                                      @NonNull final String characteristicUUID,
                                                      @NonNull final OnErrorCallback onErrorCallback) {

    final UUID uuid = UUIDConverter.convert(characteristicUUID);
    if (uuid == null) {
      onErrorCallback.onError(BleErrorUtils.invalidIdentifiers(characteristicUUID));
      return null;
    }

    final Service service = discoveredServices.get(serviceIdentifier);
    if (service == null) {
      onErrorCallback.onError(BleErrorUtils.serviceNotFound(Integer.toString(serviceIdentifier)));
      return null;
    }

    final Characteristic characteristic = service.getCharacteristicByUUID(uuid);
    if (characteristic == null) {
      onErrorCallback.onError(BleErrorUtils.characteristicNotFound(characteristicUUID));
      return null;
    }

    return characteristic;
  }

  @Nullable
  private Characteristic getCharacteristicOrEmitError(final int characteristicIdentifier,
                                                      @NonNull final OnErrorCallback onErrorCallback) {

    final Characteristic characteristic = discoveredCharacteristics.get(characteristicIdentifier);
    if (characteristic == null) {
      onErrorCallback.onError(BleErrorUtils.characteristicNotFound(Integer.toString(characteristicIdentifier)));
      return null;
    }

    return characteristic;
  }

  private void cleanServicesAndCharacteristicsForDevice(@NonNull Device device) {
    for (int i = discoveredServices.size() - 1; i >= 0; i--) {
      int key = discoveredServices.keyAt(i);
      Service service = discoveredServices.get(key);

      if (service.getDeviceID().equals(device.getId())) {
        discoveredServices.remove(key);
      }
    }
    for (int i = discoveredCharacteristics.size() - 1; i >= 0; i--) {
      int key = discoveredCharacteristics.keyAt(i);
      Characteristic characteristic = discoveredCharacteristics.get(key);

      if (characteristic.getDeviceId().equals(device.getId())) {
        discoveredCharacteristics.remove(key);
      }
    }

    for (int i = discoveredDescriptors.size() - 1; i >= 0; i--) {
      int key = discoveredDescriptors.keyAt(i);
      Descriptor descriptor = discoveredDescriptors.get(key);
      if (descriptor.getDeviceId().equals(device.getId())) {
        discoveredDescriptors.remove(key);
      }
    }
  }
}
