public class ViseBle {
    private Context context;//上下文
    private BluetoothManager bluetoothManager;//蓝牙管理
    private BluetoothAdapter bluetoothAdapter;//蓝牙适配器
    private DeviceMirrorPool deviceMirrorPool;//设备连接池
    private DeviceMirror lastDeviceMirror;//上次操作设备镜像

    private static ViseBle instance;//入口操作管理
    private static BleConfig bleConfig = BleConfig.getInstance();

    /**
     * 单例方式获取蓝牙通信入口
     *
     * @return 返回ViseBluetooth
     */
    public static ViseBle getInstance() {
        if (instance == null) {
            synchronized (ViseBle.class) {
                if (instance == null) {
                    instance = new ViseBle();
                }
            }
        }
        return instance;
    }

    private ViseBle() {
    }

    /**
     * 获取配置对象，可进行相关配置的修改
     *
     * @return
     */
    public static BleConfig config() {
        return bleConfig;
    }

    /**
     * 初始化
     *
     * @param context 上下文
     */
    public void init(Context context) {
        if (this.context == null && context != null) {
            this.context = context.getApplicationContext();
            bluetoothManager = (BluetoothManager) this.context.getSystemService(Context.BLUETOOTH_SERVICE);
            bluetoothAdapter = bluetoothManager.getAdapter();
            deviceMirrorPool = new DeviceMirrorPool();
        }
    }

    /**
     * 开始扫描
     *
     * @param scanCallback 自定义回调
     */
    public void startScan(ScanCallback scanCallback) {
        if (scanCallback == null) {
            throw new IllegalArgumentException("this ScanCallback is Null!");
        }
        scanCallback.setScan(true).scan();
    }

    /**
     * 停止扫描
     *
     * @param scanCallback 自定义回调
     */
    public void stopScan(ScanCallback scanCallback) {
        if (scanCallback == null) {
            throw new IllegalArgumentException("this ScanCallback is Null!");
        }
        scanCallback.setScan(false).removeHandlerMsg().scan();
    }

    /**
     * 连接设备
     *
     * @param bluetoothLeDevice
     * @param connectCallback
     */
    public void connect(BluetoothLeDevice bluetoothLeDevice, IConnectCallback connectCallback) {
        if (bluetoothLeDevice == null || connectCallback == null) {
            ViseLog.e("This bluetoothLeDevice or connectCallback is null.");
            return;
        }
        if (deviceMirrorPool != null && !deviceMirrorPool.isContainDevice(bluetoothLeDevice)) {
            DeviceMirror deviceMirror = new DeviceMirror(bluetoothLeDevice);
            if (lastDeviceMirror != null && !TextUtils.isEmpty(lastDeviceMirror.getUniqueSymbol())
                    && lastDeviceMirror.getUniqueSymbol().equals(deviceMirror.getUniqueSymbol())) {
                deviceMirror = lastDeviceMirror;//防止重复创建设备镜像
            }
            deviceMirror.connect(connectCallback);
            lastDeviceMirror = deviceMirror;
        } else {
            ViseLog.i("This device is connected.");
        }
    }

    /**
     * 连接指定mac地址的设备
     *
     * @param mac             设备mac地址
     * @param connectCallback 连接回调
     */
    public void connectByMac(String mac, final IConnectCallback connectCallback) {
        if (mac == null || connectCallback == null) {
            ViseLog.e("This mac or connectCallback is null.");
            return;
        }
        startScan(new SingleFilterScanCallback(new IScanCallback() {
            @Override
            public void onDeviceFound(BluetoothLeDevice bluetoothLeDevice) {

            }

            @Override
            public void onScanFinish(final BluetoothLeDeviceStore bluetoothLeDeviceStore) {
                if (bluetoothLeDeviceStore.getDeviceList().size() > 0) {
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            connect(bluetoothLeDeviceStore.getDeviceList().get(0), connectCallback);
                        }
                    });
                } else {
                    connectCallback.onConnectFailure(new TimeoutException());
                }
            }

            @Override
            public void onScanTimeout() {
                connectCallback.onConnectFailure(new TimeoutException());
            }

        }).setDeviceMac(mac));
    }

    /**
     * 连接指定设备名称的设备
     *
     * @param name            设备名称
     * @param connectCallback 连接回调
     */
    public void connectByName(String name, final IConnectCallback connectCallback) {
        if (name == null || connectCallback == null) {
            ViseLog.e("This name or connectCallback is null.");
            return;
        }
        startScan(new SingleFilterScanCallback(new IScanCallback() {
            @Override
            public void onDeviceFound(BluetoothLeDevice bluetoothLeDevice) {

            }

            @Override
            public void onScanFinish(final BluetoothLeDeviceStore bluetoothLeDeviceStore) {
                if (bluetoothLeDeviceStore.getDeviceList().size() > 0) {
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            connect(bluetoothLeDeviceStore.getDeviceList().get(0), connectCallback);
                        }
                    });
                } else {
                    connectCallback.onConnectFailure(new TimeoutException());
                }
            }

            @Override
            public void onScanTimeout() {
                connectCallback.onConnectFailure(new TimeoutException());
            }

        }).setDeviceName(name));
    }

    /**
     * 获取连接池中的设备镜像，如果没有连接则返回空
     *
     * @param bluetoothLeDevice
     * @return
     */
    public DeviceMirror getDeviceMirror(BluetoothLeDevice bluetoothLeDevice) {
        if (deviceMirrorPool != null) {
            return deviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        }
        return null;
    }

    /**
     * 获取该设备连接状态
     *
     * @param bluetoothLeDevice
     * @return
     */
    public ConnectState getConnectState(BluetoothLeDevice bluetoothLeDevice) {
        if (deviceMirrorPool != null) {
            return deviceMirrorPool.getConnectState(bluetoothLeDevice);
        }
        return ConnectState.CONNECT_DISCONNECT;
    }

    /**
     * 判断该设备是否已连接
     *
     * @param bluetoothLeDevice
     * @return
     */
    public boolean isConnect(BluetoothLeDevice bluetoothLeDevice) {
        if (deviceMirrorPool != null) {
            return deviceMirrorPool.isContainDevice(bluetoothLeDevice);
        }
        return false;
    }

    /**
     * 断开某一个设备
     *
     * @param bluetoothLeDevice
     */
    public void disconnect(BluetoothLeDevice bluetoothLeDevice) {
        if (deviceMirrorPool != null) {
            deviceMirrorPool.disconnect(bluetoothLeDevice);
        }
    }

    /**
     * 断开所有设备
     */
    public void disconnect() {
        if (deviceMirrorPool != null) {
            deviceMirrorPool.disconnect();
        }
    }

    /**
     * 清除资源，在退出应用时调用
     */
    public void clear() {
        if (deviceMirrorPool != null) {
            deviceMirrorPool.clear();
        }
    }

    /**
     * 获取Context
     *
     * @return 返回Context
     */
    public Context getContext() {
        return context;
    }

    /**
     * 获取蓝牙管理
     *
     * @return 返回蓝牙管理
     */
    public BluetoothManager getBluetoothManager() {
        return bluetoothManager;
    }

    /**
     * 获取蓝牙适配器
     *
     * @return 返回蓝牙适配器
     */
    public BluetoothAdapter getBluetoothAdapter() {
        return bluetoothAdapter;
    }

    /**
     * 获取设备镜像池
     *
     * @return
     */
    public DeviceMirrorPool getDeviceMirrorPool() {
        return deviceMirrorPool;
    }

    /**
     * 获取当前连接失败重试次数
     *
     * @return
     */
    public int getConnectRetryCount() {
        if (lastDeviceMirror == null) {
            return 0;
        }
        return lastDeviceMirror.getConnectRetryCount();
    }

    /**
     * 获取当前读取数据失败重试次数
     *
     * @return
     */
    public int getReadDataRetryCount() {
        if (lastDeviceMirror == null) {
            return 0;
        }
        return lastDeviceMirror.getReadDataRetryCount();
    }

    /**
     * 获取当前使能数据失败重试次数
     *
     * @return
     */
    public int getReceiveDataRetryCount() {
        if (lastDeviceMirror == null) {
            return 0;
        }
        return lastDeviceMirror.getReceiveDataRetryCount();
    }

    /**
     * 获取当前写入数据失败重试次数
     *
     * @return
     */
    public int getWriteDataRetryCount() {
        if (lastDeviceMirror == null) {
            return 0;
        }
        return lastDeviceMirror.getWriteDataRetryCount();
    }
} public enum ConnectState {
    CONNECT_INIT(-1),           //连接初始化
    CONNECT_PROCESS(0x00),      //连接中
    CONNECT_SUCCESS(0x01),      //连接成功
    CONNECT_FAILURE(0x02),      //连接失败
    CONNECT_TIMEOUT(0x03),      //连接超时
    CONNECT_DISCONNECT(0x04);   //连接断开

    private int code;

    ConnectState(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
} public enum PropertyType {
    PROPERTY_READ(0x01),
    PROPERTY_WRITE(0x02),
    PROPERTY_NOTIFY(0x04),
    PROPERTY_INDICATE(0x08);

    private int propertyValue;

    PropertyType(int propertyValue) {
        this.propertyValue = propertyValue;
    }

    public int getPropertyValue() {
        return propertyValue;
    }
} public class DeviceMirror {
    private final DeviceMirror deviceMirror;
    private final String uniqueSymbol;//唯一符号
    private final BluetoothLeDevice bluetoothLeDevice;//设备基础信息

    private BluetoothGatt bluetoothGatt;//蓝牙GATT
    private IRssiCallback rssiCallback;//获取信号值回调
    private IConnectCallback connectCallback;//连接回调
    private int connectRetryCount = 0;//当前连接重试次数
    private int writeDataRetryCount = 0;//当前写入数据重试次数
    private int readDataRetryCount = 0;//当前读取数据重试次数
    private int receiveDataRetryCount = 0;//当前接收数据重试次数
    private boolean isActiveDisconnect = false;//是否主动断开连接
    private boolean isIndication;//是否是指示器方式
    private boolean enable;//是否设置使能
    private byte[] writeData;//写入数据
    private ConnectState connectState = ConnectState.CONNECT_INIT;//设备状态描述
    private volatile HashMap<String, BluetoothGattChannel> writeInfoMap = new HashMap<>();//写入数据GATT信息集合
    private volatile HashMap<String, BluetoothGattChannel> readInfoMap = new HashMap<>();//读取数据GATT信息集合
    private volatile HashMap<String, BluetoothGattChannel> enableInfoMap = new HashMap<>();//设置使能GATT信息集合
    private volatile HashMap<String, IBleCallback> bleCallbackMap = new HashMap<>();//数据操作回调集合
    private volatile HashMap<String, IBleCallback> receiveCallbackMap = new HashMap<>();//数据接收回调集合

    private final Handler handler = new Handler(Looper.myLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG_CONNECT_TIMEOUT) {
                connectFailure(new TimeoutException());
            } else if (msg.what == MSG_CONNECT_RETRY) {
                connect();
            } else if (msg.what == MSG_WRITE_DATA_TIMEOUT) {
                writeFailure(new TimeoutException(), true);
            } else if (msg.what == MSG_WRITE_DATA_RETRY) {
                write(writeData);
            } else if (msg.what == MSG_READ_DATA_TIMEOUT) {
                readFailure(new TimeoutException(), true);
            } else if (msg.what == MSG_READ_DATA_RETRY) {
                read();
            } else if (msg.what == MSG_RECEIVE_DATA_TIMEOUT) {
                enableFailure(new TimeoutException(), true);
            } else if (msg.what == MSG_RECEIVE_DATA_RETRY) {
                enable(enable, isIndication);
            }
        }
    };

    /**
     * 蓝牙所有相关操作的核心回调类
     */
    private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() {

        /**
         * 连接状态改变，主要用来分析设备的连接与断开
         * @param gatt GATT
         * @param status 改变前状态
         * @param newState 改变后状态
         */
        @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
            ViseLog.i("onConnectionStateChange  status: " + status + " ,newState: " + newState +
                    "  ,thread: " + Thread.currentThread());
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                gatt.discoverServices();
            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                close();
                if (connectCallback != null) {
                    if (handler != null) {
                        handler.removeCallbacksAndMessages(null);
                    }
                    ViseBle.getInstance().getDeviceMirrorPool().removeDeviceMirror(deviceMirror);
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        connectState = ConnectState.CONNECT_DISCONNECT;
                        connectCallback.onDisconnect(isActiveDisconnect);
                    } else {
                        connectState = ConnectState.CONNECT_FAILURE;
                        connectCallback.onConnectFailure(new ConnectException(gatt, status));
                    }
                }
            } else if (newState == BluetoothGatt.STATE_CONNECTING) {
                connectState = ConnectState.CONNECT_PROCESS;
            }
        }

        /**
         * 发现服务，主要用来获取设备支持的服务列表
         * @param gatt GATT
         * @param status 当前状态
         */
        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
            ViseLog.i("onServicesDiscovered  status: " + status + "  ,thread: " + Thread.currentThread());
            if (handler != null) {
                handler.removeMessages(MSG_CONNECT_TIMEOUT);
            }
            if (status == 0) {
                ViseLog.i("onServicesDiscovered connectSuccess.");
                bluetoothGatt = gatt;
                connectState = ConnectState.CONNECT_SUCCESS;
                if (connectCallback != null) {
                    isActiveDisconnect = false;
                    ViseBle.getInstance().getDeviceMirrorPool().addDeviceMirror(deviceMirror);
                    connectCallback.onConnectSuccess(deviceMirror);
                }
            } else {
                connectFailure(new ConnectException(gatt, status));
            }
        }

        /**
         * 读取特征值，主要用来读取该特征值包含的可读信息
         * @param gatt GATT
         * @param characteristic 特征值
         * @param status 当前状态
         */
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
            ViseLog.i("onCharacteristicRead  status: " + status + ", data:" + HexUtil.encodeHexStr(characteristic.getValue()) +
                    "  ,thread: " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                handleSuccessData(readInfoMap, characteristic.getValue(), status, true);
            } else {
                readFailure(new GattException(status), true);
            }
        }

        /**
         * 写入特征值，主要用来发送数据到设备
         * @param gatt GATT
         * @param characteristic 特征值
         * @param status 当前状态
         */
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
            ViseLog.i("onCharacteristicWrite  status: " + status + ", data:" + HexUtil.encodeHexStr(characteristic.getValue()) +
                    "  ,thread: " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                handleSuccessData(writeInfoMap, characteristic.getValue(), status, false);
            } else {
                writeFailure(new GattException(status), true);
            }
        }

        /**
         * 特征值改变，主要用来接收设备返回的数据信息
         * @param gatt GATT
         * @param characteristic 特征值
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
            ViseLog.i("onCharacteristicChanged data:" + HexUtil.encodeHexStr(characteristic.getValue()) +
                    "  ,thread: " + Thread.currentThread());
            for (Map.Entry<String, IBleCallback> receiveEntry : receiveCallbackMap.entrySet()) {
                String receiveKey = receiveEntry.getKey();
                IBleCallback receiveValue = receiveEntry.getValue();
                for (Map.Entry<String, BluetoothGattChannel> gattInfoEntry : enableInfoMap.entrySet()) {
                    String bluetoothGattInfoKey = gattInfoEntry.getKey();
                    BluetoothGattChannel bluetoothGattInfoValue = gattInfoEntry.getValue();
                    if (receiveKey.equals(bluetoothGattInfoKey)) {
                        receiveValue.onSuccess(characteristic.getValue(), bluetoothGattInfoValue, bluetoothLeDevice);
                    }
                }
            }
        }

        /**
         * 读取属性描述值，主要用来获取设备当前属性描述的值
         * @param gatt GATT
         * @param descriptor 属性描述
         * @param status 当前状态
         */
        @Override
        public void onDescriptorRead(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
            ViseLog.i("onDescriptorRead  status: " + status + ", data:" + HexUtil.encodeHexStr(descriptor.getValue()) +
                    "  ,thread: " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                handleSuccessData(readInfoMap, descriptor.getValue(), status, true);
            } else {
                readFailure(new GattException(status), true);
            }
        }

        /**
         * 写入属性描述值，主要用来根据当前属性描述值写入数据到设备
         * @param gatt GATT
         * @param descriptor 属性描述值
         * @param status 当前状态
         */
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
            ViseLog.i("onDescriptorWrite  status: " + status + ", data:" + HexUtil.encodeHexStr(descriptor.getValue()) +
                    "  ,thread: " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                handleSuccessData(writeInfoMap, descriptor.getValue(), status, false);
            } else {
                writeFailure(new GattException(status), true);
            }
            if (status == BluetoothGatt.GATT_SUCCESS) {
                handleSuccessData(enableInfoMap, descriptor.getValue(), status, false);
            } else {
                enableFailure(new GattException(status), true);
            }
        }

        /**
         * 阅读设备信号值
         * @param gatt GATT
         * @param rssi 设备当前信号
         * @param status 当前状态
         */
        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            ViseLog.i("onReadRemoteRssi  status: " + status + ", rssi:" + rssi +
                    "  ,thread: " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (rssiCallback != null) {
                    rssiCallback.onSuccess(rssi);
                }
            } else {
                if (rssiCallback != null) {
                    rssiCallback.onFailure(new GattException(status));
                }
            }
        }
    };

    public DeviceMirror(BluetoothLeDevice bluetoothLeDevice) {
        deviceMirror = this;
        this.bluetoothLeDevice = bluetoothLeDevice;
        this.uniqueSymbol = bluetoothLeDevice.getAddress() + bluetoothLeDevice.getName();
    }

    /**
     * 连接设备
     *
     * @param connectCallback
     */
    public synchronized void connect(IConnectCallback connectCallback) {
        if (connectState == ConnectState.CONNECT_SUCCESS || connectState == ConnectState.CONNECT_PROCESS
                || (connectState == ConnectState.CONNECT_INIT && connectRetryCount != 0)) {
            ViseLog.e("this connect state is connecting, connectSuccess or current retry count less than config connect retry count.");
            return;
        }
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
        this.connectCallback = connectCallback;
        connectRetryCount = 0;
        connect();
    }

    /**
     * 绑定一个具备读写或可通知能力的通道，设置需要操作数据的相关信息，包含：数据操作回调，数据操作类型，数据通道建立所需的UUID。
     *
     * @param bleCallback
     * @param bluetoothGattChannel
     */
    public synchronized void bindChannel(IBleCallback bleCallback, BluetoothGattChannel bluetoothGattChannel) {
        if (bleCallback != null && bluetoothGattChannel != null) {
            String key = bluetoothGattChannel.getGattInfoKey();
            PropertyType propertyType = bluetoothGattChannel.getPropertyType();
            if (!bleCallbackMap.containsKey(key)) {
                bleCallbackMap.put(key, bleCallback);
            }
            if (propertyType == PropertyType.PROPERTY_READ) {
                if (!readInfoMap.containsKey(key)) {
                    readInfoMap.put(key, bluetoothGattChannel);
                }
            } else if (propertyType == PropertyType.PROPERTY_WRITE) {
                if (!writeInfoMap.containsKey(key)) {
                    writeInfoMap.put(key, bluetoothGattChannel);
                }
            } else if (propertyType == PropertyType.PROPERTY_NOTIFY) {
                if (!enableInfoMap.containsKey(key)) {
                    enableInfoMap.put(key, bluetoothGattChannel);
                }
            } else if (propertyType == PropertyType.PROPERTY_INDICATE) {
                if (!enableInfoMap.containsKey(key)) {
                    enableInfoMap.put(key, bluetoothGattChannel);
                }
            }
        }
    }

    /**
     * 解绑通道
     *
     * @param bluetoothGattChannel
     */
    public synchronized void unbindChannel(BluetoothGattChannel bluetoothGattChannel) {
        if (bluetoothGattChannel != null) {
            String key = bluetoothGattChannel.getGattInfoKey();
            if (bleCallbackMap.containsKey(key)) {
                bleCallbackMap.remove(key);
            }
            if (readInfoMap.containsKey(key)) {
                readInfoMap.remove(key);
            } else if (writeInfoMap.containsKey(key)) {
                writeInfoMap.remove(key);
            } else if (enableInfoMap.containsKey(key)) {
                enableInfoMap.remove(key);
            }
        }
    }

    /**
     * 写入数据
     *
     * @param data
     */
    public void writeData(byte[] data) {
        if (data == null || data.length > 20) {
            ViseLog.e("this data is null or length beyond 20 byte.");
            return;
        }
        if (!checkBluetoothGattInfo(writeInfoMap)) {
            return;
        }
        if (handler != null) {
            handler.removeMessages(MSG_WRITE_DATA_TIMEOUT);
            handler.removeMessages(MSG_WRITE_DATA_RETRY);
        }
        writeDataRetryCount = 0;
        writeData = data;
        write(data);
    }

    /**
     * 读取数据
     */
    public void readData() {
        if (!checkBluetoothGattInfo(readInfoMap)) {
            return;
        }
        if (handler != null) {
            handler.removeMessages(MSG_READ_DATA_TIMEOUT);
            handler.removeMessages(MSG_READ_DATA_RETRY);
        }
        readDataRetryCount = 0;
        read();
    }

    /**
     * 获取设备信号值
     *
     * @param rssiCallback
     */
    public void readRemoteRssi(IRssiCallback rssiCallback) {
        this.rssiCallback = rssiCallback;
        if (bluetoothGatt != null) {
            bluetoothGatt.readRemoteRssi();
        }
    }

    /**
     * 注册获取数据通知
     *
     * @param isIndication
     */
    public void registerNotify(boolean isIndication) {
        if (!checkBluetoothGattInfo(enableInfoMap)) {
            return;
        }
        if (handler != null) {
            handler.removeMessages(MSG_RECEIVE_DATA_TIMEOUT);
            handler.removeMessages(MSG_RECEIVE_DATA_RETRY);
        }
        receiveDataRetryCount = 0;
        enable = true;
        this.isIndication = isIndication;
        enable(enable, this.isIndication);
    }

    /**
     * 取消获取数据通知
     *
     * @param isIndication
     */
    public void unregisterNotify(boolean isIndication) {
        if (!checkBluetoothGattInfo(enableInfoMap)) {
            return;
        }
        if (handler != null) {
            handler.removeMessages(MSG_RECEIVE_DATA_TIMEOUT);
            handler.removeMessages(MSG_RECEIVE_DATA_RETRY);
        }
        enable = false;
        this.isIndication = isIndication;
        enable(enable, this.isIndication);
    }

    /**
     * 设置接收数据监听
     *
     * @param key             接收数据回调key，由serviceUUID+characteristicUUID+descriptorUUID组成
     * @param receiveCallback 接收数据回调
     */
    public void setNotifyListener(String key, IBleCallback receiveCallback) {
        receiveCallbackMap.put(key, receiveCallback);
    }

    /**
     * 获取当前连接失败重试次数
     *
     * @return
     */
    public int getConnectRetryCount() {
        return connectRetryCount;
    }

    /**
     * 获取当前读取数据失败重试次数
     *
     * @return
     */
    public int getReadDataRetryCount() {
        return readDataRetryCount;
    }

    /**
     * 获取当前使能数据失败重试次数
     *
     * @return
     */
    public int getReceiveDataRetryCount() {
        return receiveDataRetryCount;
    }

    /**
     * 获取当前写入数据失败重试次数
     *
     * @return
     */
    public int getWriteDataRetryCount() {
        return writeDataRetryCount;
    }

    /**
     * 获取设备唯一标识
     *
     * @return
     */
    public String getUniqueSymbol() {
        return uniqueSymbol;
    }

    /**
     * 获取蓝牙GATT
     *
     * @return 返回蓝牙GATT
     */
    public BluetoothGatt getBluetoothGatt() {
        return bluetoothGatt;
    }

    /**
     * 获取设备连接状态
     *
     * @return 返回设备连接状态
     */
    public ConnectState getConnectState() {
        return connectState;
    }

    /**
     * 获取服务列表
     *
     * @return
     */
    public List<BluetoothGattService> getGattServiceList() {
        if (bluetoothGatt != null) {
            return bluetoothGatt.getServices();
        }
        return null;
    }

    /**
     * 根据服务UUID获取指定服务
     *
     * @param serviceUuid
     * @return
     */
    public BluetoothGattService getGattService(UUID serviceUuid) {
        if (bluetoothGatt != null && serviceUuid != null) {
            return bluetoothGatt.getService(serviceUuid);
        }
        return null;
    }

    /**
     * 获取某个服务的特征值列表
     *
     * @param serviceUuid
     * @return
     */
    public List<BluetoothGattCharacteristic> getGattCharacteristicList(UUID serviceUuid) {
        if (getGattService(serviceUuid) != null && serviceUuid != null) {
            return getGattService(serviceUuid).getCharacteristics();
        }
        return null;
    }

    /**
     * 根据特征值UUID获取某个服务的指定特征值
     *
     * @param serviceUuid
     * @param characteristicUuid
     * @return
     */
    public BluetoothGattCharacteristic getGattCharacteristic(UUID serviceUuid, UUID characteristicUuid) {
        if (getGattService(serviceUuid) != null && serviceUuid != null && characteristicUuid != null) {
            return getGattService(serviceUuid).getCharacteristic(characteristicUuid);
        }
        return null;
    }

    /**
     * 获取某个特征值的描述属性列表
     *
     * @param serviceUuid
     * @param characteristicUuid
     * @return
     */
    public List<BluetoothGattDescriptor> getGattDescriptorList(UUID serviceUuid, UUID characteristicUuid) {
        if (getGattCharacteristic(serviceUuid, characteristicUuid) != null && serviceUuid != null && characteristicUuid != null) {
            return getGattCharacteristic(serviceUuid, characteristicUuid).getDescriptors();
        }
        return null;
    }

    /**
     * 根据描述属性UUID获取某个特征值的指定属性值
     *
     * @param serviceUuid
     * @param characteristicUuid
     * @param descriptorUuid
     * @return
     */
    public BluetoothGattDescriptor getGattDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid) {
        if (getGattCharacteristic(serviceUuid, characteristicUuid) != null && serviceUuid != null && characteristicUuid != null && descriptorUuid != null) {
            return getGattCharacteristic(serviceUuid, characteristicUuid).getDescriptor(descriptorUuid);
        }
        return null;
    }

    /**
     * 获取设备详细信息
     *
     * @return
     */
    public BluetoothLeDevice getBluetoothLeDevice() {
        return bluetoothLeDevice;
    }

    /**
     * 设备是否连接
     *
     * @return
     */
    public boolean isConnected() {
        return connectState == ConnectState.CONNECT_SUCCESS;
    }

    /**
     * 移除数据操作回调
     *
     * @param key
     */
    public synchronized void removeBleCallback(String key) {
        if (bleCallbackMap.containsKey(key)) {
            bleCallbackMap.remove(key);
        }
    }

    /**
     * 移除接收数据回调
     *
     * @param key
     */
    public synchronized void removeReceiveCallback(String key) {
        if (receiveCallbackMap.containsKey(key)) {
            receiveCallbackMap.remove(key);
        }
    }

    /**
     * 移除所有回调
     */
    public synchronized void removeAllCallback() {
        bleCallbackMap.clear();
        receiveCallbackMap.clear();
    }

    /**
     * 刷新设备缓存
     *
     * @return 返回是否刷新成功
     */
    public synchronized boolean refreshDeviceCache() {
        try {
            final Method refresh = BluetoothGatt.class.getMethod("refresh");
            if (refresh != null && bluetoothGatt != null) {
                final boolean success = (Boolean) refresh.invoke(getBluetoothGatt());
                ViseLog.i("Refreshing result: " + success);
                return success;
            }
        } catch (Exception e) {
            ViseLog.e("An exception occured while refreshing device" + e);
        }
        return false;
    }

    /**
     * 主动断开设备连接
     */
    public synchronized void disconnect() {
        connectState = ConnectState.CONNECT_INIT;
        connectRetryCount = 0;
        if (bluetoothGatt != null) {
            isActiveDisconnect = true;
            bluetoothGatt.disconnect();
        }
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
    }

    /**
     * 关闭GATT
     */
    public synchronized void close() {
        if (bluetoothGatt != null) {
            bluetoothGatt.close();
        }
    }

    @Override
    public String toString() {
        return "DeviceMirror{" +
                "bluetoothLeDevice=" + bluetoothLeDevice +
                ", uniqueSymbol='" + uniqueSymbol + '\'' +
                '}';
    }

    /**
     * 清除设备资源，在不使用该设备时调用
     */
    public synchronized void clear() {
        ViseLog.i("deviceMirror clear.");
        disconnect();
        refreshDeviceCache();
        close();
        if (bleCallbackMap != null) {
            bleCallbackMap.clear();
        }
        if (receiveCallbackMap != null) {
            receiveCallbackMap.clear();
        }
        if (writeInfoMap != null) {
            writeInfoMap.clear();
        }
        if (readInfoMap != null) {
            readInfoMap.clear();
        }
        if (enableInfoMap != null) {
            enableInfoMap.clear();
        }
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
    }

    /**
     * UUID转换
     *
     * @param uuid
     * @return 返回UUID
     */
    private UUID formUUID(String uuid) {
        return uuid == null ? null : UUID.fromString(uuid);
    }

    /**
     * 检查BluetoothGattChannel集合是否有值
     *
     * @param bluetoothGattInfoHashMap
     * @return
     */
    private boolean checkBluetoothGattInfo(HashMap<String, BluetoothGattChannel> bluetoothGattInfoHashMap) {
        if (bluetoothGattInfoHashMap == null || bluetoothGattInfoHashMap.size() == 0) {
            ViseLog.e("this bluetoothGattInfo map is not value.");
            return false;
        }
        return true;
    }

    /**
     * 连接设备
     */
    private synchronized void connect() {
        if (handler != null) {
            handler.removeMessages(MSG_CONNECT_TIMEOUT);
            handler.sendEmptyMessageDelayed(MSG_CONNECT_TIMEOUT, BleConfig.getInstance().getConnectTimeout());
        }
        connectState = ConnectState.CONNECT_PROCESS;
        if (bluetoothLeDevice != null && bluetoothLeDevice.getDevice() != null) {
            bluetoothLeDevice.getDevice().connectGatt(ViseBle.getInstance().getContext(), false, coreGattCallback);
        }
    }

    /**
     * 设置使能
     *
     * @param enable       是否具备使能
     * @param isIndication 是否是指示器方式
     * @return
     */
    private synchronized boolean enable(boolean enable, boolean isIndication) {
        if (handler != null) {
            handler.removeMessages(MSG_RECEIVE_DATA_TIMEOUT);
            handler.sendEmptyMessageDelayed(MSG_RECEIVE_DATA_TIMEOUT, BleConfig.getInstance().getOperateTimeout());
        }
        boolean success = false;
        for (Map.Entry<String, BluetoothGattChannel> entry : enableInfoMap.entrySet()) {
            String bluetoothGattInfoKey = entry.getKey();
            BluetoothGattChannel bluetoothGattInfoValue = entry.getValue();
            if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null) {
                success = bluetoothGatt.setCharacteristicNotification(bluetoothGattInfoValue.getCharacteristic(), enable);
            }
            BluetoothGattDescriptor bluetoothGattDescriptor = null;
            if (bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() != null) {
                bluetoothGattDescriptor = bluetoothGattInfoValue.getDescriptor();
            } else if (bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() == null) {
                if (bluetoothGattInfoValue.getCharacteristic().getDescriptors() != null
                        && bluetoothGattInfoValue.getCharacteristic().getDescriptors().size() == 1) {
                    bluetoothGattDescriptor = bluetoothGattInfoValue.getCharacteristic().getDescriptors().get(0);
                } else {
                    bluetoothGattDescriptor = bluetoothGattInfoValue.getCharacteristic()
                            .getDescriptor(UUID.fromString(BleConstant.CLIENT_CHARACTERISTIC_CONFIG));
                }
            }
            if (bluetoothGattDescriptor != null) {
                bluetoothGattInfoValue.setDescriptor(bluetoothGattDescriptor);
                if (isIndication) {
                    if (enable) {
                        bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                    } else {
                        bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                    }
                } else {
                    if (enable) {
                        bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    } else {
                        bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                    }
                }
                if (bluetoothGatt != null) {
                    bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
                }
            }
        }
        return success;
    }

    /**
     * 读取数据
     *
     * @return
     */
    private synchronized boolean read() {
        if (handler != null) {
            handler.removeMessages(MSG_READ_DATA_TIMEOUT);
            handler.sendEmptyMessageDelayed(MSG_READ_DATA_TIMEOUT, BleConfig.getInstance().getOperateTimeout());
        }
        boolean success = false;
        for (Map.Entry<String, BluetoothGattChannel> entry : readInfoMap.entrySet()) {
            String bluetoothGattInfoKey = entry.getKey();
            BluetoothGattChannel bluetoothGattInfoValue = entry.getValue();
            if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() != null) {
                success = bluetoothGatt.readDescriptor(bluetoothGattInfoValue.getDescriptor());
            } else if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() == null) {
                success = bluetoothGatt.readCharacteristic(bluetoothGattInfoValue.getCharacteristic());
            }
        }
        return success;
    }

    /**
     * 写入数据
     *
     * @param data
     * @return
     */
    private synchronized boolean write(byte[] data) {
        if (handler != null) {
            handler.removeMessages(MSG_WRITE_DATA_TIMEOUT);
            handler.sendEmptyMessageDelayed(MSG_WRITE_DATA_TIMEOUT, BleConfig.getInstance().getOperateTimeout());
        }
        boolean success = false;
        for (Map.Entry<String, BluetoothGattChannel> entry : writeInfoMap.entrySet()) {
            String bluetoothGattInfoKey = entry.getKey();
            BluetoothGattChannel bluetoothGattInfoValue = entry.getValue();
            if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() != null) {
                bluetoothGattInfoValue.getDescriptor().setValue(data);
                success = bluetoothGatt.writeDescriptor(bluetoothGattInfoValue.getDescriptor());
            } else if (bluetoothGatt != null && bluetoothGattInfoValue.getCharacteristic() != null && bluetoothGattInfoValue.getDescriptor() == null) {
                bluetoothGattInfoValue.getCharacteristic().setValue(data);
                success = bluetoothGatt.writeCharacteristic(bluetoothGattInfoValue.getCharacteristic());
            }
        }
        return success;
    }

    /**
     * 连接失败处理
     *
     * @param bleException 回调异常
     */
    private void connectFailure(BleException bleException) {
        if (connectRetryCount < BleConfig.getInstance().getConnectRetryCount()) {
            connectRetryCount++;
            if (handler != null) {
                handler.removeMessages(MSG_CONNECT_TIMEOUT);
                handler.sendEmptyMessageDelayed(MSG_CONNECT_RETRY, BleConfig.getInstance().getConnectRetryInterval());
            }
            ViseLog.i("connectFailure connectRetryCount is " + connectRetryCount);
        } else {
            if (bleException instanceof TimeoutException) {
                connectState = ConnectState.CONNECT_TIMEOUT;
            } else {
                connectState = ConnectState.CONNECT_FAILURE;
            }
            close();
            if (connectCallback != null) {
                connectCallback.onConnectFailure(bleException);
            }
            ViseLog.i("connectFailure " + bleException);
        }
    }

    /**
     * 使能失败
     *
     * @param bleException
     * @param isRemoveCall
     */
    private void enableFailure(BleException bleException, boolean isRemoveCall) {
        if (receiveDataRetryCount < BleConfig.getInstance().getOperateRetryCount()) {
            receiveDataRetryCount++;
            if (handler != null) {
                handler.removeMessages(MSG_RECEIVE_DATA_TIMEOUT);
                handler.sendEmptyMessageDelayed(MSG_RECEIVE_DATA_RETRY, BleConfig.getInstance().getOperateRetryInterval());
            }
            ViseLog.i("enableFailure receiveDataRetryCount is " + receiveDataRetryCount);
        } else {
            handleFailureData(enableInfoMap, bleException, isRemoveCall);
            ViseLog.i("enableFailure " + bleException);
        }
    }

    /**
     * 读取数据失败
     *
     * @param bleException
     * @param isRemoveCall
     */
    private void readFailure(BleException bleException, boolean isRemoveCall) {
        if (readDataRetryCount < BleConfig.getInstance().getOperateRetryCount()) {
            readDataRetryCount++;
            if (handler != null) {
                handler.removeMessages(MSG_READ_DATA_TIMEOUT);
                handler.sendEmptyMessageDelayed(MSG_READ_DATA_RETRY, BleConfig.getInstance().getOperateRetryInterval());
            }
            ViseLog.i("readFailure readDataRetryCount is " + readDataRetryCount);
        } else {
            handleFailureData(readInfoMap, bleException, isRemoveCall);
            ViseLog.i("readFailure " + bleException);
        }
    }

    /**
     * 写入数据失败
     *
     * @param bleException
     * @param isRemoveCall
     */
    private void writeFailure(BleException bleException, boolean isRemoveCall) {
        if (writeDataRetryCount < BleConfig.getInstance().getOperateRetryCount()) {
            writeDataRetryCount++;
            if (handler != null) {
                handler.removeMessages(MSG_WRITE_DATA_TIMEOUT);
                handler.sendEmptyMessageDelayed(MSG_WRITE_DATA_RETRY, BleConfig.getInstance().getOperateRetryInterval());
            }
            ViseLog.i("writeFailure writeDataRetryCount is " + writeDataRetryCount);
        } else {
            handleFailureData(writeInfoMap, bleException, isRemoveCall);
            ViseLog.i("writeFailure " + bleException);
        }
    }

    /**
     * 处理数据发送成功
     *
     * @param bluetoothGattInfoHashMap
     * @param value                    待发送数据
     * @param status                   发送数据状态
     * @param isRemoveCall             是否需要移除回调
     */
    private synchronized void handleSuccessData(HashMap<String, BluetoothGattChannel> bluetoothGattInfoHashMap, byte[] value, int status,
                                                boolean isRemoveCall) {
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
        String removeBleCallbackKey = null;
        String removeBluetoothGattInfoKey = null;
        for (Map.Entry<String, IBleCallback> callbackEntry : bleCallbackMap.entrySet()) {
            String bleCallbackKey = callbackEntry.getKey();
            IBleCallback bleCallbackValue = callbackEntry.getValue();
            for (Map.Entry<String, BluetoothGattChannel> gattInfoEntry : bluetoothGattInfoHashMap.entrySet()) {
                String bluetoothGattInfoKey = gattInfoEntry.getKey();
                BluetoothGattChannel bluetoothGattInfoValue = gattInfoEntry.getValue();
                if (bleCallbackKey.equals(bluetoothGattInfoKey)) {
                    bleCallbackValue.onSuccess(value, bluetoothGattInfoValue, bluetoothLeDevice);
                    removeBleCallbackKey = bleCallbackKey;
                    removeBluetoothGattInfoKey = bluetoothGattInfoKey;
                }
            }
        }
        synchronized (bleCallbackMap) {
            if (isRemoveCall && removeBleCallbackKey != null && removeBluetoothGattInfoKey != null) {
                bleCallbackMap.remove(removeBleCallbackKey);
                bluetoothGattInfoHashMap.remove(removeBluetoothGattInfoKey);
            }
        }
    }

    /**
     * 处理数据发送失败
     *
     * @param bluetoothGattInfoHashMap
     * @param bleExceprion             回调异常
     * @param isRemoveCall             是否需要移除回调
     */
    private synchronized void handleFailureData(HashMap<String, BluetoothGattChannel> bluetoothGattInfoHashMap, BleException bleExceprion,
                                                boolean isRemoveCall) {
        if (handler != null) {
            handler.removeCallbacksAndMessages(null);
        }
        String removeBleCallbackKey = null;
        String removeBluetoothGattInfoKey = null;
        for (Map.Entry<String, IBleCallback> callbackEntry : bleCallbackMap.entrySet()) {
            String bleCallbackKey = callbackEntry.getKey();
            IBleCallback bleCallbackValue = callbackEntry.getValue();
            for (Map.Entry<String, BluetoothGattChannel> gattInfoEntry : bluetoothGattInfoHashMap.entrySet()) {
                String bluetoothGattInfoKey = gattInfoEntry.getKey();
                if (bleCallbackKey.equals(bluetoothGattInfoKey)) {
                    bleCallbackValue.onFailure(bleExceprion);
                    removeBleCallbackKey = bleCallbackKey;
                    removeBluetoothGattInfoKey = bluetoothGattInfoKey;
                }
            }
        }
        synchronized (bleCallbackMap) {
            if (isRemoveCall && removeBleCallbackKey != null && removeBluetoothGattInfoKey != null) {
                bleCallbackMap.remove(removeBleCallbackKey);
                bluetoothGattInfoHashMap.remove(removeBluetoothGattInfoKey);
            }
        }
    }
} public class BluetoothLeDevice implements Parcelable {

    /**
     * The Constant CREATOR.
     */
    public static final Creator<BluetoothLeDevice> CREATOR = new Creator<BluetoothLeDevice>() {
        public BluetoothLeDevice createFromParcel(final Parcel in) {
            return new BluetoothLeDevice(in);
        }

        public BluetoothLeDevice[] newArray(final int size) {
            return new BluetoothLeDevice[size];
        }
    };
    protected static final int MAX_RSSI_LOG_SIZE = 10;
    private static final String PARCEL_EXTRA_BLUETOOTH_DEVICE = "bluetooth_device";
    private static final String PARCEL_EXTRA_CURRENT_RSSI = "current_rssi";
    private static final String PARCEL_EXTRA_CURRENT_TIMESTAMP = "current_timestamp";
    private static final String PARCEL_EXTRA_DEVICE_RSSI_LOG = "device_rssi_log";
    private static final String PARCEL_EXTRA_DEVICE_SCANRECORD = "device_scanrecord";
    private static final String PARCEL_EXTRA_DEVICE_SCANRECORD_STORE = "device_scanrecord_store";
    private static final String PARCEL_EXTRA_FIRST_RSSI = "device_first_rssi";
    private static final String PARCEL_EXTRA_FIRST_TIMESTAMP = "first_timestamp";
    private static final long LOG_INVALIDATION_THRESHOLD = 10 * 1000;
    private final AdRecordStore mRecordStore;
    private final BluetoothDevice mDevice;
    private final Map<Long, Integer> mRssiLog;
    private final byte[] mScanRecord;
    private final int mFirstRssi;
    private final long mFirstTimestamp;
    private int mCurrentRssi;
    private long mCurrentTimestamp;
    private transient Set<BluetoothServiceType> mServiceSet;

    /**
     * Instantiates a new Bluetooth LE device.
     *
     * @param device     a standard android Bluetooth device
     * @param rssi       the RSSI value of the Bluetooth device
     * @param scanRecord the scan record of the device
     * @param timestamp  the timestamp of the RSSI reading
     */
    public BluetoothLeDevice(final BluetoothDevice device, final int rssi, final byte[] scanRecord, final long timestamp) {
        mDevice = device;
        mFirstRssi = rssi;
        mFirstTimestamp = timestamp;
        mRecordStore = new AdRecordStore(AdRecordUtil.parseScanRecordAsSparseArray(scanRecord));
        mScanRecord = scanRecord;
        mRssiLog = new LinkedHashMap<>(MAX_RSSI_LOG_SIZE);
        updateRssiReading(timestamp, rssi);
    }

    /**
     * Instantiates a new Bluetooth LE device.
     *
     * @param device the device
     */
    public BluetoothLeDevice(final BluetoothLeDevice device) {
        mCurrentRssi = device.getRssi();
        mCurrentTimestamp = device.getTimestamp();
        mDevice = device.getDevice();
        mFirstRssi = device.getFirstRssi();
        mFirstTimestamp = device.getFirstTimestamp();
        mRecordStore = new AdRecordStore(AdRecordUtil.parseScanRecordAsSparseArray(device.getScanRecord()));
        mRssiLog = device.getRssiLog();
        mScanRecord = device.getScanRecord();
    }

    /**
     * Instantiates a new bluetooth le device.
     *
     * @param in the in
     */
    protected BluetoothLeDevice(final Parcel in) {
        final Bundle b = in.readBundle(getClass().getClassLoader());

        mCurrentRssi = b.getInt(PARCEL_EXTRA_CURRENT_RSSI, 0);
        mCurrentTimestamp = b.getLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, 0);
        mDevice = b.getParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE);
        mFirstRssi = b.getInt(PARCEL_EXTRA_FIRST_RSSI, 0);
        mFirstTimestamp = b.getLong(PARCEL_EXTRA_FIRST_TIMESTAMP, 0);
        mRecordStore = b.getParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE);
        mRssiLog = (Map<Long, Integer>) b.getSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG);
        mScanRecord = b.getByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD);
    }

    /**
     * Adds the to rssi log.
     *
     * @param timestamp   the timestamp
     * @param rssiReading the rssi reading
     */
    private void addToRssiLog(final long timestamp, final int rssiReading) {
        synchronized (mRssiLog) {
            if (timestamp - mCurrentTimestamp > LOG_INVALIDATION_THRESHOLD) {
                mRssiLog.clear();
            }

            mCurrentRssi = rssiReading;
            mCurrentTimestamp = timestamp;
            mRssiLog.put(timestamp, rssiReading);
        }
    }

    /* (non-Javadoc)
     * @see android.os.Parcelable#describeContents()
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final BluetoothLeDevice other = (BluetoothLeDevice) obj;
        if (mCurrentRssi != other.mCurrentRssi) return false;
        if (mCurrentTimestamp != other.mCurrentTimestamp) return false;
        if (mDevice == null) {
            if (other.mDevice != null) return false;
        } else if (!mDevice.equals(other.mDevice)) return false;
        if (mFirstRssi != other.mFirstRssi) return false;
        if (mFirstTimestamp != other.mFirstTimestamp) return false;
        if (mRecordStore == null) {
            if (other.mRecordStore != null) return false;
        } else if (!mRecordStore.equals(other.mRecordStore)) return false;
        if (mRssiLog == null) {
            if (other.mRssiLog != null) return false;
        } else if (!mRssiLog.equals(other.mRssiLog)) return false;
        if (Arrays.equals(mScanRecord, other.mScanRecord)) {
            return true;
        }
        return false;
    }

    /**
     * Gets the ad record store.
     *
     * @return the ad record store
     */
    public AdRecordStore getAdRecordStore() {
        return mRecordStore;
    }

    /**
     * Gets the address.
     *
     * @return the address
     */
    public String getAddress() {
        return mDevice.getAddress();
    }

    /**
     * Gets the bluetooth device bond state.
     *
     * @return the bluetooth device bond state
     */
    public String getBluetoothDeviceBondState() {
        return resolveBondingState(mDevice.getBondState());
    }

    /**
     * Gets the bluetooth device class name.
     *
     * @return the bluetooth device class name
     */
    public String getBluetoothDeviceClassName() {
        return BluetoothClassResolver.resolveDeviceClass(mDevice.getBluetoothClass().getDeviceClass());
    }

    public Set<BluetoothServiceType> getBluetoothDeviceKnownSupportedServices() {
        if (mServiceSet == null) {
            synchronized (this) {
                if (mServiceSet == null) {
                    final Set<BluetoothServiceType> serviceSet = new HashSet<>();
                    for (final BluetoothServiceType service : BluetoothServiceType.values()) {

                        if (mDevice.getBluetoothClass().hasService(service.getCode())) {
                            serviceSet.add(service);
                        }
                    }
                    mServiceSet = Collections.unmodifiableSet(serviceSet);
                }
            }
        }

        return mServiceSet;
    }

    /**
     * Gets the bluetooth device major class name.
     *
     * @return the bluetooth device major class name
     */
    public String getBluetoothDeviceMajorClassName() {
        return BluetoothClassResolver.resolveMajorDeviceClass(mDevice.getBluetoothClass().getMajorDeviceClass());
    }

    /**
     * Gets the device.
     *
     * @return the device
     */
    public BluetoothDevice getDevice() {
        return mDevice;
    }

    /**
     * Gets the first rssi.
     *
     * @return the first rssi
     */
    public int getFirstRssi() {
        return mFirstRssi;
    }

    /**
     * Gets the first timestamp.
     *
     * @return the first timestamp
     */
    public long getFirstTimestamp() {
        return mFirstTimestamp;
    }

    /**
     * Gets the name.
     *
     * @return the name
     */
    public String getName() {
        return mDevice.getName();
    }

    /**
     * Gets the rssi.
     *
     * @return the rssi
     */
    public int getRssi() {
        return mCurrentRssi;
    }

    /**
     * Gets the rssi log.
     *
     * @return the rssi log
     */
    protected Map<Long, Integer> getRssiLog() {
        synchronized (mRssiLog) {
            return mRssiLog;
        }
    }

    /**
     * Gets the running average rssi.
     *
     * @return the running average rssi
     */
    public double getRunningAverageRssi() {
        int sum = 0;
        int count = 0;

        synchronized (mRssiLog) {

            for (final Long aLong : mRssiLog.keySet()) {
                count++;
                sum += mRssiLog.get(aLong);
            }
        }

        if (count > 0) {
            return sum / count;
        } else {
            return 0;
        }

    }

    /**
     * 获取major
     * @return major
     */
    public long getMajor(){
        return HexUtil.byteToLong(HexUtil.subBytes(mScanRecord,25,2),0,2,true);
    }

    /**
     * 获取minor
     * @return minor
     */
    public long minor(){
        return HexUtil.byteToLong(HexUtil.subBytes(mScanRecord,27,2),0,2,true);
    }

    /**
     * Gets the scan record.
     *
     * @return the scan record
     */
    public byte[] getScanRecord() {
        return mScanRecord;
    }

    /**
     * Gets the timestamp.
     *
     * @return the timestamp
     */
    public long getTimestamp() {
        return mCurrentTimestamp;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + mCurrentRssi;
        result = prime * result + (int) (mCurrentTimestamp ^ (mCurrentTimestamp >>> 32));
        result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode());
        result = prime * result + mFirstRssi;
        result = prime * result + (int) (mFirstTimestamp ^ (mFirstTimestamp >>> 32));
        result = prime * result + ((mRecordStore == null) ? 0 : mRecordStore.hashCode());
        result = prime * result + ((mRssiLog == null) ? 0 : mRssiLog.hashCode());
        result = prime * result + Arrays.hashCode(mScanRecord);
        return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "BluetoothLeDevice [mDevice=" + mDevice + ", " +
                "mRssi=" + mFirstRssi + ", mScanRecord=" + HexUtil.encodeHexStr(mScanRecord) +
                ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" +
                getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" +
                getBluetoothDeviceClassName() + "]";
    }

    /**
     * Update rssi reading.
     *
     * @param timestamp   the timestamp
     * @param rssiReading the rssi reading
     */
    public void updateRssiReading(final long timestamp, final int rssiReading) {
        addToRssiLog(timestamp, rssiReading);
    }

    /* (non-Javadoc)
     * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
     */
    @Override
    public void writeToParcel(final Parcel parcel, final int arg1) {
        final Bundle b = new Bundle(getClass().getClassLoader());

        b.putByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD, mScanRecord);

        b.putInt(PARCEL_EXTRA_FIRST_RSSI, mFirstRssi);
        b.putInt(PARCEL_EXTRA_CURRENT_RSSI, mCurrentRssi);

        b.putLong(PARCEL_EXTRA_FIRST_TIMESTAMP, mFirstTimestamp);
        b.putLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, mCurrentTimestamp);

        b.putParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE, mDevice);
        b.putParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE, mRecordStore);
        b.putSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG, (Serializable) mRssiLog);

        parcel.writeBundle(b);
    }

    /**
     * Resolve bonding state.
     *
     * @param bondState the bond state
     * @return the string
     */
    private static String resolveBondingState(final int bondState) {
        switch (bondState) {
            case BluetoothDevice.BOND_BONDED://已配对
                return "Paired";
            case BluetoothDevice.BOND_BONDING://配对中
                return "Pairing";
            case BluetoothDevice.BOND_NONE://未配对
                return "UnBonded";
            default:
                return "Unknown";//未知状态
        }
    }
} public class GattAttributeResolver {
    public static final String BASE_GUID = "00000000-0000-1000-8000-00805f9b34fb";
    public static final String SERVICE_DISCOVERY_PROTOCOL_SDP = "00000001-0000-1000-8000-00805f9b34fb";
    public static final String USER_DATAGRAM_PROTOCOL_UDP = "00000002-0000-1000-8000-00805f9b34fb";
    public static final String RADIO_FREQUENCY_COMMUNICATION_PROTOCOL_RFCOMM = "00000003-0000-1000-8000-00805f9b34fb";
    public static final String TCP = "00000004-0000-1000-8000-00805f9b34fb";
    public static final String TCSBIN = "00000005-0000-1000-8000-00805f9b34fb";
    public static final String TCSAT = "00000006-0000-1000-8000-00805f9b34fb";
    public static final String OBJECT_EXCHANGE_PROTOCOL_OBEX = "00000008-0000-1000-8000-00805f9b34fb";
    public static final String IP = "00000009-0000-1000-8000-00805f9b34fb";
    public static final String FTP = "0000000a-0000-1000-8000-00805f9b34fb";
    public static final String HTTP = "0000000c-0000-1000-8000-00805f9b34fb";
    public static final String WSP = "0000000e-0000-1000-8000-00805f9b34fb";
    public static final String BNEP_SVC = "0000000f-0000-1000-8000-00805f9b34fb";
    public static final String UPNP_PROTOCOL = "00000010-0000-1000-8000-00805f9b34fb";
    public static final String HIDP = "00000011-0000-1000-8000-00805f9b34fb";
    public static final String HARDCOPY_CONTROL_CHANNEL_PROTOCOL = "00000012-0000-1000-8000-00805f9b34fb";
    public static final String HARDCOPY_DATA_CHANNEL_PROTOCOL = "00000014-0000-1000-8000-00805f9b34fb";
    public static final String HARDCOPY_NOTIFICATION_PROTOCOL = "00000016-0000-1000-8000-00805f9b34fb";
    public static final String VCTP_PROTOCOL = "00000017-0000-1000-8000-00805f9b34fb";
    public static final String VDTP_PROTOCOL = "00000019-0000-1000-8000-00805f9b34fb";
    public static final String CMPT_PROTOCOL = "0000001b-0000-1000-8000-00805f9b34fb";
    public static final String UDI_C_PLANE_PROTOCOL = "0000001d-0000-1000-8000-00805f9b34fb";
    public static final String MCAP_CONTROL_CHANNEL = "0000001e-0000-1000-8000-00805f9b34fb";
    public static final String MCAP_DATA_CHANNEL = "0000001f-0000-1000-8000-00805f9b34fb";
    public static final String L2CAP = "00000100-0000-1000-8000-00805f9b34fb";
    public static final String SERVICE_DISCOVERY_SERVER = "00001000-0000-1000-8000-00805f9b34fb";
    public static final String BROWSE_GROUP_DESCRIPTOR = "00001001-0000-1000-8000-00805f9b34fb";
    public static final String PUBLIC_BROWSE_GROUP = "00001002-0000-1000-8000-00805f9b34fb";
    public static final String SPP = "00001101-0000-1000-8000-00805f9b34fb";
    public static final String LAN_ACCESS_USING_PPP = "00001102-0000-1000-8000-00805f9b34fb";
    public static final String DUN_GW = "00001103-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_SYNC = "00001104-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_OBJECT_PUSH = "00001105-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_FILE_TRANSFER = "00001106-0000-1000-8000-00805f9b34fb";
    public static final String IRMC_SYNC_COMMAND = "00001107-0000-1000-8000-00805f9b34fb";
    public static final String HSP_HS = "00001108-0000-1000-8000-00805f9b34fb";
    public static final String CORDLESS_TELEPHONY = "00001109-0000-1000-8000-00805f9b34fb";
    public static final String AUDIO_SOURCE = "0000110a-0000-1000-8000-00805f9b34fb";
    public static final String AUDIO_SINK = "0000110b-0000-1000-8000-00805f9b34fb";
    public static final String AV_REMOTE_CONTROL_TARGET = "0000110c-0000-1000-8000-00805f9b34fb";
    public static final String ADVANCED_AUDIO = "0000110d-0000-1000-8000-00805f9b34fb";
    public static final String AVRCP_REMOTE = "0000110e-0000-1000-8000-00805f9b34fb";
    public static final String VIDEO_CONFERENCING = "0000110f-0000-1000-8000-00805f9b34fb";
    public static final String INTERCOM = "00001110-0000-1000-8000-00805f9b34fb";
    public static final String FAX = "00001111-0000-1000-8000-00805f9b34fb";
    public static final String HEADSET_PROFILE_HSP_AUDIO_GATEWAY = "00001112-0000-1000-8000-00805f9b34fb";
    public static final String WAP = "00001113-0000-1000-8000-00805f9b34fb";
    public static final String WAP_CLIENT = "00001114-0000-1000-8000-00805f9b34fb";
    public static final String PANU = "00001115-0000-1000-8000-00805f9b34fb";
    public static final String NAP = "00001116-0000-1000-8000-00805f9b34fb";
    public static final String GN = "00001117-0000-1000-8000-00805f9b34fb";
    public static final String DIRECT_PRINTING = "00001118-0000-1000-8000-00805f9b34fb";
    public static final String REFERENCE_PRINTING = "00001119-0000-1000-8000-00805f9b34fb";
    public static final String IMAGING = "0000111a-0000-1000-8000-00805f9b34fb";
    public static final String IMAGING_RESPONDER = "0000111b-0000-1000-8000-00805f9b34fb";
    public static final String IMAGING_AUTOMATIC_ARCHIVE = "0000111c-0000-1000-8000-00805f9b34fb";
    public static final String IMAGING_REFERENCE_OBJECTS = "0000111d-0000-1000-8000-00805f9b34fb";
    public static final String HANDS_FREE_PROFILE_HFP = "0000111e-0000-1000-8000-00805f9b34fb";
    public static final String HANDS_FREE_PROFILE_HFP_AUDIO_GATEWAY = "0000111f-0000-1000-8000-00805f9b34fb";
    public static final String DIRECT_PRINTING_REFERENCE_OBJECTS = "00001120-0000-1000-8000-00805f9b34fb";
    public static final String REFLECTED_UI = "00001121-0000-1000-8000-00805f9b34fb";
    public static final String BASIC_PRINTING = "00001122-0000-1000-8000-00805f9b34fb";
    public static final String PRINTING_STATUS = "00001123-0000-1000-8000-00805f9b34fb";
    public static final String HID = "00001124-0000-1000-8000-00805f9b34fb";
    public static final String HARDCOPY_CABLE_REPLACEMENT = "00001125-0000-1000-8000-00805f9b34fb";
    public static final String HCR_PRINT = "00001126-0000-1000-8000-00805f9b34fb";
    public static final String HCR_SCAN = "00001127-0000-1000-8000-00805f9b34fb";
    public static final String COMMON_ISDN_ACCESS = "00001128-0000-1000-8000-00805f9b34fb";
    public static final String VIDEO_CONFERENCING_GATEWAY = "00001129-0000-1000-8000-00805f9b34fb";
    public static final String UDIMT = "0000112a-0000-1000-8000-00805f9b34fb";
    public static final String UDITA = "0000112b-0000-1000-8000-00805f9b34fb";
    public static final String AUDIO_VIDEO = "0000112c-0000-1000-8000-00805f9b34fb";
    public static final String SIM_ACCESS = "0000112d-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_PCE = "0000112e-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_PSE = "0000112f-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_PBAP = "00001130-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_MAS = "00001132-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_MNS = "00001133-0000-1000-8000-00805f9b34fb";
    public static final String OBEX_MAP = "00001134-0000-1000-8000-00805f9b34fb";
    public static final String PNP = "00001200-0000-1000-8000-00805f9b34fb";
    public static final String GENERIC_NETWORKING = "00001201-0000-1000-8000-00805f9b34fb";
    public static final String GENERIC_FILE_TRANSFER = "00001202-0000-1000-8000-00805f9b34fb";
    public static final String GENERIC_AUDIO = "00001203-0000-1000-8000-00805f9b34fb";
    public static final String GENERIC_TELEPHONY = "00001204-0000-1000-8000-00805f9b34fb";
    public static final String UPNP = "00001205-0000-1000-8000-00805f9b34fb";
    public static final String UPNP_IP = "00001206-0000-1000-8000-00805f9b34fb";
    public static final String ESDP_UPNP_IP_PAN = "00001300-0000-1000-8000-00805f9b34fb";
    public static final String ESDP_UPNP_IP_LAP = "00001301-0000-1000-8000-00805f9b34fb";
    public static final String ESDP_UPNP_L2CAP = "00001302-0000-1000-8000-00805f9b34fb";
    public static final String VIDEO_DISTRIBUTION_PROFILE_VDP_SOURCE = "00001303-0000-1000-8000-00805f9b34fb";
    public static final String VIDEO_DISTRIBUTION_PROFILE_VDP_SINK = "00001304-0000-1000-8000-00805f9b34fb";
    public static final String VIDEO_DISTRIBUTION_PROFILE_VDP = "00001305-0000-1000-8000-00805f9b34fb";
    public static final String HEALTH_DEVICE_PROFILE_HDP = "00001400-0000-1000-8000-00805f9b34fb";
    public static final String HEALTH_DEVICE_PROFILE_HDP_SOURCE = "00001401-0000-1000-8000-00805f9b34fb";
    public static final String HEALTH_DEVICE_PROFILE_HDP_SINK = "00001402-0000-1000-8000-00805f9b34fb";
    public static final String GAP = "00001800-0000-1000-8000-00805f9b34fb";
    public static final String GATT = "00001801-0000-1000-8000-00805f9b34fb";
    public static final String IMMEDIATE_ALERT = "00001802-0000-1000-8000-00805f9b34fb";
    public static final String LINK_LOSS = "00001803-0000-1000-8000-00805f9b34fb";
    public static final String TX_POWER = "00001804-0000-1000-8000-00805f9b34fb";
    public static final String HEALTH_THERMOMETER = "00001809-0000-1000-8000-00805f9b34fb";
    public static final String DEVICE_INFORMATION = "0000180a-0000-1000-8000-00805f9b34fb";
    public static final String HEART_RATE = "0000180d-0000-1000-8000-00805f9b34fb";
    public static final String CYCLING_SC = "00001816-0000-1000-8000-00805f9b34fb";
    public static final String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
    public static final String DEVICE_NAME = "00002a00-0000-1000-8000-00805f9b34fb";
    public static final String APPEARANCE = "00002a01-0000-1000-8000-00805f9b34fb";
    public static final String PERIPHERAL_PRIVACY_FLAG = "00002a02-0000-1000-8000-00805f9b34fb";
    public static final String RECONNECTION_ADDRESS = "00002a03-0000-1000-8000-00805f9b34fb";
    public static final String PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = "00002a04-0000-1000-8000-00805f9b34fb";
    public static final String SERVICE_CHANGED = "00002a05-0000-1000-8000-00805f9b34fb";
    public static final String ALERT_LEVEL = "00002a06-0000-1000-8000-00805f9b34fb";
    public static final String TX_POWER_LEVEL = "00002a07-0000-1000-8000-00805f9b34fb";
    public static final String DATE_TIME = "00002a08-0000-1000-8000-00805f9b34fb";
    public static final String DAY_OF_WEEK = "00002a09-0000-1000-8000-00805f9b34fb";
    public static final String DAY_DATE_TIME = "00002a0a-0000-1000-8000-00805f9b34fb";
    public static final String EXACT_TIME_256 = "00002a0c-0000-1000-8000-00805f9b34fb";
    public static final String DST_OFFSET = "00002a0d-0000-1000-8000-00805f9b34fb";
    public static final String TIME_ZONE = "00002a0e-0000-1000-8000-00805f9b34fb";
    public static final String LOCAL_TIME_INFORMATION = "00002a0f-0000-1000-8000-00805f9b34fb";
    public static final String TIME_WITH_DST = "00002a11-0000-1000-8000-00805f9b34fb";
    public static final String TIME_ACCURACY = "00002a12-0000-1000-8000-00805f9b34fb";
    public static final String TIME_SOURCE = "00002a13-0000-1000-8000-00805f9b34fb";
    public static final String REFERENCE_TIME_INFORMATION = "00002a14-0000-1000-8000-00805f9b34fb";
    public static final String TIME_UPDATE_CONTROL_POINT = "00002a16-0000-1000-8000-00805f9b34fb";
    public static final String TIME_UPDATE_STATE = "00002a17-0000-1000-8000-00805f9b34fb";
    public static final String TEMPERATURE_MEASUREMENT = "00002a1c-0000-1000-8000-00805f9b34fb";
    public static final String TEMPERATURE_TYPE = "00002a1d-0000-1000-8000-00805f9b34fb";
    public static final String INTERMEDIATE_TEMPERATURE = "00002a1e-0000-1000-8000-00805f9b34fb";
    public static final String MEASUREMENT_INTERVAL = "00002a21-0000-1000-8000-00805f9b34fb";
    public static final String SYSTEM_ID = "00002a23-0000-1000-8000-00805f9b34fb";
    public static final String MODEL_NUMBER_STRING = "00002a24-0000-1000-8000-00805f9b34fb";
    public static final String SERIAL_NUMBER_STRING = "00002a25-0000-1000-8000-00805f9b34fb";
    public static final String FIRMWARE_REVISION_STRING = "00002a26-0000-1000-8000-00805f9b34fb";
    public static final String HARDWARE_REVISION_STRING = "00002a27-0000-1000-8000-00805f9b34fb";
    public static final String SOFTWARE_REVISION_STRING = "00002a28-0000-1000-8000-00805f9b34fb";
    public static final String MANUFACTURER_NAME_STRING = "00002a29-0000-1000-8000-00805f9b34fb";
    public static final String IEEE_1107320601_REGULATORY = "00002a2a-0000-1000-8000-00805f9b34fb";
    public static final String CURRENT_TIME = "00002a2b-0000-1000-8000-00805f9b34fb";
    public static final String BLOOD_PRESSURE_MEASUREMENT = "00002a35-0000-1000-8000-00805f9b34fb";
    public static final String INTERMEDIATE_CUFF_PRESSURE = "00002a36-0000-1000-8000-00805f9b34fb";
    public static final String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
    public static final String BODY_SENSOR_LOCATION = "00002a38-0000-1000-8000-00805f9b34fb";
    public static final String HEART_RATE_CONTROL_POINT = "00002a39-0000-1000-8000-00805f9b34fb";
    public static final String ALERT_STATUS = "00002a3f-0000-1000-8000-00805f9b34fb";
    public static final String RINGER_CONTROL_POINT = "00002a40-0000-1000-8000-00805f9b34fb";
    public static final String RINGER_SETTING = "00002a41-0000-1000-8000-00805f9b34fb";
    public static final String ALERT_CATEGORY_ID_BIT_MASK = "00002a42-0000-1000-8000-00805f9b34fb";
    public static final String ALERT_CATEGORY_ID = "00002a43-0000-1000-8000-00805f9b34fb";
    public static final String ALERT_NOTIFICATION_CONTROL_POINT = "00002a44-0000-1000-8000-00805f9b34fb";
    public static final String UNREAD_ALERT_STATUS = "00002a45-0000-1000-8000-00805f9b34fb";
    public static final String NEW_ALERT = "00002a46-0000-1000-8000-00805f9b34fb";
    public static final String SUPPORTED_NEW_ALERT_CATEGORY = "00002a47-0000-1000-8000-00805f9b34fb";
    public static final String SUPPORTED_UNREAD_ALERT_CATEGORY = "00002a48-0000-1000-8000-00805f9b34fb";
    public static final String BLOOD_PRESSURE_FEATURE = "00002a49-0000-1000-8000-00805f9b34fb";
    public static final String PNPID = "00002a50-0000-1000-8000-00805f9b34fb";
    public static final String SC_CONTROL_POINT = "00002a55-0000-1000-8000-00805f9b34fb";
    public static final String CSC_MEASUREMENT = "00002a5b-0000-1000-8000-00805f9b34fb";
    public static final String CSC_FEATURE = "00002a5c-0000-1000-8000-00805f9b34fb";
    public static final String SENSOR_LOCATION = "00002a5d-0000-1000-8000-00805f9b34fb";
    public static final String ACTIVESYNC = "831c4071-7bc8-4a9c-a01c-15df25a4adbc";
    public static final String ESTIMOTE_SERVICE = "b9403000-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_UUID = "b9403003-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_MAJOR = "b9403001-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_MINOR = "b9403002-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_BATTERY = "b9403041-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_TEMPERATURE = "b9403021-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_POWER = "b9403011-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_ADVERTISING_INTERVAL = "b9403012-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_VERSION_SERVICE = "b9404000-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_SOFTWARE_VERSION = "b9404001-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_HARDWARE_VERSION = "b9404002-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_AUTHENTICATION_SERVICE = "b9402000-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_ADVERTISING_SEED = "b9402001-f5f8-466e-aff9-25556b57fe6d";
    public static final String ESTIMOTE_ADVERTISING_VECTOR = "b9402002-f5f8-466e-aff9-25556b57fe6d";

    private final static Map<String, String> sGattAttributesMap = populateGattAttributesMap();

    public static String getAttributeName(final String uuid, final String fallback) {
        final String name = sGattAttributesMap.get(uuid.toLowerCase(Locale.US));
        return name == null ? fallback : name;
    }

    private static Map<String, String> populateGattAttributesMap() {
        final Map<String, String> map = new HashMap<>();

        map.put(BASE_GUID, "Base GUID");
        map.put(SERVICE_DISCOVERY_PROTOCOL_SDP, "Service Discovery Protocol (SDP)");
        map.put(USER_DATAGRAM_PROTOCOL_UDP, "User Datagram Protocol (UDP)");
        map.put(RADIO_FREQUENCY_COMMUNICATION_PROTOCOL_RFCOMM, "Radio Frequency Communication Protocol (RFCOMM)");
        map.put(TCP, "TCP");
        map.put(TCSBIN, "TCSBIN");
        map.put(TCSAT, "TCSAT");
        map.put(OBJECT_EXCHANGE_PROTOCOL_OBEX, "Object Exchange Protocol (OBEX)");
        map.put(IP, "IP");
        map.put(FTP, "FTP");
        map.put(HTTP, "HTTP");
        map.put(WSP, "WSP");
        map.put(BNEP_SVC, "BNEP_SVC");
        map.put(UPNP_PROTOCOL, "UPNP Protocol");
        map.put(HIDP, "HIDP");
        map.put(HARDCOPY_CONTROL_CHANNEL_PROTOCOL, "Hardcopy Control Channel Protocol");
        map.put(HARDCOPY_DATA_CHANNEL_PROTOCOL, "Hardcopy Data Channel Protocol");
        map.put(HARDCOPY_NOTIFICATION_PROTOCOL, "Hardcopy Notification Protocol");
        map.put(VCTP_PROTOCOL, "VCTP Protocol");
        map.put(VDTP_PROTOCOL, "VDTP Protocol");
        map.put(CMPT_PROTOCOL, "CMPT Protocol");
        map.put(UDI_C_PLANE_PROTOCOL, "UDI C Plane Protocol");
        map.put(MCAP_CONTROL_CHANNEL, "MCAP Control Channel");
        map.put(MCAP_DATA_CHANNEL, "MCAP Data Channel");
        map.put(L2CAP, "L2CAP");
        map.put(SERVICE_DISCOVERY_SERVER, "Service Discovery Server");
        map.put(BROWSE_GROUP_DESCRIPTOR, "Browse Group Descriptor");
        map.put(PUBLIC_BROWSE_GROUP, "Public Browse Group");
        map.put(SPP, "SPP");
        map.put(LAN_ACCESS_USING_PPP, "LAN Access Using PPP");
        map.put(DUN_GW, "DUN_GW");
        map.put(OBEX_SYNC, "OBEX_SYNC");
        map.put(OBEX_OBJECT_PUSH, "OBEX Object Push");
        map.put(OBEX_FILE_TRANSFER, "OBEX File Transfer");
        map.put(IRMC_SYNC_COMMAND, "IrMC Sync Command");
        map.put(HSP_HS, "HSP_HS");
        map.put(CORDLESS_TELEPHONY, "Cordless Telephony");
        map.put(AUDIO_SOURCE, "Audio Source");
        map.put(AUDIO_SINK, "Audio Sink");
        map.put(AV_REMOTE_CONTROL_TARGET, "AV Remote Control Target");
        map.put(ADVANCED_AUDIO, "ADVANCED_AUDIO");
        map.put(AVRCP_REMOTE, "AVRCP_REMOTE");
        map.put(VIDEO_CONFERENCING, "Video Conferencing");
        map.put(INTERCOM, "Intercom");
        map.put(FAX, "FAX");
        map.put(HEADSET_PROFILE_HSP_AUDIO_GATEWAY, "Headset Profile (HSP) - Audio Gateway");
        map.put(WAP, "WAP");
        map.put(WAP_CLIENT, "WAP Client");
        map.put(PANU, "PANU");
        map.put(NAP, "NAP");
        map.put(GN, "GN");
        map.put(DIRECT_PRINTING, "Direct Printing");
        map.put(REFERENCE_PRINTING, "Reference Printing");
        map.put(IMAGING, "Imaging");
        map.put(IMAGING_RESPONDER, "Imaging Responder");
        map.put(IMAGING_AUTOMATIC_ARCHIVE, "Imaging Automatic Archive");
        map.put(IMAGING_REFERENCE_OBJECTS, "Imaging Reference Objects");
        map.put(HANDS_FREE_PROFILE_HFP, "Hands Free Profile (HFP)");
        map.put(HANDS_FREE_PROFILE_HFP_AUDIO_GATEWAY, "Hands Free Profile (HFP) – Audio Gateway");
        map.put(DIRECT_PRINTING_REFERENCE_OBJECTS, "Direct Printing Reference Objects");
        map.put(REFLECTED_UI, "Reflected UI");
        map.put(BASIC_PRINTING, "Basic Printing");
        map.put(PRINTING_STATUS, "Printing Status");
        map.put(HID, "HID");
        map.put(HARDCOPY_CABLE_REPLACEMENT, "Hardcopy Cable Replacement");
        map.put(HCR_PRINT, "HCR Print");
        map.put(HCR_SCAN, "HCR Scan");
        map.put(COMMON_ISDN_ACCESS, "Common ISDN Access");
        map.put(VIDEO_CONFERENCING_GATEWAY, "Video Conferencing Gateway");
        map.put(UDIMT, "UDIMT");
        map.put(UDITA, "UDITA");
        map.put(AUDIO_VIDEO, "Audio Video");
        map.put(SIM_ACCESS, "SIM Access");
        map.put(OBEX_PCE, "OBEX PCE");
        map.put(OBEX_PSE, "OBEX PSE");
        map.put(OBEX_PBAP, "OBEX PBAP");
        map.put(OBEX_MAS, "OBEX MAS");
        map.put(OBEX_MNS, "OBEX MNS");
        map.put(OBEX_MAP, "OBEX MAP");
        map.put(PNP, "PNP");
        map.put(GENERIC_NETWORKING, "Generic Networking");
        map.put(GENERIC_FILE_TRANSFER, "Generic File Transfer");
        map.put(GENERIC_AUDIO, "Generic Audio");
        map.put(GENERIC_TELEPHONY, "Generic Telephony");
        map.put(UPNP, "UPNP");
        map.put(UPNP_IP, "UPNP IP");
        map.put(ESDP_UPNP_IP_PAN, "ESDP UPnP IP PAN");
        map.put(ESDP_UPNP_IP_LAP, "ESDP UPnP IP LAP");
        map.put(ESDP_UPNP_L2CAP, "ESDP Upnp L2CAP");
        map.put(VIDEO_DISTRIBUTION_PROFILE_VDP_SOURCE, "Video Distribution Profile (VDP) - Source");
        map.put(VIDEO_DISTRIBUTION_PROFILE_VDP_SINK, "Video Distribution Profile (VDP) - Sink");
        map.put(VIDEO_DISTRIBUTION_PROFILE_VDP, "Video Distribution Profile (VDP)");
        map.put(HEALTH_DEVICE_PROFILE_HDP, "Health Device Profile (HDP)");
        map.put(HEALTH_DEVICE_PROFILE_HDP_SOURCE, "Health Device Profile (HDP) - Source");
        map.put(HEALTH_DEVICE_PROFILE_HDP_SINK, "Health Device Profile (HDP) - Sink");
        map.put(GAP, "GAP");
        map.put(GATT, "GATT");
        map.put(IMMEDIATE_ALERT, "IMMEDIATE_ALERT");
        map.put(LINK_LOSS, "LINK_LOSS");
        map.put(TX_POWER, "TX_POWER");
        map.put(HEALTH_THERMOMETER, "Health Thermometer");
        map.put(DEVICE_INFORMATION, "Device Information");
        map.put(HEART_RATE, "HEART_RATE");
        map.put(CYCLING_SC, "CYCLING_SC");
        map.put(CLIENT_CHARACTERISTIC_CONFIG, "CLIENT_CHARACTERISTIC_CONFIG");
        map.put(DEVICE_NAME, "Device Name");
        map.put(APPEARANCE, "Appearance");
        map.put(PERIPHERAL_PRIVACY_FLAG, "Peripheral Privacy Flag");
        map.put(RECONNECTION_ADDRESS, "Reconnection Address");
        map.put(PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS, "Peripheral Preferred Connection Parameters");
        map.put(SERVICE_CHANGED, "Service Changed");
        map.put(ALERT_LEVEL, "Alert Level");
        map.put(TX_POWER_LEVEL, "Tx Power Level");
        map.put(DATE_TIME, "Date Time");
        map.put(DAY_OF_WEEK, "Day of Week");
        map.put(DAY_DATE_TIME, "Day Date Time");
        map.put(EXACT_TIME_256, "Exact Time 256");
        map.put(DST_OFFSET, "DST Offset");
        map.put(TIME_ZONE, "Time Zone");
        map.put(LOCAL_TIME_INFORMATION, "Local Time Information");
        map.put(TIME_WITH_DST, "Time with DST");
        map.put(TIME_ACCURACY, "Time Accuracy");
        map.put(TIME_SOURCE, "Time Source");
        map.put(REFERENCE_TIME_INFORMATION, "Reference Time Information");
        map.put(TIME_UPDATE_CONTROL_POINT, "Time Update Control Point");
        map.put(TIME_UPDATE_STATE, "Time Update State");
        map.put(TEMPERATURE_MEASUREMENT, "Temperature Measurement");
        map.put(TEMPERATURE_TYPE, "Temperature Type");
        map.put(INTERMEDIATE_TEMPERATURE, "Intermediate Temperature");
        map.put(MEASUREMENT_INTERVAL, "Measurement Interval");
        map.put(SYSTEM_ID, "System ID");
        map.put(MODEL_NUMBER_STRING, "Model Number String");
        map.put(SERIAL_NUMBER_STRING, "Serial Number String");
        map.put(FIRMWARE_REVISION_STRING, "Firmware Revision String");
        map.put(HARDWARE_REVISION_STRING, "Hardware Revision String");
        map.put(SOFTWARE_REVISION_STRING, "Software Revision String");
        map.put(MANUFACTURER_NAME_STRING, "Manufacturer Name String");
        map.put(IEEE_1107320601_REGULATORY, "IEEE 11073-20601 Regulatory");
        map.put(CURRENT_TIME, "Current Time");
        map.put(BLOOD_PRESSURE_MEASUREMENT, "Blood Pressure Measurement");
        map.put(INTERMEDIATE_CUFF_PRESSURE, "Intermediate Cuff Pressure");
        map.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement");
        map.put(BODY_SENSOR_LOCATION, "Body Sensor Location");
        map.put(HEART_RATE_CONTROL_POINT, "Heart Rate Control Point");
        map.put(ALERT_STATUS, "Alert Status");
        map.put(RINGER_CONTROL_POINT, "Ringer Control Point");
        map.put(RINGER_SETTING, "Ringer Setting");
        map.put(ALERT_CATEGORY_ID_BIT_MASK, "Alert Category ID Bit Mask");
        map.put(ALERT_CATEGORY_ID, "Alert Category ID");
        map.put(ALERT_NOTIFICATION_CONTROL_POINT, "Alert Notification Control Point");
        map.put(UNREAD_ALERT_STATUS, "Unread Alert Status");
        map.put(NEW_ALERT, "New Alert");
        map.put(SUPPORTED_NEW_ALERT_CATEGORY, "Supported New Alert Category");
        map.put(SUPPORTED_UNREAD_ALERT_CATEGORY, "Supported Unread Alert Category");
        map.put(BLOOD_PRESSURE_FEATURE, "Blood Pressure Feature");
        map.put(PNPID, "PNPID");
        map.put(SC_CONTROL_POINT, "SC_CONTROL_POINT");
        map.put(CSC_MEASUREMENT, "CSC_MEASUREMENT");
        map.put(CSC_FEATURE, "CSC_FEATURE");
        map.put(SENSOR_LOCATION, "SENSOR_LOCATION");
        map.put(ACTIVESYNC, "ActiveSync");
        map.put(ESTIMOTE_SERVICE, "Estimote Service");
        map.put(ESTIMOTE_UUID, "Estimote UUID");
        map.put(ESTIMOTE_MAJOR, "Estimote Major");
        map.put(ESTIMOTE_MINOR, "Estimote Minor");
        map.put(ESTIMOTE_BATTERY, "Estimote Battery");
        map.put(ESTIMOTE_TEMPERATURE, "Estimote Temperature");
        map.put(ESTIMOTE_POWER, "Estimote Power");
        map.put(ESTIMOTE_ADVERTISING_INTERVAL, "Estimote Advertising Interval");
        map.put(ESTIMOTE_VERSION_SERVICE, "Estimote Version Service");
        map.put(ESTIMOTE_SOFTWARE_VERSION, "Estimote Software Version");
        map.put(ESTIMOTE_HARDWARE_VERSION, "Estimote Hardware Version");
        map.put(ESTIMOTE_AUTHENTICATION_SERVICE, "Estimote Authentication Service");
        map.put(ESTIMOTE_ADVERTISING_SEED, "Estimote Advertising Seed");
        map.put(ESTIMOTE_ADVERTISING_VECTOR, "Estimote Advertising Vector");

        return map;
    }
} public class HexUtil {
    /**
     * 用于建立十六进制字符的输出的小写字符数组
     */
    private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * 用于建立十六进制字符的输出的大写字符数组
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data byte[]
     * @return 十六进制char[]
     */
    public static char[] encodeHex(byte[] data) {
        return encodeHex(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data        byte[]
     * @param toLowerCase <code>true</code> 传换成小写格式 ， <code>false</code> 传换成大写格式
     * @return 十六进制char[]
     */
    public static char[] encodeHex(byte[] data, boolean toLowerCase) {
        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符数组
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制char[]
     */
    protected static char[] encodeHex(byte[] data, char[] toDigits) {
        int l = data.length;
        char[] out = new char[l << 1];
        // two characters form the hex value.
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }
        return out;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data byte[]
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data) {
        return encodeHexStr(data, true);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data        byte[]
     * @param toLowerCase <code>true</code> 传换成小写格式 ， <code>false</code> 传换成大写格式
     * @return 十六进制String
     */
    public static String encodeHexStr(byte[] data, boolean toLowerCase) {
        return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param data     byte[]
     * @param toDigits 用于控制输出的char[]
     * @return 十六进制String
     */
    protected static String encodeHexStr(byte[] data, char[] toDigits) {
        if (data == null) {
            ViseLog.e("this data is null.");
            return "";
        }
        return new String(encodeHex(data, toDigits));
    }

    /**
     * 将十六进制字符串转换为字节数组
     *
     * @param data
     * @return
     */
    public static byte[] decodeHex(String data) {
        if (data == null) {
            ViseLog.e("this data is null.");
            return new byte[0];
        }
        return decodeHex(data.toCharArray());
    }

    /**
     * 将十六进制字符数组转换为字节数组
     *
     * @param data 十六进制char[]
     * @return byte[]
     * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度，将抛出运行时异常
     */
    public static byte[] decodeHex(char[] data) {

        int len = data.length;

        if ((len & 0x01) != 0) {
            throw new RuntimeException("Odd number of characters.");
        }

        byte[] out = new byte[len >> 1];

        // two characters form the hex value.
        for (int i = 0, j = 0; j < len; i++) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f = f | toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 0xFF);
        }

        return out;
    }

    /**
     * 将十六进制字符转换成一个整数
     *
     * @param ch    十六进制char
     * @param index 十六进制字符在字符数组中的位置
     * @return 一个整数
     * @throws RuntimeException 当ch不是一个合法的十六进制字符时，抛出运行时异常
     */
    protected static int toDigit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    /**
     * 截取字节数组
     *
     * @param src   byte []  数组源  这里填16进制的 数组
     * @param begin 起始位置 源数组的起始位置。0位置有效
     * @param count 截取长度
     * @return
     */
    public static byte[] subBytes(byte[] src, int begin, int count) {
        byte[] bs = new byte[count];
        System.arraycopy(src, begin, bs, 0, count);  // bs 目的数组  0 截取后存放的数值起始位置。0位置有效
        return bs;
    }

    /**
     * int转byte数组
     *
     * @param bb
     * @param x
     * @param index 第几位开始
     * @param flag 标识高低位顺序，高位在前为true，低位在前为false
     */
    public static void intToByte(byte[] bb, int x, int index, boolean flag) {
        if (flag) {
            bb[index + 0] = (byte) (x >> 24);
            bb[index + 1] = (byte) (x >> 16);
            bb[index + 2] = (byte) (x >> 8);
            bb[index + 3] = (byte) (x >> 0);
        } else {
            bb[index + 3] = (byte) (x >> 24);
            bb[index + 2] = (byte) (x >> 16);
            bb[index + 1] = (byte) (x >> 8);
            bb[index + 0] = (byte) (x >> 0);
        }
    }

    /**
     * byte数组转int
     *
     * @param bb
     * @param index 第几位开始
     * @param flag 标识高低位顺序，高位在前为true，低位在前为false
     * @return
     */
    public static int byteToInt(byte[] bb, int index, boolean flag) {
        if (flag) {
            return (int) ((((bb[index + 0] & 0xff) << 24)
                    | ((bb[index + 1] & 0xff) << 16)
                    | ((bb[index + 2] & 0xff) << 8)
                    | ((bb[index + 3] & 0xff) << 0)));
        } else {
            return (int) ((((bb[index + 3] & 0xff) << 24)
                    | ((bb[index + 2] & 0xff) << 16)
                    | ((bb[index + 1] & 0xff) << 8)
                    | ((bb[index + 0] & 0xff) << 0)));
        }
    }


    /**
     * 字节数组逆序
     *
     * @param data
     * @return
     */
    public static byte[] reverse(byte[] data) {
        byte[] reverseData = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            reverseData[i] = data[data.length - 1 - i];
        }
        return reverseData;
    }

    /**
     * 蓝牙传输 16进制 高低位 读数的 转换
     *
     * @param data 截取数据源，字节数组
     * @param index 截取数据开始位置
     * @param count 截取数据长度，只能为2、4、8个字节
     * @param flag 标识高低位顺序，高位在前为true，低位在前为false
     * @return
     */
    public static long byteToLong(byte[] data, int index, int count, boolean flag) {
        long lg = 0;
        if (flag) {
            switch (count) {
                case 2:
                    lg = ((((long) data[index + 0] & 0xff) << 8)
                            | (((long) data[index + 1] & 0xff) << 0));
                    break;

                case 4:
                    lg = ((((long) data[index + 0] & 0xff) << 24)
                            | (((long) data[index + 1] & 0xff) << 16)
                            | (((long) data[index + 2] & 0xff) << 8)
                            | (((long) data[index + 3] & 0xff) << 0));
                    break;

                case 8:
                    lg = ((((long) data[index + 0] & 0xff) << 56)
                            | (((long) data[index + 1] & 0xff) << 48)
                            | (((long) data[index + 2] & 0xff) << 40)
                            | (((long) data[index + 3] & 0xff) << 32)
                            | (((long) data[index + 4] & 0xff) << 24)
                            | (((long) data[index + 5] & 0xff) << 16)
                            | (((long) data[index + 6] & 0xff) << 8)
                            | (((long) data[index + 7] & 0xff) << 0));
                    break;
            }
            return lg;
        } else {
            switch (count) {
                case 2:
                    lg = ((((long) data[index + 1] & 0xff) << 8)
                            | (((long) data[index + 0] & 0xff) << 0));
                    break;
                case 4:
                    lg = ((((long) data[index + 3] & 0xff) << 24)
                            | (((long) data[index + 2] & 0xff) << 16)
                            | (((long) data[index + 1] & 0xff) << 8)
                            | (((long) data[index + 0] & 0xff) << 0));
                    break;
                case 8:
                    lg = ((((long) data[index + 7] & 0xff) << 56)
                            | (((long) data[index + 6] & 0xff) << 48)
                            | (((long) data[index + 5] & 0xff) << 40)
                            | (((long) data[index + 4] & 0xff) << 32)
                            | (((long) data[index + 3] & 0xff) << 24)
                            | (((long) data[index + 2] & 0xff) << 16)
                            | (((long) data[index + 1] & 0xff) << 8)
                            | (((long) data[index + 0] & 0xff) << 0));
                    break;
            }
            return lg;
        }
    }

} public class BluetoothDeviceManager {

    private static BluetoothDeviceManager instance;
    private DeviceMirrorPool mDeviceMirrorPool;
    private ScanEvent scanEvent = new ScanEvent();
    private ConnectEvent connectEvent = new ConnectEvent();
    private CallbackDataEvent callbackDataEvent = new CallbackDataEvent();
    private NotifyDataEvent notifyDataEvent = new NotifyDataEvent();

    /**
     * 连接回调
     */
    private IConnectCallback connectCallback = new IConnectCallback() {

        @Override
        public void onConnectSuccess(final DeviceMirror deviceMirror) {
            ViseLog.i("Connect Success!");
            BusManager.getBus().post(connectEvent.setDeviceMirror(deviceMirror).setSuccess(true));
        }

        @Override
        public void onConnectFailure(BleException exception) {
            ViseLog.i("Connect Failure!");
            BusManager.getBus().post(connectEvent.setSuccess(false).setDisconnected(false));
        }

        @Override
        public void onDisconnect(boolean isActive) {
            ViseLog.i("Disconnect!");
            BusManager.getBus().post(connectEvent.setSuccess(false).setDisconnected(true));
        }
    };

    /**
     * 接收数据回调
     */
    private IBleCallback receiveCallback = new IBleCallback() {
        @Override
        public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) {
            if (data == null) {
                return;
            }
            ViseLog.i("notify success:" + HexUtil.encodeHexStr(data));
            BusManager.getBus().post(notifyDataEvent.setData(data)
                    .setBluetoothLeDevice(bluetoothLeDevice)
                    .setBluetoothGattChannel(bluetoothGattInfo));
        }

        @Override
        public void onFailure(BleException exception) {
            if (exception == null) {
                return;
            }
            ViseLog.i("notify fail:" + exception.getDescription());
        }
    };

    /**
     * 操作数据回调
     */
    private IBleCallback bleCallback = new IBleCallback() {
        @Override
        public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) {
            if (data == null) {
                return;
            }
            ViseLog.i("callback success:" + HexUtil.encodeHexStr(data));
            BusManager.getBus().post(callbackDataEvent.setData(data).setSuccess(true)
                    .setBluetoothLeDevice(bluetoothLeDevice)
                    .setBluetoothGattChannel(bluetoothGattInfo));
            if (bluetoothGattInfo != null && (bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_INDICATE
                    || bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_NOTIFY)) {
                DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
                if (deviceMirror != null) {
                    deviceMirror.setNotifyListener(bluetoothGattInfo.getGattInfoKey(), receiveCallback);
                }
            }
        }

        @Override
        public void onFailure(BleException exception) {
            if (exception == null) {
                return;
            }
            ViseLog.i("callback fail:" + exception.getDescription());
            BusManager.getBus().post(callbackDataEvent.setSuccess(false));
        }
    };

    private BluetoothDeviceManager() {

    }

    public static BluetoothDeviceManager getInstance() {
        if (instance == null) {
            synchronized (BluetoothDeviceManager.class) {
                if (instance == null) {
                    instance = new BluetoothDeviceManager();
                }
            }
        }
        return instance;
    }

    public void init(Context context) {
        if (context == null) {
            return;
        }
        //蓝牙相关配置修改
        ViseBle.config()
                .setScanTimeout(-1)//扫描超时时间，这里设置为永久扫描
                .setScanRepeatInterval(5 * 1000)//扫描间隔5秒
                .setConnectTimeout(10 * 1000)//连接超时时间
                .setOperateTimeout(5 * 1000)//设置数据操作超时时间
                .setConnectRetryCount(3)//设置连接失败重试次数
                .setConnectRetryInterval(1000)//设置连接失败重试间隔时间
                .setOperateRetryCount(3)//设置数据操作失败重试次数
                .setOperateRetryInterval(1000)//设置数据操作失败重试间隔时间
                .setMaxConnectCount(3);//设置最大连接设备数量
        //蓝牙信息初始化，全局唯一，必须在应用初始化时调用
        ViseBle.getInstance().init(context.getApplicationContext());
        mDeviceMirrorPool = ViseBle.getInstance().getDeviceMirrorPool();
    }

    public void connect(BluetoothLeDevice bluetoothLeDevice) {
        ViseBle.getInstance().connect(bluetoothLeDevice, connectCallback);
    }

    public void disconnect(BluetoothLeDevice bluetoothLeDevice) {
        ViseBle.getInstance().disconnect(bluetoothLeDevice);
    }

    public boolean isConnected(BluetoothLeDevice bluetoothLeDevice) {
        return ViseBle.getInstance().isConnect(bluetoothLeDevice);
    }

    public void bindChannel(BluetoothLeDevice bluetoothLeDevice, PropertyType propertyType, UUID serviceUUID,
                                 UUID characteristicUUID, UUID descriptorUUID) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
                    .setBluetoothGatt(deviceMirror.getBluetoothGatt())
                    .setPropertyType(propertyType)
                    .setServiceUUID(serviceUUID)
                    .setCharacteristicUUID(characteristicUUID)
                    .setDescriptorUUID(descriptorUUID)
                    .builder();
            deviceMirror.bindChannel(bleCallback, bluetoothGattChannel);
        }
    }

    public void write(final BluetoothLeDevice bluetoothLeDevice, byte[] data) {
        if (dataInfoQueue != null) {
            dataInfoQueue.clear();
            dataInfoQueue = splitPacketFor20Byte(data);
            new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    send(bluetoothLeDevice);
                }
            });
        }
    }

    public void read(BluetoothLeDevice bluetoothLeDevice) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            deviceMirror.readData();
        }
    }

    public void registerNotify(BluetoothLeDevice bluetoothLeDevice, boolean isIndicate) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            deviceMirror.registerNotify(isIndicate);
        }
    }

    //发送队列，提供一种简单的处理方式，实际项目场景需要根据需求优化
    private Queue<byte[]> dataInfoQueue = new LinkedList<>();
    private void send(final BluetoothLeDevice bluetoothLeDevice) {
        if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
            DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
            if (dataInfoQueue.peek() != null && deviceMirror != null) {
                deviceMirror.writeData(dataInfoQueue.poll());
            }
            if (dataInfoQueue.peek() != null) {
                new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        send(bluetoothLeDevice);
                    }
                }, 100);
            }
        }
    }

    /**
     * 数据分包
     *
     * @param data
     * @return
     */
    private Queue<byte[]> splitPacketFor20Byte(byte[] data) {
        Queue<byte[]> dataInfoQueue = new LinkedList<>();
        if (data != null) {
            int index = 0;
            do {
                byte[] surplusData = new byte[data.length - index];
                byte[] currentData;
                System.arraycopy(data, index, surplusData, 0, data.length - index);
                if (surplusData.length <= 20) {
                    currentData = new byte[surplusData.length];
                    System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
                    index += surplusData.length;
                } else {
                    currentData = new byte[20];
                    System.arraycopy(data, index, currentData, 0, 20);
                    index += 20;
                }
                dataInfoQueue.offer(currentData);
            } while (index < data.length);
        }
        return dataInfoQueue;
    }

} public class ToastUtil {
    private static final long DROP_DUPLICATE_TOAST_TS = 2 * 1000; // 2s

    private static String sLast = "";

    private static long sLastTs = 0;

    private static Toast mBasicToast = null;

    public enum CommonToastType {
        TOAST_TYPE_NORMAL, // 普通toast-笑脸
        TOAST_TYPE_SUC, // 成功toast-笑脸
        TOAST_TYPE_ACC, // 加速toast-笑脸
        TOAST_TYPE_SMILE, // 微笑toast-笑脸
        TOAST_TYPE_ALARM// 失败or警告toast-哭脸
    }

    /**
     * 上下文.
     */
    private static Context mContext = null;

    /**
     * 显示Toast.
     */
    public static final int SHOW_TOAST = 0;
    /**
     * 主要Handler类，在线程中可用
     * what：0.提示文本信息
     */
    private static Handler baseHandler = new Handler(Looper.getMainLooper()) {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_TOAST:
                    showToast(mContext, msg.getData().getString("TEXT"));
                    break;
                default:
                    break;
            }
        }
    };


    public static void showToast(Context context, int strRes) {
        showToast(context, context.getResources().getString(strRes));
    }

    public static void showShortToast(Context context, String prompt) {
        showToast(context, prompt);

    }

    public static void showLongToast(Context context, String prompt) {
        showToast(context, prompt);
    }

    public static Toast makeText(Context context, CharSequence text, int duration) {
        mContext = context.getApplicationContext();
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(R.layout.common_layout_toast, null);
        TextView tv = (TextView) v.findViewById(R.id.tvToast);
        tv.setText(text);

        result.setView(v);
        result.setGravity(Gravity.BOTTOM, 0, 163);
        result.setDuration(duration);

        return result;
    }

    public static synchronized void showToast(Context context, String str) {
        showToast(context, CommonToastType.TOAST_TYPE_NORMAL, str);
    }

    /**
     * toast（带动画）-显示时间2S
     *
     * @param context
     * @param type
     * @param str
     */
    public static synchronized void showToast(Context context, CommonToastType type, String str) {
        try {
            mContext = context.getApplicationContext();
            long newTs = System.currentTimeMillis();
            if (str != null
                    && (!str.equals(sLast) || newTs < sLastTs || (newTs - sLastTs) > DROP_DUPLICATE_TOAST_TS)) {
                sLast = str;
                sLastTs = newTs;
                if (mBasicToast == null) {
                    mBasicToast = new Toast(context);
                }
                View toastView = LayoutInflater.from(context).inflate(
                        R.layout.common_layout_toast, null);
                TextView txt = (TextView) toastView.findViewById(R.id.tvToast);
                txt.setText(str);

                mBasicToast.setView(toastView);
                int px = (int) ViewUtil.dip2px(context, 60);
                mBasicToast.setGravity(Gravity.BOTTOM, 0, px);
                mBasicToast.setDuration(Toast.LENGTH_SHORT);// 默认只显示2S
                mBasicToast.show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 描述：在线程中提示文本信息.
     *
     * @param resId 要提示的字符串资源ID，消息what值为0,
     */
    public static void showToastInThread(Context context, int resId) {
        mContext = context.getApplicationContext();
        Message msg = baseHandler.obtainMessage(SHOW_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString("TEXT", context.getResources().getString(resId));
        msg.setData(bundle);
        baseHandler.sendMessage(msg);
    }

    /**
     * 描述：在线程中提示文本信息.
     *
     * @param context
     * @param text
     */
    public static void showToastInThread(Context context, String text) {
        mContext = context.getApplicationContext();
        Message msg = baseHandler.obtainMessage(SHOW_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString("TEXT", text);
        msg.setData(bundle);
        baseHandler.sendMessage(msg);
    }

    /**
     * 让Toast立马消失
     */
    public static void cancelToast() {
        if (mBasicToast != null) {
            mBasicToast.cancel();
        }
    }
} public class CallbackDataEvent implements IEvent {
    private byte[] data;
    private boolean isSuccess;
    private BluetoothLeDevice bluetoothLeDevice;
    private BluetoothGattChannel bluetoothGattChannel;

    public CallbackDataEvent setSuccess(boolean success) {
        isSuccess = success;
        return this;
    }

    public byte[] getData() {
        return data;
    }

    public CallbackDataEvent setData(byte[] data) {
        this.data = data;
        return this;
    }

    public boolean isSuccess() {
        return isSuccess;
    }

    public BluetoothLeDevice getBluetoothLeDevice() {
        return bluetoothLeDevice;
    }

    public CallbackDataEvent setBluetoothLeDevice(BluetoothLeDevice bluetoothLeDevice) {
        this.bluetoothLeDevice = bluetoothLeDevice;
        return this;
    }

    public BluetoothGattChannel getBluetoothGattChannel() {
        return bluetoothGattChannel;
    }

    public CallbackDataEvent setBluetoothGattChannel(BluetoothGattChannel bluetoothGattChannel) {
        this.bluetoothGattChannel = bluetoothGattChannel;
        return this;
    }
} public class ConnectEvent implements IEvent {
    private boolean isSuccess;
    private boolean isDisconnected;
    private DeviceMirror deviceMirror;

    public boolean isSuccess() {
        return isSuccess;
    }

    public ConnectEvent setSuccess(boolean success) {
        isSuccess = success;
        return this;
    }

    public boolean isDisconnected() {
        return isDisconnected;
    }

    public ConnectEvent setDisconnected(boolean disconnected) {
        isDisconnected = disconnected;
        return this;
    }

    public DeviceMirror getDeviceMirror() {
        return deviceMirror;
    }

    public ConnectEvent setDeviceMirror(DeviceMirror deviceMirror) {
        this.deviceMirror = deviceMirror;
        return this;
    }
} public class NotifyDataEvent implements IEvent {
    private byte[] data;
    private BluetoothLeDevice bluetoothLeDevice;
    private BluetoothGattChannel bluetoothGattChannel;

    public byte[] getData() {
        return data;
    }

    public NotifyDataEvent setData(byte[] data) {
        this.data = data;
        return this;
    }

    public BluetoothLeDevice getBluetoothLeDevice() {
        return bluetoothLeDevice;
    }

    public NotifyDataEvent setBluetoothLeDevice(BluetoothLeDevice bluetoothLeDevice) {
        this.bluetoothLeDevice = bluetoothLeDevice;
        return this;
    }

    public BluetoothGattChannel getBluetoothGattChannel() {
        return bluetoothGattChannel;
    }

    public NotifyDataEvent setBluetoothGattChannel(BluetoothGattChannel bluetoothGattChannel) {
        this.bluetoothGattChannel = bluetoothGattChannel;
        return this;
    }
}
import android.app.AlertDialog;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import com.vise.baseble.ViseBle;
import com.vise.baseble.common.ConnectState;
import com.vise.baseble.common.PropertyType;
import com.vise.baseble.core.DeviceMirror;
import com.vise.baseble.model.BluetoothLeDevice;
import com.vise.baseble.model.resolver.GattAttributeResolver;
import com.vise.baseble.utils.HexUtil;
import com.vise.bledemo.R;
import com.vise.bledemo.common.BluetoothDeviceManager;
import com.vise.bledemo.common.ToastUtil;
import com.vise.bledemo.event.CallbackDataEvent;
import com.vise.bledemo.event.ConnectEvent;
import com.vise.bledemo.event.NotifyDataEvent;
import com.vise.xsnow.cache.SpCache;
import com.vise.xsnow.event.BusManager;
import com.vise.xsnow.event.Subscribe;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
package com.vise.bledemo.activity;




/**
 * 设备数据操作相关展示界面
 */
public class DeviceControlActivity extends AppCompatActivity {

    private static final String LIST_NAME = "NAME";
    private static final String LIST_UUID = "UUID";
    public static final String WRITE_CHARACTERISTI_UUID_KEY = "write_uuid_key";
    public static final String NOTIFY_CHARACTERISTIC_UUID_KEY = "notify_uuid_key";
    public static final String WRITE_DATA_KEY = "write_data_key";

    private SimpleExpandableListAdapter simpleExpandableListAdapter;
    private TextView mConnectionState;
    private TextView mGattUUID;
    private TextView mGattUUIDDesc;
    private TextView mDataAsString;
    private TextView mDataAsArray;
    private EditText mInput;
    private EditText mOutput;

    private SpCache mSpCache;
    //设备信息
    private BluetoothLeDevice mDevice;