[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n.idea/.gitignore\n.idea/compiler.xml\n.idea/gradle.xml\n.idea/intellij-javadocs-4.0.1.xml\n.idea/misc.xml\n.idea/sbt.xml\n.idea/vcs.xml\n.idea/codeStyles/codeStyleConfig.xml\n.idea/codeStyles/Project.xml\n.idea/inspectionProfiles/Project_Default.xml\n.idea/sonarlint/issuestore/index.pb\n.idea/sonarlint/issuestore/0/5/05efc8b1657769a27696d478ded1e95f38737233\n.idea/sonarlint/issuestore/0/7/0712df971a99ac4d2fccb8e0fb19f377f3374cca\n.idea/sonarlint/issuestore/1/4/141f074f42b7618731e03860b83591695fc45b34\n.idea/sonarlint/issuestore/1/7/17ebefcfefc1932c79193259a35c282e59f29e32\n.idea/sonarlint/issuestore/2/a/2afbb999f001938c88fa43fc2ef52abf0f8213e4\n.idea/sonarlint/issuestore/3/f/3f604333d77f906a746c7ba9f863d2139ac381ba\n.idea/sonarlint/issuestore/4/0/40ce750d38060383ffa2ca21095e83385593ead2\n.idea/sonarlint/issuestore/4/4/443bfeba92309bf514f3e39be0961d08b0b756a7\n.idea/sonarlint/issuestore/4/7/47e330b37492be03483c7657e9fcec3513e5ec0f\n.idea/sonarlint/issuestore/9/8/98cca74c1c0c3e8343788bf02a1700ac660f893a\n.idea/sonarlint/issuestore/9/f/9f08693c85f95f731b2faec96449210cc5407271\n.idea/sonarlint/issuestore/d/3/d396928ac15e23fca51b0277da6aac0508305f1d\n.idea/sonarlint/issuestore/f/0/f07866736216be0ee2aba49e392191aeae700a35\n.idea/sonarlint/issuestore/f/0/f098d04f62ef69cedfc781eb9212c5c5bbf1dc88\n.idea/sonarlint/issuestore/f/2/f25afbb4385061bdb9965342e5eb4474aa843ff6\n.idea/sonarlint/issuestore/f/4/f4a01d6a4fcb971362ec00a83903fd3902f52164\n.idea/sonarlint/issuestore/f/6/f6706a18253c0aebf3b63b4f8147984ed99b3fb9\n.idea/sonarlint/issuestore/f/b/fbe448ebfc3eb2d4e308f6b8b043666f5b57235e\n.idea/sonarlint/issuestore/f/f/ff8b003b156eab859952b4376b9cfbfac02fd59d\n.idea/\n"
  },
  {
    "path": "README.md",
    "content": "# BleCore Android蓝牙低功耗(BLE)快速开发框架\n\n## 本项目持续维护更新\n\n*   如果觉得对您有帮助，可以犒劳一下作者\n*   ![1744788695450](https://github.com/user-attachments/assets/62a16ec4-e1d9-4bb3-9dc3-bc6e11cad38f)\n \n\n\n*   当前版本[![](https://jitpack.io/v/buhuiming/BleCore.svg)](https://jitpack.io/#buhuiming/BleCore) \n*   minSdk 24\n*   targetSdk 35\n*   compileSdk 35\n\n#### * 基于Kotlin、协程\n#### * 基于sdk 35，最新API\n#### * 详细的完整的容错机制\n#### * 基于多个蓝牙库的设计思想\n#### * 强大的Notify\\Indicate\\Read\\Write任务队列\n\n![20230613110126](https://github.com/buhuiming/BleCore/blob/main/screenshots/20230613110126.png)\n![20230613110146](https://github.com/buhuiming/BleCore/blob/main/screenshots/20230613110146.png)\n![20230614090104](https://github.com/buhuiming/BleCore/blob/main/screenshots/20230614090104.png)\n\n### demo体验\n\n![apk_address](https://github.com/buhuiming/BleCore/blob/main/screenshots/apk_address.png)\n\n###\n###\n\n### 详细用法参考demo \n### 详细用法参考demo \n### 详细用法参考demo \n\n### 用法\n\n        allprojects {\n            repositories {\n                ...\n                maven { url 'https://jitpack.io' }\n            }\n        }\n\n        dependencies {\n            implementation 'com.github.buhuiming:BleCore:latest version'\n        }\n\n#### 添加权限\n\n    //动态申请\n    val LOCATION_PERMISSION = \n        if (VERSION.SDK_INT < VERSION_CODES.S) {\n            arrayOf(\n                //注册精准位置权限，否则可能Ble扫描不到设备\n                Manifest.permission.ACCESS_FINE_LOCATION,\n                Manifest.permission.ACCESS_COARSE_LOCATION,\n            )\n        } else {\n            arrayOf(\n            Manifest.permission.BLUETOOTH_SCAN,\n            Manifest.permission.BLUETOOTH_CONNECT,\n            )\n        }\n*    特别注意：权限Manifest.permission.ACCESS_FINE_LOCATION，Manifest.permission.ACCESS_COARSE_LOCATION\n     在Android12及以上版本已经去掉、包括校验。权限Manifest.permission.BLUETOOTH_ADVERTISE已经去掉。\n\n*    注意：\n*    有些设备GPS是关闭状态的话，申请定位权限之后，GPS是依然关闭状态，这里要根据GPS是否打开来跳转页面\n*    BleUtil.isGpsOpen(context) 判断GPS是否打开（可以通过BleOptions.setNeedCheckGps(boolean)来配置是否需要GPS检测）\n*    跳转到系统GPS设置页面，GPS设置是全局的独立的，是否打开跟权限申请无关\n     startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))\n*    跳转到系统蓝牙设置页面\n     startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS))\n\n#### 初始化\n    val options =\n            BleOptions.builder()\n                .setScanServiceUuid(\"0000ff80-0000-1000-8000-00805f9b34fb\", \"0000ff90-0000-1000-8000-00805f9b34fb\")\n                .setScanDeviceName(\"midea\", \"BYD BLE3\")\n                .setScanDeviceAddress(\"70:86:CE:88:7A:AF\", \"5B:AE:65:88:59:5E\", \"B8:8C:29:8B:BE:07\")\n                .isContainScanDeviceName(true)\n                .setAutoConnect(false)\n                .setEnableLog(true)\n                .setScanMillisTimeOut(12000)\n                //这个机制是：不会因为扫描的次数导致上一次扫描到的数据被清空，也就是onScanStart和onScanComplete\n                //都只会回调一次，而且扫描到的数据是所有扫描次数的总和\n                .setScanRetryCountAndInterval(2, 1000)\n                .setConnectMillisTimeOut(10000)\n                .setConnectRetryCountAndInterval(2, 5000)\n                .setOperateMillisTimeOut(6000)\n                .setOperateInterval(80)\n                .setMaxConnectNum(5)\n                .setMtu(500)\n                .setTaskQueueType(BleTaskQueueType.Operate)\n                .build()\n    BleManager.get().init(application, options)\n\n    //或者使用默认配置\n    BleManager.get().init(application)\n\nsetTaskQueueType方法，有3个选项分别是：\n*   BleTaskQueueType.Default\n    一个设备的Notify\\Indicate\\Read\\Write\\mtu操作所对应的任务共享同一个任务\n    队列(共享队列)(不区分特征值)，rssi在rssi队列\n*   BleTaskQueueType.Operate\n    一个设备每个操作独立一个任务队列(不区分特征值)\n    Notify在Notify队列中，Indicate在Indicate队列中，Read在Read队列中，\n    Write在Write队列中，mtu在共享队列，rssi在rssi队列中，\n    不同操作任务之间相互不影响，相同操作任务之间先进先出按序执行\n    例如特征值1的写操作和特征值2的写操作，在同一个任务队列当中；特征值1的写操作和特征值1的读操作，\n    在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n*   BleTaskQueueType.Independent\n    一个设备每个特征值下的每个操作独立一个任务队列(区分特征值)\n    Notify\\Indicate\\Read\\Write所对应的任务分别放入到独立的任务队列中，\n    mtu在共享队列，rssi在rssi队列中，\n    且按特征值区分，不同操作任务之间相互不影响，相同操作任务之间相互不影响\n    例如特征值1的写操作和特征值2的写操作，在两个不同的任务队列当中；特征值1的写操作和特征值1的读操作，\n    在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n\n注意：BleTaskQueueType.Operate、BleTaskQueueType.Independent这两种模式下\n* 1、在Notify\\Indicate\\Read\\Write 未完成的情况下，不要执行设置Mtu，否则会导致前者操作失败\n* 2、同时执行Notify\\Indicate\\Read\\Write其中两个以上操作，会可能报设备忙碌失败\n \n建议：以上模式主要也是针对操作之间的问题，强烈建议不要同时执行2个及以上操作，模式BleTaskQueueType.Default就是为\n     了让设备所有操作同一时间只执行一个，Rssi不受影响\n    \n#### 扫描\n    注意：扫描之前先检查权限、检查GPS开关、检查蓝牙开关\n    扫描及过滤过程是在工作线程中进行，所以不会影响主线程的UI操作，最终每一个回调结果都会回到主线程。\n    开启扫描：\n    BleManager.get().startScan {\n        onScanStart {\n\n        }\n        onLeScan { bleDevice, currentScanCount ->\n            //可以根据currentScanCount是否已有清空列表数据\n        }\n        onLeScanDuplicateRemoval { bleDevice, currentScanCount ->\n            //与onLeScan区别之处在于：同一个设备只会出现一次\n        }\n        onScanComplete { bleDeviceList, bleDeviceDuplicateRemovalList ->\n            //扫描到的数据是所有扫描次数的总和\n        }\n        onScanFail {\n            val msg: String = when (it) {\n                is BleScanFailType.UnSupportBle -> \"BleScanFailType.UnSupportBle: 设备不支持蓝牙\"\n                is BleScanFailType.NoBlePermission -> \"BleScanFailType.NoBlePermission: 权限不足，请检查\"\n                is BleScanFailType.GPSDisable -> \"BleScanFailType.BleDisable: 设备未打开GPS定位\"\n                is BleScanFailType.BleDisable -> \"BleScanFailType.BleDisable: 蓝牙未打开\"\n                is BleScanFailType.AlReadyScanning -> \"BleScanFailType.AlReadyScanning: 正在扫描\"\n                is BleScanFailType.ScanError -> {\n                    \"BleScanFailType.ScanError: ${it.throwable?.message}\"\n                }\n            }\n            BleLogger.e(msg)\n            Toast.makeText(application, msg, Toast.LENGTH_SHORT).show()\n        }\n    }\n\n#### 停止扫描\n    BleManager.get().stopScan()\n\n#### 是否扫描中\n    BleManager.get().isScanning()\n\n#### 连接\n    BleManager.get().connect(device)\n    BleManager.get().connect(deviceAddress)\n\n*    在某些型号手机上，connectGatt必须在主线程才能有效，所以把连接过程放在主线程，回调也在主线程。\n*    为保证重连成功率，建议断开后间隔一段时间之后进行重连。（非常关键，因为断开后会有释放资源的等待时间，如果马上重连，会导致连接的资源会被释放掉，而产生错误）\n*    v1.9.0添加字段isForeConnect，主要针对某些机型，当触发连接超时回调连接失败并释放资源之后，此时外设开启触发手机系统已连接，但BleCore资源被\n     释放 (bluetoothGatt是null)，或BleCore和系统的连接状态不一致，而导致setMtu和Notify/Indicate都失败。\n\n#### 停止连接\n    BleManager.get().stopConnect(device)\n\n#### 断开连接\n    BleManager.get().disConnect(device)\n    BleManager.get().disConnect(deviceAddress)\n\n*    断开后，并不会马上更新状态，所以马上连接会直接返回已连接，而且扫描不出来，要等待一定时间才可以\n*    BleConnectCallback中onDisConnecting、onDisConnected分别是，断开连接时触发onDisConnecting，\n     真正断开之后触发onDisConnected。(isActiveDisConnected = true的时候，触发onDisConnecting之后大约1秒左右\n     才会触发onDisConnected；isActiveDisConnected = false的时候，触发onDisConnecting之后大约5毫秒左右\n     才会触发onDisConnected)\n\n#### 是否已连接\n    BleManager.get().isConnected(bleDeviceAddress: String, simplySystemStatus: Boolean = true)\n    BleManager.get().isConnected(bleDevice: BleDevice?, simplySystemStatus: Boolean = true)\n\n*    simplySystemStatus 为true，只根据系统的状态规则；为false，会根据sdk的状态，换句话说，只根据系统的状态返回。\n     此字段的意义在于：有时，sdk资源被系统回收(状态未连接)，但是系统的状态是已连接。\n\n#### 扫描并连接，如果扫描到多个设备，则会连接第一个\n    BleManager.get().startScanAndConnect(bleScanCallback: BleScanCallback,\n                                         bleConnectCallback: BleConnectCallback)\n\n*    扫描到首个符合扫描规则的设备后，便停止扫描，然后连接该设备。\n\n#### 获取设备的BluetoothGatt对象\n    BleManager.get().getBluetoothGatt(device)\n\n#### 设置Notify\n    BleManager.get().notify(bleDevice: BleDevice,\n                                  serviceUUID: String,\n                                  notifyUUID: String,\n                                  bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n                                  bleIndicateCallback: BleIndicateCallback)\nBleDescriptorGetType设计原则\n*    正常情况下，每个特征值下至少有一个默认描述符，并且遵循蓝牙联盟定义的UUID规则，如\n     [com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR]便是蓝牙联盟定义的\n     客户端特性配置的描述符UUID，这样做是方便BLE终端在接入不同类型设备时，能够获取到正确的配置。\n     比如有一个APP，需要 接入A商家的智能手表和B商家的智能手表来监听用户的心跳，而如果A商家的智能手表或者B商家的智能手表\n     不遵循蓝牙联盟定义关于 心跳相关的UUID，则对APP来说就要分别去获取A商家的智能手表或者B商家的智能手表对应特征值的描述\n     符UUID，显然是不合理的。当然这个是需要硬件设备支持的，也就是说硬件设备可以自定义UUID，但需要遵循规则。\n*    在开发过程中，我们会遇到不同硬件设备定义UUID的情况，有的硬件设备通过特征值的UUID来获取描述符(用来writeDescriptor，\n     打开或关闭notify、indicate)，而非是通过系统提供接受通知自带的UUID获取描述符。此外特征值有多个描述符时，获取其中\n     一个描述符来写入数据，可能会导致onCharacteristicChanged函数没有回调，我不确定是否是硬件设备需要支持修改的问题。\n     因此[AllDescriptor]方式则是简单粗暴的将特征值下所有的描述符都写入数据，以保证onCharacteristicChanged函数回调，\n     这个方法经过了一系列设备的验证可行，但不保证是完全有效的。\n\n#### 取消Notify\n    BleManager.get().stopNotify(bleDevice: BleDevice,\n                                  serviceUUID: String,\n                                  notifyUUID: String,\n                                  bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default)\n\n#### 设置Indicate\n    BleManager.get().indicate(bleDevice: BleDevice,\n                                  serviceUUID: String,\n                                  indicateUUID: String,\n                                  bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n                                  bleIndicateCallback: BleIndicateCallback)\n\n#### 取消Indicate\n    BleManager.get().stopIndicate(bleDevice: BleDevice,\n                                  serviceUUID: String,\n                                  indicateUUID: String,\n                                  bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default)\n\n#### 读取信号值\n    BleManager.get().readRssi(bleDevice: BleDevice, bleRssiCallback: BleRssiCallback)\n    \n*    获取设备的信号强度，需要在设备连接之后进行。\n*    某些设备可能无法读取Rssi，不会回调onRssiSuccess(),而会因为超时而回调onRssiFail()。\n\n#### 设置Mtu值\n    BleManager.get().setMtu(bleDevice: BleDevice, bleMtuChangedCallback: BleMtuChangedCallback) \n\n*    设置MTU，需要在设备连接之后进行操作。\n*    默认每一个BLE设备都必须支持的MTU为23。\n*    MTU为23，表示最多可以发送20个字节的数据。\n*    该方法的参数mtu，最小设置为23，最大设置为512。\n*    并不是每台设备都支持拓展MTU，需要通讯双方都支持才行，也就是说，需要设备硬件也支持拓展MTU该方法才会起效果。\n     调用该方法后，可以通过onMtuChanged(int mtu)查看最终设置完后，设备的最大传输单元被拓展到多少。如果设备不支持，\n     可能无论设置多少，最终的mtu还是23。 \n*    建议在indicate、notify、read、write未完成的情况下，不要执行设置Mtu，否则会导致前者操作失败\n\n\n#### 设置连接的优先级\n    BleManager.get().setConnectionPriority(connectionPriority: Int)\n\n*    设置连接的优先级，一般用于高速传输大量数据的时候可以进行设置。\n\n#### 读特征值数据\n    BleManager.get().readData(bleDevice: BleDevice,\n                              serviceUUID: String,\n                              readUUID: String,\n                              bleIndicateCallback: BleReadCallback)\n\n#### 写数据\n     BleManager.get().writeData(bleDevice: BleDevice,\n                                serviceUUID: String,\n                                writeUUID: String,\n                                data: ByteArray,\n                                bleWriteCallback: BleWriteCallback)\n     BleManager.get().writeData(bleDevice: BleDevice,\n                                serviceUUID: String,\n                                writeUUID: String,\n                                data: SparseArray,\n                                bleWriteCallback: BleWriteCallback)\n\n*    因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n*    特殊情况下：indicate\\mtu\\notify\\read\\rssi 这些操作，同一个特征值在不同地方调用(不同callback)，最后面的操作\n     对应的回调才会触发，其他地方先前的操作对应的回调不会触发\n     解决方案：业务层每个特征值对应的操作维护一个单例的callback对象（假如为SingleCallback），在不同地方调用再传递callback\n             (放入到SingleCallback中的集合CallbackList)，SingleCallback 回调时循环CallbackList中的callback，这样就达到了\n              同一个特征值在不同地方调用，都能收到回调\n     \n*    indicate\\mtu\\notify\\read\\rssi这些操作 ，同一个特征值在不同地方调用，后面的操作会取消前面未完成的操作；write操作比较\n     特殊，每个写操作都会有回调，且write操作之间不会被取消。具体详情看taskId\n\n*    一次写操作，分包后，假如某个数据包写失败，后面的数据包不会继续写，例如一次写操作分包后有10个数据包，第7个写失败，后面第8、9、10不会再写     \n\n#### 断开某个设备的连接 释放资源\n    BleManager.get().close(bleDevice: BleDevice)\n\n#### 断开所有连接 释放资源\n    BleManager.get().closeAll()\n\n#### 一些移除监听的函数\n    BleManager.get().removeBleScanCallback()\n    BleManager.get().removeBleConnectCallback(bleDevice: BleDevice)\n    BleManager.get().removeBleIndicateCallback(bleDevice: BleDevice, indicateUUID: String)\n    BleManager.get().removeBleNotifyCallback(bleDevice: BleDevice, notifyUUID: String)\n    BleManager.get().removeBleRssiCallback(bleDevice: BleDevice)\n    BleManager.get().removeBleMtuChangedCallback(bleDevice: BleDevice)\n    BleManager.get().removeBleReadCallback(bleDevice: BleDevice, readUUID: String)\n    BleManager.get().removeBleWriteCallback(bleDevice: BleDevice, writeUUID: String)\n\n#### v1.5.0新增addBleEventCallback方法\n    有用户反馈，设置[connect]的bleConnectCallback、[notify]的bleNotifyCallback、\n     [indicate]的bleIndicateCallback、[setMtu]的bleMtuChangedCallback之后，当其他地方需要监听这些回调时比较\n    不方便，所以添加addBleEventCallback来实现。addBleEventCallback与上述回调共存\n\n#### v1.7.0新增系统蓝牙变化广播监听\n    BleManager.get().registerBluetoothStateReceiver()\n    BleManager.get().unRegisterBluetoothStateReceiver()\n\n#### v1.8.0新增stopConnect方法停止或者取消连接\n    BleManager.get().stopConnect(device)\n\n#### v2.0.0新增writeQueueData方法\n    BleManager.get().writeQueueData()，此方法支持跳过空数据包，支持写失败后重试，提高成功率。可以用于OTA升级\n\n#### 获取BleCore日志，使用自定义的日志框架打印日志或收集BleCore日志\n    通过第一步初始化时候，setEnableLog方法来决定是否使用BleCore的日志打印；\n    业务层，通过实现BleLogEvent接口，如下：\n\n    class MainActivity : BaseActivity(), BleLogEvent {\n        override fun onCreate(savedInstanceState: Bundle?) {\n            super.onCreate(savedInstanceState)\n            //添加BleCore日志监听\n            BleLogManager.get().addLogListener(this)\n        }\n\n        override fun onDestroy() {\n            super.onDestroy()\n            //移除BleCore日志监听\n            BleLogManager.get().removeLogListener(this)\n        }\n\n        /**\n         * 获取BleCore库的日志，并统一使用Logger来打印日志获取其他收集功能\n        */\n        override fun onLog(level: BleLogLevel, tag: String, message: String?) {\n            if (message.isNullOrEmpty()) {\n                return\n            }\n           when (level) {\n               is BleLogLevel.Debug ->  Logger.d(tag, message)\n               is BleLogLevel.Info ->  Logger.i(tag, message)\n               is BleLogLevel.Warn ->  Logger.w(tag, message)\n               is BleLogLevel.Error ->  Logger.e(tag, message)\n           }\n        }\n    } \n    \n\n#### [问题锦集](https://juejin.cn/post/6844903896100372494)，但愿对你有帮助\n    https://blog.51cto.com/u_16213573/7811086\n* 1、少部分机型会存在断开连接(gatt.disconnect)后，连接状态仍未刷新，导致其他机型连接不上外设。\n  [参考](https://stackoverflow.com/questions/44521828/android-ble-gatt-disconnected-vs-device-disconnected)\n\n#### 其他\n* 1、关闭系统蓝牙，没有触发onConnectionStateChange\n  解决方案：\n  1、操作前判断蓝牙状态，\n  2、系统蓝牙变化广播监听\n     BleManager.get().registerBluetoothStateReceiver(getBluetoothCallback())\n     BleManager.get().unRegisterBluetoothStateReceiver()\n\n\n#### 考虑把Collections.synchronizedList换成其他不会导致死锁的集合\n\n## License\n\n```\nCopyright (c) 2023 Bekie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n    id 'org.jetbrains.kotlin.android'\n}\n\nandroid {\n    namespace 'com.bhm.demo'\n    compileSdk 35\n\n    defaultConfig {\n        applicationId \"com.bhm.ble\"\n        minSdk 24\n        targetSdk 35\n        versionCode 261\n        versionName \"2.6.1\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    kotlinOptions {\n        jvmTarget = '17'\n    }\n    buildFeatures {\n        viewBinding true\n    }\n}\n\ndependencies {\n\n    testImplementation 'junit:junit:4.13.2'\n    androidTestImplementation 'androidx.test.ext:junit:1.3.0'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'\n\n    implementation \"io.github.cymchad:BaseRecyclerViewAdapterHelper:3.0.13\"\n    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'\n\n    implementation project(\":ble\")\n    implementation project(\":support\")\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "app/src/androidTest/java/com/bhm/demo/ExampleInstrumentedTest.kt",
    "content": "package com.bhm.demo\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.bhm.ble\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n            android:name=\"com.bhm.support.sdk.common.BaseApplication\"\n            android:allowBackup=\"true\"\n            android:fullBackupContent=\"@xml/backup_rules\"\n            tools:targetApi=\"31\"\n            android:dataExtractionRules=\"@xml/data_extraction_rules\"\n            android:label=\"@string/app_name\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:supportsRtl=\"true\"\n            android:theme=\"@style/Theme.BleCore\" >\n        <activity\n                android:name=\".ui.MainActivity\"\n                android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.bhm.demo.ui.OptionSettingActivity\" />\n        <activity android:name=\"com.bhm.demo.ui.DetailOperateActivity\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/BaseActivity.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo\n\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Message\nimport android.view.KeyEvent\nimport android.view.View\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.ViewModelStoreOwner\nimport androidx.viewbinding.ViewBinding\nimport com.bhm.network.base.HttpActivity\nimport com.bhm.network.base.HttpLoadingDialog\nimport com.bhm.network.core.HttpOptions\nimport com.bhm.support.sdk.common.BaseViewModel\nimport com.bhm.support.sdk.core.AppTheme\nimport com.bhm.support.sdk.core.WeakHandler\nimport com.bhm.support.sdk.entity.MessageEvent\nimport com.bhm.support.sdk.utils.ActivityUtil\nimport com.bhm.support.sdk.utils.ViewUtil\nimport com.noober.background.BackgroundLibrary\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport org.greenrobot.eventbus.ThreadMode\n\n\n/**\n * demo activity基类\n *\n * @author Buhuiming\n * @date 2023年05月24日 16时06分\n */\nabstract class BaseActivity<VM : BaseViewModel, B : ViewBinding> : HttpActivity(), Handler.Callback {\n\n    lateinit var viewModel: VM\n\n    private var activityLauncher: ActivityResultLauncher<Intent>? = null\n\n    private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null\n\n    private var arCallback: ((resultCode: Int, resultIntent: Intent?) -> Unit)? = null\n\n    private var permissionAgree: (() -> Unit)? = null\n\n    private var permissionRefuse: ((refusePermissions: ArrayList<String>) -> Unit)? = null\n\n    lateinit var mainHandler: WeakHandler\n\n    lateinit var viewBinding: B\n\n    lateinit var rootView: View\n\n    private var httpOptions: HttpOptions? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        BackgroundLibrary.inject(this)\n        super.onCreate(savedInstanceState)\n        AppTheme.fitSystemWindow(this)\n        ActivityUtil.addActivity(this)\n        EventBus.getDefault().register(this)\n        init()\n        viewBinding = ViewUtil.inflateWithGeneric(this, layoutInflater)\n        rootView = viewBinding.root\n        setContentView(rootView)\n        initData()\n        initEvent()\n    }\n\n    fun showLoading(msg: String? = \"\") {\n        httpOptions = HttpOptions.create(this)\n            .setLoadingDialog(HttpLoadingDialog())\n            .setLoadingTitle(msg)\n            .setDialogAttribute(\n                true,\n                cancelable = false,\n                dialogDismissInterruptRequest = true\n            )\n            .build()\n        httpOptions?.let {\n            it.dialog?.showLoading(it)\n        }\n    }\n\n    fun dismissLoading() {\n        httpOptions?.dialog?.dismissLoading(this)\n    }\n\n    protected open fun initData() {}\n\n    protected open fun initEvent() {}\n\n    override fun onDestroy() {\n        super.onDestroy()\n        EventBus.getDefault().unregister(this)\n        ActivityUtil.removeActivity(this)\n        mainHandler.removeCallbacksAndMessages(null)\n    }\n\n    /**\n     *ViewModel绑定\n     */\n    private fun init() {\n        viewModel = createViewModel(this, createViewModel())\n        activityLauncher = registerForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { result ->\n            if (result != null) {\n                arCallback?.let {\n                    it(result.resultCode, result.data)\n                }\n            }\n        }\n        permissionLauncher = registerForActivityResult(\n            ActivityResultContracts.RequestMultiplePermissions()\n        ) {\n            val refusePermission: ArrayList<String> = ArrayList()\n            it.keys.forEach { res ->\n                if (it[res] == false) {\n                    refusePermission.add(res)\n                }\n            }\n\n            if (refusePermission.isNotEmpty()) {\n                permissionRefuse?.let {\n                    it(refusePermission)\n                }\n            } else {\n                permissionAgree?.let {\n                    it()\n                }\n            }\n        }\n        mainHandler = WeakHandler(Looper.getMainLooper(), this)\n    }\n\n    /**\n     * 创建ViewModel\n     */\n    abstract fun createViewModel(): VM\n\n    /** 是否屏蔽返回键\n     * @return\n     */\n    protected open fun isRefusedBackPress(): Boolean {\n        return false\n    }\n\n    private fun createViewModel(owner: ViewModelStoreOwner, viewModel: VM): VM {\n        return ViewModelProvider(owner)[viewModel.javaClass]\n    }\n\n    fun startActivity(intent: Intent, arCallback: (resultCode: Int, resultIntent: Intent?) -> Unit) {\n        this.arCallback = arCallback\n        activityLauncher?.launch(intent)\n    }\n\n    fun requestPermission(permissions: Array<String>,\n                          agree: () -> Unit,\n                          refuse: (refusePermissions: ArrayList<String>) -> Unit\n    ) {\n        this.permissionAgree = agree\n        this.permissionRefuse = refuse\n        var allAgree = true\n        for (permission in permissions) {\n            if( ContextCompat.checkSelfPermission(this, permission) !=\n                PackageManager.PERMISSION_GRANTED) {\n                allAgree=false\n                break\n            }\n        }\n        if (allAgree) {\n            permissionAgree?.let {\n                it()\n            }\n            return\n        }\n        permissionLauncher?.launch(permissions)\n    }\n\n    override fun handleMessage(msg: Message): Boolean {\n        return false\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    open fun onMessageEvent(event: MessageEvent?) {\n        //EventBus Do something\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        if (isRefusedBackPress() && keyCode == KeyEvent.KEYCODE_BACK) {  //欢迎页 按物理返回键不能关闭APP\n            return true\n        } else if (keyCode == KeyEvent.KEYCODE_BACK) {\n            finish()\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/adapter/DetailsExpandAdapter.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.adapter\n\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.view.View\nimport android.widget.Button\nimport android.widget.CheckBox\nimport com.bhm.demo.R\nimport com.bhm.demo.entity.CharacteristicNode\nimport com.bhm.demo.entity.OperateType\nimport com.bhm.demo.entity.ServiceNode\nimport com.bhm.support.sdk.utils.ViewUtil\nimport com.chad.library.adapter.base.BaseNodeAdapter\nimport com.chad.library.adapter.base.entity.node.BaseNode\nimport com.chad.library.adapter.base.provider.BaseNodeProvider\nimport com.chad.library.adapter.base.viewholder.BaseViewHolder\n\n\n/**\n * 折叠布局 显示服务 特征值\n *\n * @author Buhuiming\n * @date 2023年06月01日 10时10分\n */\nclass DetailsExpandAdapter(nodeList: MutableList<BaseNode>,\n                           operateCallback: ((checkBox: CheckBox?,\n                                              operateType: OperateType,\n                                              isChecked: Boolean,\n                                              node: CharacteristicNode) -> Unit)? = null\n) : BaseNodeAdapter(nodeList) {\n\n    init {\n        // 需要占满一行的，使用此方法（例如section）\n        addFullSpanNodeProvider(ServiceNodeProvider())\n        // 普通的item provider\n        addNodeProvider(CharacteristicProvider(operateCallback))\n    }\n\n    override fun getItemType(data: List<BaseNode>, position: Int): Int {\n        return when (data[position]) {\n            is ServiceNode -> 0\n            is CharacteristicNode -> 1\n            else -> -1\n        }\n    }\n\n    class ServiceNodeProvider : BaseNodeProvider() {\n        override val itemViewType: Int\n            get() = 0\n        override val layoutId: Int\n            get() = R.layout.layout_recycler_service\n\n        override fun convert(helper: BaseViewHolder, item: BaseNode) {\n            val node = item as ServiceNode\n            helper.setText(R.id.tvServiceName, \"服务: (${node.serviceName})\")\n            helper.setText(R.id.tvServiceUUID, \"ServiceUUID: ${node.serviceUUID}\")\n            helper.setVisible(R.id.ivExpand, node.childNode?.isNotEmpty() == true)\n            if (node.isExpanded) {\n                helper.setImageResource(R.id.ivExpand, R.drawable.icon_down)\n            } else {\n                helper.setImageResource(R.id.ivExpand, R.drawable.icon_right)\n            }\n        }\n\n        override fun onClick(helper: BaseViewHolder, view: View, data: BaseNode, position: Int) {\n            super.onClick(helper, view, data, position)\n            getAdapter()?.expandOrCollapse(position, animate = true, notify = true)\n        }\n    }\n\n    class CharacteristicProvider(private val operateCallback: ((checkBox: CheckBox?,\n                                                                operateType: OperateType,\n                                                                isChecked: Boolean,\n                                                                node: CharacteristicNode) -> Unit)? = null\n    ) : BaseNodeProvider() {\n        override val itemViewType: Int\n            get() = 1\n        override val layoutId: Int\n            get() = R.layout.layout_recycler_characteristic\n\n        override fun convert(helper: BaseViewHolder, item: BaseNode) {\n            val node = item as CharacteristicNode\n            helper.setText(R.id.tvCharacteristicName, \"特征(${node.characteristicName})\")\n            helper.setText(R.id.tvCharacteristicUUID, \"CharacteristicUUID: ${node.characteristicUUID}\")\n            helper.setText(R.id.tvCharacteristicProperties, \"CharacteristicProperties: ${node.characteristicProperties}\")\n            helper.setGone(R.id.tvCharacteristicProperties, node.characteristicProperties.isEmpty())\n\n            val cbWrite = helper.getView<CheckBox>(R.id.cbWrite)\n            val btnReadData = helper.getView<Button>(R.id.btnReadData)\n            val cbNotify = helper.getView<CheckBox>(R.id.cbNotify)\n            val cbIndicate = helper.getView<CheckBox>(R.id.cbIndicate)\n            cbWrite.isChecked = node.enableWrite\n            cbNotify.isChecked = node.enableNotify\n            cbIndicate.isChecked = node.enableIndicate\n\n            val charaProp: Int = node.characteristicIntProperties\n            helper.setGone(R.id.btnReadData, charaProp and BluetoothGattCharacteristic.PROPERTY_READ <= 0)\n            helper.setGone(R.id.cbWrite, charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE <= 0 &&\n                    charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE <= 0)\n            helper.setGone(R.id.cbNotify, charaProp and BluetoothGattCharacteristic.PROPERTY_NOTIFY <= 0)\n            helper.setGone(R.id.cbIndicate, charaProp and BluetoothGattCharacteristic.PROPERTY_INDICATE <= 0)\n\n            cbWrite.setOnClickListener { buttonView ->\n                if (ViewUtil.isInvalidClick(buttonView)) {\n                    return@setOnClickListener\n                }\n                node.enableWrite = cbWrite.isChecked\n                val isChecked = cbWrite.isChecked\n                operateCallback?.invoke(buttonView as CheckBox, OperateType.Write, isChecked, node)\n            }\n            btnReadData.setOnClickListener {\n                if (ViewUtil.isInvalidClick(it)) {\n                    return@setOnClickListener\n                }\n                operateCallback?.invoke(null, OperateType.Read, false, node)\n            }\n            cbNotify.setOnClickListener { buttonView ->\n                if (ViewUtil.isInvalidClick(buttonView)) {\n                    return@setOnClickListener\n                }\n                node.enableNotify = cbNotify.isChecked\n                val isChecked = cbNotify.isChecked\n                operateCallback?.invoke(buttonView as CheckBox, OperateType.Notify, isChecked, node)\n            }\n            cbIndicate.setOnClickListener { buttonView ->\n                if (ViewUtil.isInvalidClick(buttonView)) {\n                    return@setOnClickListener\n                }\n                node.enableIndicate = cbIndicate.isChecked\n                val isChecked = cbIndicate.isChecked\n                operateCallback?.invoke(buttonView as CheckBox, OperateType.Indicate, isChecked, node)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/adapter/DeviceListAdapter.kt",
    "content": "package com.bhm.demo.adapter\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.content.ContextCompat\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.demo.R\nimport com.bhm.demo.databinding.LayoutRecyclerItemBinding\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com.chad.library.adapter.base.viewholder.BaseViewHolder\n\n\n/**\n * 设备列表\n *\n * @author Buhuiming\n * @date 2023年05月18日 11时06分\n */\nclass DeviceListAdapter(data: MutableList<BleDevice>?\n) : BaseQuickAdapter<BleDevice, DeviceListAdapter.VH>(0, data) {\n\n    class VH(\n        parent: ViewGroup,\n        val binding: LayoutRecyclerItemBinding = LayoutRecyclerItemBinding.inflate(\n            LayoutInflater.from(parent.context), parent, false\n        ),\n    ) : BaseViewHolder(binding.root)\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {\n        return VH(parent)\n    }\n\n    override fun convert(holder: VH, item: BleDevice) {\n        holder.binding.tvName.text = buildString {\n            append(item.deviceName)\n            append(\", \")\n            append(item.deviceAddress)\n        }\n//        holder.binding.btnRssi.text = \"${item.rssi ?: 0}\"\n        val rssi = item.rssi ?: 0\n        when {\n            rssi >= -65 -> {\n                holder.binding.ivRssi.setImageResource(R.drawable.adddevice_device_signal_four_icon)\n            }\n            rssi >= -75 -> {\n                holder.binding.ivRssi.setImageResource(R.drawable.adddevice_device_signal_three_icon)\n            }\n            rssi >= -85 -> {\n                holder.binding.ivRssi.setImageResource(R.drawable.adddevice_device_signal_two_icon)\n            }\n            else -> {\n                holder.binding.ivRssi.setImageResource(R.drawable.adddevice_device_signal_one_icon)\n            }\n        }\n        if (BleManager.get().isConnected(item)) {\n            holder.binding.btnConnect.text = \"断开\"\n            holder.binding.btnOperate.isEnabled = true\n            holder.binding.btnConnect.setBackgroundColor(\n                ContextCompat\n                .getColor(holder.binding.btnConnect.context, R.color.red))\n        } else {\n            holder.binding.btnConnect.text = \"连接\"\n            holder.binding.btnOperate.isEnabled = false\n            holder.binding.btnConnect.setBackgroundColor(ContextCompat\n                .getColor(holder.binding.btnConnect.context, R.color.black))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/adapter/LoggerListAdapter.kt",
    "content": "package com.bhm.demo.adapter\n\nimport android.graphics.Color\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport com.bhm.demo.databinding.LayoutRecyclerLogBinding\nimport com.bhm.demo.entity.LogEntity\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com.chad.library.adapter.base.viewholder.BaseViewHolder\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.logging.Level\n\n\n/**\n * 日志输出列表\n *\n * @author Buhuiming\n * @date 2023年6月1日 15时51分\n */\nclass LoggerListAdapter(data: MutableList<LogEntity>?\n) : BaseQuickAdapter<LogEntity, LoggerListAdapter.VH>(0, data) {\n\n    class VH(\n        parent: ViewGroup,\n        val binding: LayoutRecyclerLogBinding = LayoutRecyclerLogBinding.inflate(\n            LayoutInflater.from(parent.context), parent, false\n        ),\n    ) : BaseViewHolder(binding.root)\n\n    override fun onCreateDefViewHolder(parent: ViewGroup, viewType: Int): VH {\n        return VH(parent)\n    }\n\n    override fun convert(holder: VH, item: LogEntity) {\n        val df = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\", Locale.CANADA)\n        holder.binding.tvTime.text = buildString {\n            append(df.format(System.currentTimeMillis()))\n            append(\": \")\n        }\n        holder.binding.tvName.text = item.msg\n        when (item.level) {\n            Level.INFO -> holder.binding.tvName.setTextColor(Color.BLUE)\n            Level.WARNING -> holder.binding.tvName.setTextColor(Color.parseColor(\"#FF9800\"))\n            Level.OFF -> holder.binding.tvName.setTextColor(Color.RED)\n            Level.FINE -> holder.binding.tvName.setTextColor(Color.BLACK)\n            else -> holder.binding.tvName.setTextColor(Color.GRAY)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/constants/Constants.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.constants\n\nimport android.Manifest\nimport android.os.Build.VERSION\nimport android.os.Build.VERSION_CODES\n\n\n/**\n * 常量\n *\n * @author Buhuiming\n * @date 2023年05月19日 13时37分\n */\nval LOCATION_PERMISSION = if (VERSION.SDK_INT < VERSION_CODES.S) {\n    arrayOf(\n        //注册精准位置权限，否则可能Ble扫描不到设备\n        Manifest.permission.ACCESS_FINE_LOCATION,\n        Manifest.permission.ACCESS_COARSE_LOCATION,\n    )\n} else {\n    arrayOf(\n        Manifest.permission.BLUETOOTH_SCAN,\n        Manifest.permission.BLUETOOTH_CONNECT,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/entity/CharacteristicNode.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.entity\n\nimport com.chad.library.adapter.base.entity.node.BaseExpandNode\nimport com.chad.library.adapter.base.entity.node.BaseNode\n\n\n/**\n * 特征值数据\n * 不能使用data class, 属性写是var，否则BaseNodeAdapter会因为折叠刷新数据闪退\n * @author Buhuiming\n * @date 2023年06月01日 10时16分\n */\nclass CharacteristicNode(var characteristicName: String,\n                         var serviceUUID: String,\n                         var characteristicUUID: String,\n                         var characteristicProperties: String,\n                         var characteristicIntProperties: Int,\n                         var enableNotify: Boolean = false,\n                         var enableIndicate: Boolean = false,\n                         var enableWrite: Boolean = false,\n)  : BaseExpandNode() {\n\n    init {\n        isExpanded = false\n    }\n\n    override val childNode: MutableList<BaseNode>?\n        get() = null\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/entity/LogEntity.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.entity\n\nimport java.util.logging.Level\n\n\n/**\n * 日志信息\n *\n * @author Buhuiming\n * @date 2023年06月01日 15时54分\n */\ndata class LogEntity(\n    val level: Level,\n    val msg: String,\n    val time: Long = System.currentTimeMillis()\n)"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/entity/OperateType.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.entity\n\n\n/**\n * 操作类型\n *\n * @author Buhuiming\n * @date 2023年06月01日 14时11分\n */\nsealed class OperateType {\n\n    /**\n     * 写操作\n     */\n    object Write : OperateType()\n\n    /**\n     * 读操作\n     */\n    object Read : OperateType()\n\n    /**\n     * Notify操作\n     */\n    object Notify : OperateType()\n\n    /**\n     * Indicate操作\n     */\n    object Indicate : OperateType()\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/entity/RefreshBleDevice.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.entity\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * @author Buhuiming\n * @date 2023年05月30日 11时57分\n */\ndata class RefreshBleDevice(\n    val bleDevice: BleDevice?,\n    var tag: Long? = null\n)"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/entity/ServiceNode.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.entity\n\nimport com.chad.library.adapter.base.entity.node.BaseExpandNode\nimport com.chad.library.adapter.base.entity.node.BaseNode\n\n\n/**\n * 服务数据\n * 不能使用data class, 属性写是var，否则BaseNodeAdapter会因为折叠刷新数据闪退\n * @author Buhuiming\n * @date 2023年06月01日 10时11分\n */\nclass ServiceNode(var serviceName: String,\n                  var serviceUUID: String,\n                  var characteristicList: MutableList<BaseNode>?\n) : BaseExpandNode() {\n\n    init {\n        isExpanded = false\n    }\n\n    override val childNode: MutableList<BaseNode>?\n        get() = characteristicList\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/ui/DetailOperateActivity.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.ui\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport android.content.Intent\nimport android.os.Build\nimport android.view.KeyEvent\nimport android.view.View\nimport android.widget.CheckBox\nimport android.widget.Toast\nimport androidx.core.view.WindowCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.DefaultItemAnimator\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.demo.BaseActivity\nimport com.bhm.demo.R\nimport com.bhm.demo.adapter.DetailsExpandAdapter\nimport com.bhm.demo.adapter.LoggerListAdapter\nimport com.bhm.demo.databinding.ActivityDetailBinding\nimport com.bhm.demo.entity.CharacteristicNode\nimport com.bhm.demo.entity.OperateType\nimport com.bhm.demo.vm.DetailViewModel\nimport com.bhm.support.sdk.core.AppTheme\nimport com.bhm.support.sdk.entity.MessageEvent\nimport com.bhm.support.sdk.utils.ViewUtil\nimport kotlinx.coroutines.launch\n\n\n/**\n * 服务，特征 操作页面\n *\n * @author Buhuiming\n * @date 2023年06月01日 09时17分\n */\nclass DetailOperateActivity : BaseActivity<DetailViewModel, ActivityDetailBinding>() {\n\n    override fun createViewModel() = DetailViewModel(application)\n\n    private var bleDevice: BleDevice? = null\n\n    private var expandAdapter: DetailsExpandAdapter? = null\n\n    private var loggerListAdapter: LoggerListAdapter? = null\n\n    private var disConnectWhileClose = false // 关闭页面后是否断开连接\n\n    private var currentSendNode: CharacteristicNode? = null\n\n    private var connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED\n\n    private var operateCallback: ((checkBox: CheckBox?,\n                                   operateType: OperateType,\n                                   isChecked: Boolean,\n                                   node: CharacteristicNode) -> Unit)? = null\n\n    override fun initData() {\n        super.initData()\n        val controller = WindowCompat.getInsetsController(\n            window,\n            window.decorView\n        )\n        controller.isAppearanceLightStatusBars = true\n        controller.isAppearanceLightNavigationBars = true\n        bleDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            intent.getParcelableExtra(\"data\", BleDevice::class.java)\n        } else {\n            @Suppress(\"DEPRECATION\")\n            intent.getParcelableExtra(\"data\")\n        }\n        disConnectWhileClose = intent.getBooleanExtra(\"disConnectWhileClose\", false)\n\n        if (bleDevice == null) {\n            finish()\n            return\n        }\n        viewBinding.tvName.text = buildString {\n            append(\"设备广播名：\")\n            append(getBleDevice().deviceName)\n            append(\"\\r\\n\")\n            append(\"地址：${getBleDevice().deviceAddress}\")\n        }\n        initList()\n    }\n\n    private fun getBleDevice(): BleDevice {\n        return bleDevice!!\n    }\n\n    private fun initList() {\n        val layoutManager = LinearLayoutManager(applicationContext)\n        layoutManager.orientation = LinearLayoutManager.VERTICAL\n        viewBinding.recyclerView.layoutManager = layoutManager\n        viewBinding.recyclerView.addItemDecoration(DividerItemDecoration(applicationContext, DividerItemDecoration.VERTICAL))\n        operateCallback = { checkBox, operateType, isChecked, node ->\n            when (operateType) {\n                is OperateType.Write -> {\n                    if (isChecked) {\n                        if (viewBinding.btnSend.isEnabled) {\n                            checkBox?.isChecked = false\n                            Toast.makeText(applicationContext, \"请取消其他特征值写操作\", Toast.LENGTH_SHORT).show()\n                        } else {\n                            viewBinding.btnSend.isEnabled = true\n                            viewBinding.etContent.isEnabled = true\n                            currentSendNode = node\n                        }\n                    } else {\n                        viewBinding.btnSend.isEnabled = false\n                        viewBinding.etContent.isEnabled = false\n                        currentSendNode = null\n                    }\n                }\n                is OperateType.Read -> {\n                    viewModel.readData(getBleDevice(), node)\n                }\n                is OperateType.Notify -> {\n                    if (isChecked) {\n                        viewModel.notify(getBleDevice(), node)\n                    } else {\n                        viewModel.stopNotify(getBleDevice(), node)\n                    }\n                }\n                is OperateType.Indicate -> {\n                    if (isChecked) {\n                        viewModel.indicate(getBleDevice(), node)\n                    } else {\n                        viewModel.stopIndicate(getBleDevice(), node)\n                    }\n                }\n            }\n        }\n        expandAdapter = DetailsExpandAdapter(viewModel.getListData(getBleDevice()), operateCallback)\n        viewBinding.recyclerView.adapter = expandAdapter\n        if ((expandAdapter?.data?.size?: 0) > 0) {\n            expandAdapter?.expand(0)\n        }\n\n        val logLayoutManager = LinearLayoutManager(applicationContext)\n        logLayoutManager.orientation = LinearLayoutManager.VERTICAL\n        viewBinding.logRecyclerView.setHasFixedSize(true)\n        viewBinding.logRecyclerView.layoutManager = logLayoutManager\n        (viewBinding.recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false\n        loggerListAdapter = LoggerListAdapter(viewModel.listLogData)\n        viewBinding.logRecyclerView.adapter = loggerListAdapter\n\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun initEvent() {\n        super.initEvent()\n        lifecycleScope.launch {\n            viewModel.listLogStateFlow.collect {\n                viewModel.listLogData.add(it)\n                val position = viewModel.listLogData.size - 1\n                loggerListAdapter?.notifyItemInserted(position)\n                viewBinding.logRecyclerView.smoothScrollToPosition(position)\n            }\n        }\n        lifecycleScope.launch {\n            viewModel.listRefreshStateFlow.collect {\n                if (it.isNotEmpty()) {\n                    expandAdapter?.notifyDataSetChanged()\n                }\n            }\n        }\n\n        viewBinding.btnConnectionPriority.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            when (connectionPriority) {\n                BluetoothGatt.CONNECTION_PRIORITY_BALANCED -> connectionPriority =\n                    BluetoothGatt.CONNECTION_PRIORITY_HIGH\n                BluetoothGatt.CONNECTION_PRIORITY_HIGH -> connectionPriority =\n                    BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER\n                BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER -> connectionPriority =\n                    BluetoothGatt.CONNECTION_PRIORITY_BALANCED\n            }\n            viewModel.setConnectionPriority(getBleDevice(), connectionPriority)\n        }\n\n        viewBinding.btnClear.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            loggerListAdapter?.notifyItemRangeRemoved(0, viewModel.listLogData.size)\n            viewModel.listLogData.clear()\n        }\n\n        viewBinding.btnSetMtu.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            viewModel.setMtu(getBleDevice())\n        }\n\n        viewBinding.btnReadRssi.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            viewModel.readRssi(getBleDevice())\n        }\n\n        viewBinding.btnSend.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            val content = viewBinding.etContent.text.toString()\n            if (content.isEmpty()) {\n                Toast.makeText(applicationContext, \"请输入数据\", Toast.LENGTH_SHORT).show()\n                return@setOnClickListener\n            }\n            if (currentSendNode == null) {\n                Toast.makeText(applicationContext, \"请选择特征值写操作\", Toast.LENGTH_SHORT).show()\n                return@setOnClickListener\n            }\n            currentSendNode?.let { node ->\n                viewModel.writeData(\n                    getBleDevice(),\n                    node,\n                    content\n                )\n            }\n        }\n    }\n\n    public fun showContent(view: View) {\n        if (viewBinding.llContent.visibility == View.VISIBLE) {\n            viewBinding.llContent.visibility = View.GONE\n        } else {\n            viewBinding.llContent.visibility = View.VISIBLE\n        }\n    }\n\n    /**\n     * 接收到断开通知\n     */\n    override fun onMessageEvent(event: MessageEvent?) {\n        super.onMessageEvent(event)\n        event?.let {\n            val device = it.data as BleDevice\n            if (getBleDevice() == device) {\n                BleManager.get().close(getBleDevice())\n                setResult(0, null)\n                finish()\n            }\n        }\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        if (keyCode == KeyEvent.KEYCODE_BACK) {\n            if (disConnectWhileClose) {\n                BleManager.get().close(getBleDevice())\n                setResult(0, Intent())\n            } else {\n                BleManager.get().removeAllCharacterCallback(getBleDevice())\n                setResult(0, null)\n            }\n            finish()\n            return true\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        expandAdapter = null\n        operateCallback = null\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/ui/MainActivity.kt",
    "content": "package com.bhm.demo.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.view.View\nimport android.widget.Toast\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.DefaultItemAnimator\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.demo.BaseActivity\nimport com.bhm.demo.R\nimport com.bhm.demo.adapter.DeviceListAdapter\nimport com.bhm.demo.constants.LOCATION_PERMISSION\nimport com.bhm.demo.databinding.ActivityMainBinding\nimport com.bhm.demo.vm.MainViewModel\nimport com.bhm.support.sdk.utils.ViewUtil\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * 主页面\n * @author Buhuiming\n * @date :2023/5/24 15:39\n */\nclass MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>() {\n\n    private var listAdapter: DeviceListAdapter? = null\n\n    private var autoOpenDetailsActivity = false\n\n    override fun createViewModel() = MainViewModel(application)\n\n    override fun initData() {\n        super.initData()\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n        val controller = WindowCompat.getInsetsController(\n            window,\n            window.decorView\n        )\n        controller.isAppearanceLightStatusBars = false\n        controller.isAppearanceLightNavigationBars = false\n        ViewCompat.setOnApplyWindowInsetsListener(viewBinding.vTop) { _: View, insets: WindowInsetsCompat ->\n            val statusBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())\n            val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            viewBinding.vTop.layoutParams.height = statusBars.top\n            rootView.setPadding(0, 0, 0, navBars.bottom)\n            insets\n        }\n        ViewCompat.requestApplyInsets(viewBinding.vTop)\n        initList()\n        viewModel.initBle()\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun initEvent() {\n        super.initEvent()\n        lifecycleScope.launch {\n            //添加扫描到的设备 刷新列表\n            viewModel.listDRStateFlow.collect {\n                if (it.deviceName != null && it.deviceAddress != null) {\n                    val position = (listAdapter?.itemCount?: 1) - 1\n                    listAdapter?.notifyItemInserted(position)\n                    viewBinding.recyclerView.smoothScrollToPosition(position)\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.scanStopStateFlow.collect {\n                viewBinding.pbLoading.visibility = if (it) { View.INVISIBLE } else { View.VISIBLE }\n                viewBinding.btnStart.text = if (it) { \"开启扫描\" } else { \"扫描中...\" }\n                viewBinding.btnStart.isEnabled = it\n                viewBinding.btnConnect.isEnabled = it\n                viewBinding.btnSetting.isEnabled = it\n                viewBinding.btnStop.isEnabled = !it\n            }\n        }\n\n        lifecycleScope.launch {\n            //连接设备后 刷新列表\n            viewModel.refreshStateFlow.collect {\n                delay(300)\n                dismissLoading()\n                if (it?.bleDevice == null) {\n                    listAdapter?.notifyDataSetChanged()\n                    return@collect\n                }\n                it.bleDevice.let { bleDevice ->\n                    val position = listAdapter?.data?.indexOf(bleDevice) ?: -1\n                    if (position >= 0) {\n                        listAdapter?.notifyItemChanged(position)\n                    }\n                    val isConnected= viewModel.isConnected(bleDevice)\n                    if (it.bleDevice.deviceAddress == viewBinding.etAddress.text.toString()) {\n                        viewBinding.btnConnect.isEnabled = !isConnected\n                    }\n                    if (isConnected && autoOpenDetailsActivity) {\n                        openDetails(it.bleDevice)\n                    }\n                    autoOpenDetailsActivity = false\n                }\n            }\n        }\n\n        listAdapter?.addChildClickViewIds(R.id.btnConnect, R.id.btnOperate)\n        listAdapter?.setOnItemChildClickListener { adapter, view, position ->\n            if (ViewUtil.isInvalidClick(view)) {\n                return@setOnItemChildClickListener\n            }\n            val bleDevice: BleDevice? = adapter.data[position] as BleDevice?\n            if (view.id == R.id.btnConnect) {\n                if (viewModel.isConnected(bleDevice)) {\n                    showLoading(\"断开中...\")\n                    viewModel.disConnect(bleDevice)\n                } else {\n                    showLoading(\"连接中...\")\n                    viewModel.connect(bleDevice)\n                }\n            } else if (view.id == R.id.btnOperate) {\n                openDetails(bleDevice)\n            }\n        }\n\n        viewBinding.btnConnect.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            requestPermission(\n                LOCATION_PERMISSION,\n                {\n                    BleLogger.d(\"获取到了权限\")\n                    val address = viewBinding.etAddress.text.toString()\n                    if (address.isEmpty()) {\n                        Toast.makeText(application, \"请输入设备地址\", Toast.LENGTH_SHORT).show()\n                        return@requestPermission\n                    }\n                    if (!Regex(\"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$\").matches(address)) {\n                        Toast.makeText(application, \"请输入正确的设备地址\", Toast.LENGTH_SHORT).show()\n                        return@requestPermission\n                    }\n                    autoOpenDetailsActivity = true\n                    showLoading(\"连接中...\")\n//            viewModel.startScanAndConnect(this@MainActivity)\n                    viewModel.connect(viewBinding.etAddress.text.toString())\n\n                }, {\n                    BleLogger.w(\"缺少定位权限\")\n                }\n            )\n        }\n\n        viewBinding.btnSetting.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            startActivity(Intent(this@MainActivity, OptionSettingActivity::class.java))\n        }\n\n        viewBinding.btnStart.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            listAdapter?.notifyItemRangeRemoved(0, viewModel.listDRData.size)\n            viewModel.listDRData.clear()\n            viewModel.startScan(this@MainActivity)\n        }\n\n        viewBinding.btnStop.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            viewModel.stopScan()\n        }\n    }\n\n    private fun initList() {\n        val layoutManager = LinearLayoutManager(this)\n        layoutManager.orientation = LinearLayoutManager.VERTICAL\n        viewBinding.recyclerView.setHasFixedSize(true)\n        viewBinding.recyclerView.layoutManager = layoutManager\n        viewBinding.recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))\n        //解决RecyclerView局部刷新时闪烁\n        (viewBinding.recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false\n        listAdapter = DeviceListAdapter(viewModel.listDRData)\n        viewBinding.recyclerView.adapter = listAdapter\n    }\n\n    /**\n     * 打开操作页面\n     */\n    private fun openDetails(bleDevice: BleDevice?) {\n        if (viewModel.isConnected(bleDevice)) {\n            val intent = Intent(this@MainActivity, DetailOperateActivity::class.java)\n            intent.putExtra(\"data\", bleDevice)\n            intent.putExtra(\"disConnectWhileClose\", autoOpenDetailsActivity)\n            startActivity(intent) { _, resultIntent ->\n                if (resultIntent != null) {\n                    showLoading(\"断开中...\")\n                    //断开需要一定的时间，才可以连接，这里防止没断开完成，马上点击连接\n                    lifecycleScope.launch {\n                        delay(1200)\n                        dismissLoading()\n                    }\n                }\n            }\n        } else {\n            Toast.makeText(application, \"设备未连接\", Toast.LENGTH_SHORT).show()\n            val index = listAdapter?.data?.indexOf(bleDevice) ?: -1\n            if (index >= 0) {\n                listAdapter?.notifyItemChanged(index)\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        viewModel.stopScan()\n        viewModel.close()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/ui/OptionSettingActivity.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.ui\n\nimport android.widget.ArrayAdapter\nimport androidx.core.view.WindowCompat\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.attribute.BleOptions\nimport com.bhm.ble.data.BleTaskQueueType\nimport com.bhm.ble.data.Constants.AUTO_CONNECT\nimport com.bhm.ble.data.Constants.CONTAIN_SCAN_DEVICE_NAME\nimport com.bhm.ble.data.Constants.DEFAULT_AUTO_SET_MTU\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_RETRY_COUNT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_RETRY_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_MAX_CONNECT_NUM\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_RETRY_COUNT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_RETRY_INTERVAL\nimport com.bhm.ble.data.Constants.ENABLE_LOG\nimport com.bhm.ble.data.Constants.SCAN_NEED_CHECK_GPS\nimport com.bhm.demo.BaseActivity\nimport com.bhm.demo.R\nimport com.bhm.demo.databinding.ActivitySettingBinding\nimport com.bhm.support.sdk.common.BaseViewModel\nimport com.bhm.support.sdk.core.AppTheme\nimport com.bhm.support.sdk.utils.ViewUtil\n\n\n/**\n * 设置扫描配置项页面\n *\n * @author Buhuiming\n * @date 2023年05月24日 15时38分\n */\nclass OptionSettingActivity : BaseActivity<BaseViewModel, ActivitySettingBinding>() {\n\n    override fun createViewModel() = BaseViewModel (application)\n\n    override fun initData() {\n        super.initData()\n        val controller = WindowCompat.getInsetsController(\n            window,\n            window.decorView\n        )\n        controller.isAppearanceLightStatusBars = true\n        controller.isAppearanceLightNavigationBars = true\n        val taskQueueTypes = arrayOf(\"Default\", \"Operate\", \"Independent\")\n        viewBinding.spTaskQueueType.adapter =\n            ArrayAdapter(this, android.R.layout.simple_spinner_item, taskQueueTypes)\n        viewBinding.spTaskQueueType.setSelection(0)\n        val options = BleManager.get().getOptions()\n        options?.let {\n            val etScanServiceUuidText = StringBuilder()\n            options.scanServiceUuids.forEach { string ->\n                if (string.isNotEmpty()) {\n                    etScanServiceUuidText.append(string)\n                    etScanServiceUuidText.append(\",\")\n                }\n            }\n\n            if (etScanServiceUuidText.isNotEmpty()) {\n                etScanServiceUuidText.delete(etScanServiceUuidText.length - 1,\n                    etScanServiceUuidText.length)\n            }\n            val etScanDeviceNameText = StringBuilder()\n            options.scanDeviceNames.forEach { string ->\n                if (string.isNotEmpty()) {\n                    etScanDeviceNameText.append(string)\n                    etScanDeviceNameText.append(\",\")\n                }\n            }\n            if (etScanDeviceNameText.isNotEmpty()) {\n                etScanDeviceNameText.delete(etScanDeviceNameText.length - 1,\n                    etScanDeviceNameText.length)\n            }\n            val etScanDeviceAddressText = StringBuilder()\n            options.scanDeviceAddresses.forEach { string ->\n                if (string.isNotEmpty()) {\n                    etScanDeviceAddressText.append(string)\n                    etScanDeviceAddressText.append(\",\")\n                }\n            }\n            if (etScanDeviceAddressText.isNotEmpty()) {\n                etScanDeviceAddressText.delete(etScanDeviceAddressText.length - 1,\n                    etScanDeviceAddressText.length)\n            }\n            viewBinding.etScanServiceUuid.setText(etScanServiceUuidText.toString())\n            viewBinding.etScanDeviceName.setText(etScanDeviceNameText.toString())\n            viewBinding.etScanDeviceAddress.setText(etScanDeviceAddressText.toString())\n            viewBinding.etScanOutTime.setText(it.scanMillisTimeOut.toString())\n            viewBinding.etScanRetryCount.setText(it.scanRetryCount.toString())\n            viewBinding.etScanRetryInterval.setText(it.scanRetryInterval.toString())\n            viewBinding.etConnectOutTime.setText(it.connectMillisTimeOut.toString())\n            viewBinding.etConnectRetryCount.setText(it.connectRetryCount.toString())\n            viewBinding.etConnectRetryInterval.setText(it.connectRetryInterval.toString())\n            viewBinding.etOperateMillisTimeOut.setText(it.operateMillisTimeOut.toString())\n            viewBinding.etOperateInterval.setText(it.operateInterval.toString())\n            viewBinding.etMaxConnectNum.setText(it.maxConnectNum.toString())\n            viewBinding.etMTU.setText(it.mtu.toString())\n            viewBinding.cbContainScanDeviceName.isChecked = it.containScanDeviceName\n            viewBinding.cbLogger.isChecked = it.enableLog\n            viewBinding.cbNeedCheckGPS.isChecked = it.needCheckGps\n            viewBinding.cbMtu.isChecked = it.autoSetMtu\n            viewBinding.cbAutoConnect.isChecked = it.autoConnect\n            viewBinding.spTaskQueueType.setSelection(getTaskQueueType(it.taskQueueType))\n        }\n    }\n\n    override fun initEvent() {\n        super.initEvent()\n        viewBinding.btnReSet.setOnClickListener {\n            if (ViewUtil.isInvalidClick(it)) {\n                return@setOnClickListener\n            }\n            viewBinding.etScanServiceUuid.setText(\"\")\n            viewBinding.etScanDeviceName.setText(\"\")\n            viewBinding.etScanDeviceAddress.setText(\"\")\n            viewBinding.etScanOutTime.setText(DEFAULT_SCAN_MILLIS_TIMEOUT.toString())\n            viewBinding.etScanRetryCount.setText(DEFAULT_SCAN_RETRY_COUNT.toString())\n            viewBinding.etScanRetryInterval.setText(DEFAULT_SCAN_RETRY_INTERVAL.toString())\n            viewBinding.etConnectOutTime.setText(DEFAULT_CONNECT_MILLIS_TIMEOUT.toString())\n            viewBinding.etConnectRetryCount.setText(DEFAULT_CONNECT_RETRY_COUNT.toString())\n            viewBinding.etConnectRetryInterval.setText(DEFAULT_CONNECT_RETRY_INTERVAL.toString())\n            viewBinding.etOperateMillisTimeOut.setText(DEFAULT_OPERATE_MILLIS_TIMEOUT.toString())\n            viewBinding.etOperateInterval.setText(DEFAULT_OPERATE_INTERVAL.toString())\n            viewBinding.etMaxConnectNum.setText(DEFAULT_MAX_CONNECT_NUM.toString())\n            viewBinding.etMTU.setText(DEFAULT_MTU.toString())\n            viewBinding.cbContainScanDeviceName.isChecked = CONTAIN_SCAN_DEVICE_NAME\n            viewBinding.cbLogger.isChecked = ENABLE_LOG\n            viewBinding.cbNeedCheckGPS.isChecked = SCAN_NEED_CHECK_GPS\n            viewBinding.cbMtu.isChecked = DEFAULT_AUTO_SET_MTU\n            viewBinding.cbAutoConnect.isChecked = AUTO_CONNECT\n            viewBinding.spTaskQueueType.setSelection(0)\n            BleManager.get().init(application)\n        }\n        viewBinding.btnSave.setOnClickListener { view ->\n            if (ViewUtil.isInvalidClick(view)) {\n                return@setOnClickListener\n            }\n            if (viewBinding.etMaxConnectNum.text.toString().toInt() > 7) {\n                viewBinding.etMaxConnectNum.setText(\"7\")\n            }\n\n            val builder = BleOptions.builder()\n            val scanServiceUuids = viewBinding.etScanServiceUuid.text.toString().split(\",\")\n            scanServiceUuids.forEach {\n                builder.setScanServiceUuid(it)\n            }\n            val scanDeviceNames = viewBinding.etScanDeviceName.text.toString().split(\",\")\n            scanDeviceNames.forEach {\n                builder.setScanDeviceName(it)\n            }\n            val scanDeviceAddresses = viewBinding.etScanDeviceAddress.text.toString().split(\",\")\n            scanDeviceAddresses.forEach {\n                builder.setScanDeviceAddress(it)\n            }\n\n            builder\n                .isContainScanDeviceName(viewBinding.cbContainScanDeviceName.isChecked)\n                .setEnableLog(viewBinding.cbLogger.isChecked)\n                .setScanMillisTimeOut(viewBinding.etScanOutTime.text.toString().toLong())\n//                //这个机制是：不会因为扫描的次数导致上一次扫描到的数据被清空，也就是onScanStart和onScanComplete\n//                //都只会回调一次，而且扫描到的数据是所有扫描次数的总和\n                .setScanRetryCountAndInterval(viewBinding.etScanRetryCount.text.toString().toInt(),\n                    viewBinding.etScanRetryInterval.text.toString().toLong())\n                .setConnectMillisTimeOut(viewBinding.etConnectOutTime.text.toString().toLong())\n                .setConnectRetryCountAndInterval(viewBinding.etConnectRetryCount.text.toString().toInt(),\n                    viewBinding.etConnectRetryInterval.text.toString().toLong())\n                .setAutoConnect(viewBinding.cbAutoConnect.isChecked)\n                .setOperateMillisTimeOut(viewBinding.etOperateMillisTimeOut.text.toString().toLong())\n                .setOperateInterval(viewBinding.etOperateInterval.text.toString().toLong())\n                .setMaxConnectNum(viewBinding.etMaxConnectNum.text.toString().toInt())\n                .setMtu(viewBinding.etMTU.text.toString().toInt(), viewBinding.cbMtu.isChecked)\n                .setTaskQueueType(getTaskQueueType(viewBinding.spTaskQueueType.selectedItemPosition))\n                .setNeedCheckGps(viewBinding.cbNeedCheckGPS.isChecked)\n            BleManager.get().init(application, builder.build())\n            BleManager.get().disConnectAll()\n            finish()\n        }\n    }\n\n    private fun getTaskQueueType(taskQueueType: BleTaskQueueType): Int {\n        return when (taskQueueType) {\n            BleTaskQueueType.Default -> 0\n            BleTaskQueueType.Operate -> 1\n            BleTaskQueueType.Independent -> 2\n        }\n    }\n\n    private fun getTaskQueueType(taskQueueType: Int): BleTaskQueueType {\n        return when (taskQueueType) {\n            1 -> BleTaskQueueType.Operate\n            2 -> BleTaskQueueType.Independent\n            else -> BleTaskQueueType.Default\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/vm/DetailViewModel.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.demo.vm\n\nimport android.app.Application\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.util.SparseArray\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.data.BleDescriptorGetType\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport com.bhm.demo.entity.CharacteristicNode\nimport com.bhm.demo.entity.LogEntity\nimport com.bhm.demo.entity.ServiceNode\nimport com.bhm.support.sdk.common.BaseViewModel\nimport com.chad.library.adapter.base.entity.node.BaseNode\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport java.util.logging.Level\n\n\n/**\n *\n * @author Buhuiming\n * @date 2023年06月01日 09时18分\n */\nclass DetailViewModel(application: Application) : BaseViewModel(application) {\n\n    private val listLogMutableStateFlow = MutableStateFlow(LogEntity(Level.INFO, \"数据适配完毕\"))\n\n    val listLogStateFlow: StateFlow<LogEntity> = listLogMutableStateFlow\n\n    private val listRefreshMutableStateFlow = MutableStateFlow(\"\")\n\n    val listRefreshStateFlow: StateFlow<String> = listRefreshMutableStateFlow\n\n    val listLogData = mutableListOf<LogEntity>()\n\n    /**\n     * 根据bleDevice拿到服务特征值数据\n     */\n    fun getListData(bleDevice: BleDevice): MutableList<BaseNode> {\n        val gatt = BleManager.get().getBluetoothGatt(bleDevice)\n        val list: MutableList<BaseNode> = arrayListOf()\n        gatt?.services?.forEachIndexed { index, service ->\n            val childList: MutableList<BaseNode> = arrayListOf()\n            service.characteristics?.forEachIndexed { position, characteristics ->\n                val characteristicNode = CharacteristicNode(\n                    position.toString(),\n                    service.uuid.toString(),\n                    characteristics.uuid.toString(),\n                    getOperateType(characteristics),\n                    characteristics.properties,\n                    enableNotify = false,\n                    enableIndicate = false,\n                    enableWrite = false\n                )\n                childList.add(characteristicNode)\n            }\n            val serviceNode = ServiceNode(\n                index.toString(),\n                service.uuid.toString(),\n                childList\n            )\n            list.add(serviceNode)\n        }\n        return list\n    }\n\n    /**\n     * 获取特征值的属性\n     */\n    private fun getOperateType(characteristic: BluetoothGattCharacteristic): String {\n        val property = StringBuilder()\n        val charaProp: Int = characteristic.properties\n        if (charaProp and BluetoothGattCharacteristic.PROPERTY_READ != 0) {\n            property.append(\"Read\")\n            property.append(\" , \")\n        }\n        if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) {\n            property.append(\"Write\")\n            property.append(\" , \")\n        }\n        if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {\n            property.append(\"Write No Response\")\n            property.append(\" , \")\n        }\n        if (charaProp and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {\n            property.append(\"Notify\")\n            property.append(\" , \")\n        }\n        if (charaProp and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {\n            property.append(\"Indicate\")\n            property.append(\" , \")\n        }\n        if (property.length > 1) {\n            property.delete(property.length - 2, property.length - 1)\n        }\n        return if (property.isNotEmpty()) {\n            property.toString()\n        } else {\n            \"\"\n        }\n    }\n\n    /**\n     * 添加日志显示\n     */\n    @Synchronized\n    fun addLogMsg(logEntity: LogEntity) {\n        listLogMutableStateFlow.value = logEntity\n    }\n\n    /**\n     * notify\n     */\n    fun notify(bleDevice: BleDevice,\n               node: CharacteristicNode\n    ) {\n        BleManager.get().notify(bleDevice, node.serviceUUID, node.characteristicUUID, 2000, BleDescriptorGetType.AllDescriptor) {\n            onNotifyFail { _, _, t ->\n                addLogMsg(LogEntity(Level.OFF, \"notify失败：${t.message}\"))\n                node.enableNotify = false\n                listRefreshMutableStateFlow.value = System.currentTimeMillis().toString()\n            }\n            onNotifySuccess { _, _ ->\n                addLogMsg(LogEntity(Level.FINE, \"notify成功：${node.characteristicUUID}\"))\n            }\n            onCharacteristicChanged {_, _, data ->\n                //数据处理在IO线程，显示UI要切换到主线程\n                launchInMainThread {\n                    addLogMsg(\n                        LogEntity(\n                            Level.INFO, \"Notify接收到${node.characteristicUUID}的数据：\" +\n                                    BleUtil.bytesToHex(data)\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n    /**\n     * stop notify\n     */\n    fun stopNotify(\n        bleDevice: BleDevice,\n        node: CharacteristicNode\n    ) {\n        val success = BleManager.get().stopNotify(bleDevice, node.serviceUUID, node.characteristicUUID, BleDescriptorGetType.AllDescriptor)\n        if (success == true) {\n            addLogMsg(LogEntity(Level.FINE, \"notify取消成功：${node.characteristicUUID}\"))\n        } else {\n            addLogMsg(LogEntity(Level.OFF, \"notify取消失败：${node.characteristicUUID}\"))\n        }\n    }\n\n    /**\n     * indicate\n     */\n    fun indicate(bleDevice: BleDevice,\n                 node: CharacteristicNode\n    ) {\n        BleManager.get().indicate(bleDevice, node.serviceUUID, node.characteristicUUID, BleDescriptorGetType.AllDescriptor) {\n            onIndicateFail {_, _, t ->\n                addLogMsg(LogEntity(Level.OFF, \"indicate失败：${t.message}\"))\n                node.enableIndicate = false\n                listRefreshMutableStateFlow.value = System.currentTimeMillis().toString()\n            }\n            onIndicateSuccess { _, _, ->\n                addLogMsg(LogEntity(Level.FINE, \"indicate成功：${node.characteristicUUID}\"))\n            }\n            onCharacteristicChanged {_, _, data ->\n                //数据处理在IO线程，显示UI要切换到主线程\n                launchInMainThread {\n                    addLogMsg(\n                        LogEntity(\n                            Level.INFO, \"Indicate接收到${node.characteristicUUID}的数据：\" +\n                                    BleUtil.bytesToHex(data)\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n    /**\n     * stop indicate\n     */\n    fun stopIndicate(\n        bleDevice: BleDevice,\n        node: CharacteristicNode\n    ) {\n        val success = BleManager.get().stopIndicate(bleDevice, node.serviceUUID, node.characteristicUUID, BleDescriptorGetType.AllDescriptor)\n        if (success == true) {\n            addLogMsg(LogEntity(Level.FINE, \"indicate取消成功：${node.characteristicUUID}\"))\n        } else {\n            addLogMsg(LogEntity(Level.OFF, \"indicate取消失败：${node.characteristicUUID}\"))\n        }\n    }\n\n    /**\n     * 设置设备的传输优先级\n     */\n    fun setConnectionPriority(bleDevice: BleDevice, connectionPriority: Int) {\n        val success = BleManager.get().setConnectionPriority(bleDevice, connectionPriority)\n        if (success) {\n            addLogMsg(LogEntity(Level.FINE, \"设置设备的传输优先级成功：$connectionPriority\"))\n        } else {\n            addLogMsg(LogEntity(Level.OFF, \"设置设备的传输优先级失败：$connectionPriority\"))\n        }\n    }\n\n    /**\n     * 读取信号值\n     */\n    fun readRssi(bleDevice: BleDevice) {\n        BleManager.get().readRssi(bleDevice) {\n            onRssiFail {_, t ->\n                addLogMsg(LogEntity(Level.OFF, \"读取信号值失败：${t.message}\"))\n            }\n            onRssiSuccess {_, rssi ->\n                addLogMsg(LogEntity(Level.FINE, \"${bleDevice.deviceAddress} -> 读取信号值成功：${rssi}\"))\n            }\n        }\n    }\n\n    /**\n     * 设置mtu\n     */\n    fun setMtu(bleDevice: BleDevice) {\n        BleManager.get().setMtu(bleDevice) {\n            onSetMtuFail {_, t ->\n                addLogMsg(LogEntity(Level.OFF, \"设置mtu值失败：${t.message}\"))\n            }\n            onMtuChanged {_, mtu ->\n                addLogMsg(LogEntity(Level.FINE, \"${bleDevice.deviceAddress} -> 设置mtu值成功：${mtu}\"))\n            }\n        }\n    }\n\n    /**\n     * 读特征值数据\n     */\n    fun readData(bleDevice: BleDevice,\n                 node: CharacteristicNode\n    ) {\n        BleManager.get().readData(bleDevice, node.serviceUUID, node.characteristicUUID) {\n            onReadFail {_, t ->\n                addLogMsg(LogEntity(Level.OFF, \"读特征值数据失败：${t.message}\"))\n            }\n            onReadSuccess {_, data ->\n                addLogMsg(LogEntity(Level.FINE, \"${node.characteristicUUID} -> 读特征值数据成功：${BleUtil.bytesToHex(data)}\"))\n            }\n        }\n    }\n\n    /**\n     * 写数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     */\n    fun writeData(bleDevice: BleDevice,\n                  node: CharacteristicNode,\n                  text: String) {\n\n        val data = text.toByteArray()\n        BleLogger.i(\"data is: ${BleUtil.bytesToHex(data)}\")\n        val mtu = BleManager.get().getOptions()?.mtu?: DEFAULT_MTU\n        //mtu长度包含了ATT的opcode一个字节以及ATT的handle2个字节\n        val maxLength = mtu - 3\n        val listData: SparseArray<ByteArray> = BleUtil.subpackage(data, maxLength)\n        BleManager.get()\n            .writeData(bleDevice, node.serviceUUID, node.characteristicUUID, listData) {\n                onWriteFail { _, currentPackage, _, t ->\n                    addLogMsg(\n                        LogEntity(\n                            Level.OFF,\n                            \"第${currentPackage}包数据写失败：${t.message}\"\n                        )\n                    )\n                }\n                onWriteSuccess { _, currentPackage, _, justWrite ->\n                    addLogMsg(\n                        LogEntity(\n                            Level.FINE,\n                            \"${node.characteristicUUID} -> 第${currentPackage}包数据写成功：\" +\n                                    BleUtil.bytesToHex(justWrite)\n                        )\n                    )\n                }\n                onWriteComplete { _, allSuccess ->\n                    //代表所有数据写成功，可以在这个方法中处理成功的逻辑\n                    addLogMsg(\n                        LogEntity(\n                            Level.FINE,\n                            \"${node.characteristicUUID} -> 写数据完成，是否成功：$allSuccess\"\n                        )\n                    )\n                }\n            }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/bhm/demo/vm/MainViewModel.kt",
    "content": "package com.bhm.demo.vm\n\nimport android.app.Application\nimport android.content.Intent\nimport android.provider.Settings\nimport android.widget.Toast\nimport androidx.lifecycle.viewModelScope\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.attribute.BleOptions\nimport com.bhm.ble.callback.BleConnectCallback\nimport com.bhm.ble.callback.BleScanCallback\nimport com.bhm.ble.data.BleConnectFailType\nimport com.bhm.ble.data.BleScanFailType\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport com.bhm.demo.BaseActivity\nimport com.bhm.demo.constants.LOCATION_PERMISSION\nimport com.bhm.demo.entity.RefreshBleDevice\nimport com.bhm.support.sdk.common.BaseViewModel\nimport com.bhm.support.sdk.entity.MessageEvent\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.launch\nimport org.greenrobot.eventbus.EventBus\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * @author Buhuiming\n * @date 2023年05月18日 10时49分\n */\nclass MainViewModel(private val application: Application) : BaseViewModel(application) {\n\n    private val listDRMutableStateFlow = MutableStateFlow(\n        BleDevice(null, null, null, null, null, null, null)\n    )\n\n    val listDRStateFlow: StateFlow<BleDevice> = listDRMutableStateFlow\n\n    val listDRData = mutableListOf<BleDevice>()\n\n    private val scanStopMutableStateFlow = MutableStateFlow(true)\n\n    val scanStopStateFlow: StateFlow<Boolean> = scanStopMutableStateFlow\n\n    private val refreshMutableStateFlow = MutableStateFlow(\n        RefreshBleDevice(null, null)\n    )\n\n    val refreshStateFlow: StateFlow<RefreshBleDevice?> = refreshMutableStateFlow\n\n    /**\n     * 初始化蓝牙组件\n     */\n    fun initBle() {\n        BleManager.get().init(application,\n            BleOptions.Builder()\n                .setScanMillisTimeOut(5000)\n                .setConnectMillisTimeOut(5000)\n                //一般不推荐autoSetMtu，因为如果设置的等待时间会影响其他操作\n//                .setMtu(100, true)\n                .setMaxConnectNum(2)\n                .setConnectRetryCountAndInterval(2, 1000)\n                .setStopScanWhenStartConnect(false)\n                .setNeedCheckGps(true)\n                .build()\n        )\n        BleManager.get().registerBluetoothStateReceiver {\n            onStateOff {\n                refreshMutableStateFlow.value = RefreshBleDevice(null, System.currentTimeMillis())\n            }\n        }\n    }\n\n    /**\n     * 检查权限、检查GPS开关、检查蓝牙开关\n     */\n    private suspend fun hasScanPermission(activity: BaseActivity<*, *>): Boolean {\n        val isBleSupport = BleManager.get().isBleSupport()\n        BleLogger.e(\"设备是否支持蓝牙: $isBleSupport\")\n        if (!isBleSupport) {\n            return false\n        }\n        var hasScanPermission = suspendCoroutine { continuation ->\n            activity.requestPermission(\n                LOCATION_PERMISSION,\n                {\n                    BleLogger.d(\"获取到了权限\")\n                    try {\n                        continuation.resume(true)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                }, {\n                    BleLogger.w(\"缺少定位权限\")\n                    try {\n                        continuation.resume(false)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                }\n            )\n        }\n        //有些设备GPS是关闭状态的话，申请定位权限之后，GPS是依然关闭状态，这里要根据GPS是否打开来跳转页面\n        if (hasScanPermission && !BleUtil.isGpsOpen(application)) {\n            //跳转到系统GPS设置页面，GPS设置是全局的独立的，是否打开跟权限申请无关\n            hasScanPermission = suspendCoroutine {\n                activity.startActivity(\n                    Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)\n                ) { _, _ ->\n                    val enable = BleUtil.isGpsOpen(application)\n                    BleLogger.i(\"是否打开了GPS: $enable\")\n                    try {\n                        it.resume(enable)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                }\n            }\n        }\n        if (hasScanPermission && !BleManager.get().isBleEnable()) {\n            //跳转到系统GPS设置页面，GPS设置是全局的独立的，是否打开跟权限申请无关\n            hasScanPermission = suspendCoroutine {\n                activity.startActivity(\n                    Intent(Settings.ACTION_BLUETOOTH_SETTINGS)\n                ) { _, _ ->\n                    viewModelScope.launch {\n                        //打开蓝牙后需要一些时间才能获取到时开启状态，这里延时一下处理\n                        delay(1000)\n                        val enable = BleManager.get().isBleEnable()\n                        BleLogger.i(\"是否打开了蓝牙: $enable\")\n                        try {\n                            it.resume(enable)\n                        } catch (e: Exception) {\n                            BleLogger.e(e.message)\n                        }\n                    }\n                }\n            }\n        }\n        return hasScanPermission\n    }\n\n    /**\n     * 开始扫描\n     */\n    fun startScan(activity: BaseActivity<*, *>) {\n        viewModelScope.launch {\n            val hasScanPermission = hasScanPermission(activity)\n            if (hasScanPermission) {\n                BleManager.get().startScan(getScanCallback(true))\n            } else {\n                BleLogger.e(\"请检查权限、检查GPS开关、检查蓝牙开关\")\n            }\n        }\n    }\n\n    private fun getScanCallback(showData: Boolean): BleScanCallback.() -> Unit {\n        return {\n            onScanStart {\n                BleLogger.d(\"onScanStart\")\n                scanStopMutableStateFlow.value = false\n            }\n            onLeScan { bleDevice, _ ->\n                //可以根据currentScanCount是否已有清空列表数据\n                bleDevice.deviceName?.let { _ ->\n\n                }\n            }\n            onLeScanDuplicateRemoval { bleDevice, _ ->\n                bleDevice.deviceName?.let { _ ->\n                    if (showData) {\n                        listDRData.add(bleDevice)\n                        listDRMutableStateFlow.value = bleDevice\n                    }\n                }\n            }\n            onScanComplete { bleDeviceList, bleDeviceDuplicateRemovalList ->\n                //扫描到的数据是所有扫描次数的总和\n                bleDeviceList.forEach {\n                    it.deviceName?.let { deviceName ->\n                        BleLogger.i(\"bleDeviceList-> $deviceName, ${it.deviceAddress}\")\n                    }\n                }\n                bleDeviceDuplicateRemovalList.forEach {\n                    it.deviceName?.let { deviceName ->\n                        BleLogger.e(\"bleDeviceDuplicateRemovalList-> $deviceName, ${it.deviceAddress}\")\n                    }\n                }\n                scanStopMutableStateFlow.value = true\n                if (listDRData.isEmpty() && showData) {\n                    Toast.makeText(application, \"没有扫描到数据\", Toast.LENGTH_SHORT).show()\n                }\n            }\n            onScanFail {\n                val msg: String = when (it) {\n                    is BleScanFailType.UnSupportBle -> \"设备不支持蓝牙\"\n                    is BleScanFailType.NoBlePermission -> \"权限不足，请检查\"\n                    is BleScanFailType.GPSDisable -> \"设备未打开GPS定位\"\n                    is BleScanFailType.BleDisable -> \"蓝牙未打开\"\n                    is BleScanFailType.AlReadyScanning -> \"正在扫描\"\n                    is BleScanFailType.ScanError -> {\n                        \"${it.throwable?.message}\"\n                    }\n                }\n                BleLogger.e(msg)\n                Toast.makeText(application, msg, Toast.LENGTH_SHORT).show()\n                scanStopMutableStateFlow.value = true\n            }\n        }\n    }\n\n    /**\n     * 停止扫描\n     */\n    fun stopScan() {\n        BleManager.get().stopScan()\n    }\n\n    /**\n     * 是否已连接\n     */\n    fun isConnected(bleDevice: BleDevice?) = BleManager.get().isConnected(bleDevice)\n\n    /**\n     * 开始连接\n     */\n    fun connect(address: String) {\n        connect(BleManager.get().buildBleDeviceByDeviceAddress(address))\n    }\n\n    /**\n     * 扫描并连接，如果扫描到多个设备，则会连接第一个\n     */\n    fun startScanAndConnect(activity: BaseActivity<*, *>) {\n        viewModelScope.launch {\n            val hasScanPermission = hasScanPermission(activity)\n            if (hasScanPermission) {\n                BleManager.get().startScanAndConnect(\n                    false,\n                    getScanCallback(false),\n                    connectCallback\n                )\n            }\n        }\n    }\n\n    /**\n     * 开始连接\n     */\n    fun connect(bleDevice: BleDevice?) {\n        bleDevice?.let { device ->\n            BleManager.get().connect(device, false, connectCallback)\n        }\n    }\n\n    private val connectCallback: BleConnectCallback.() -> Unit = {\n        onConnectStart {\n            BleLogger.e(\"-----onConnectStart\")\n        }\n        onConnectFail { bleDevice, connectFailType ->\n            val msg: String = when (connectFailType) {\n                is BleConnectFailType.UnSupportBle -> \"设备不支持蓝牙\"\n                is BleConnectFailType.NoBlePermission -> \"权限不足，请检查\"\n                is BleConnectFailType.NullableBluetoothDevice -> \"设备为空\"\n                is BleConnectFailType.BleDisable -> \"蓝牙未打开\"\n                is BleConnectFailType.ConnectException -> \"连接异常(${connectFailType.throwable.message})\"\n                is BleConnectFailType.ConnectTimeOut -> \"连接超时\"\n                is BleConnectFailType.AlreadyConnecting -> \"连接中\"\n                is BleConnectFailType.ScanNullableBluetoothDevice -> \"连接失败，扫描数据为空\"\n            }\n            BleLogger.e(msg)\n            Toast.makeText(application, msg, Toast.LENGTH_SHORT).show()\n            refreshMutableStateFlow.value = RefreshBleDevice(bleDevice, System.currentTimeMillis())\n        }\n        onDisConnecting { isActiveDisConnected, bleDevice, _, _ ->\n            BleLogger.e(\"-----${bleDevice.deviceAddress} -> onDisConnecting: $isActiveDisConnected\")\n        }\n        onDisConnected { isActiveDisConnected, bleDevice, _, _ ->\n            Toast.makeText(application, \"断开连接(${bleDevice.deviceAddress}，isActiveDisConnected: \" +\n                    \"$isActiveDisConnected)\", Toast.LENGTH_SHORT).show()\n            BleLogger.e(\"-----${bleDevice.deviceAddress} -> onDisConnected: $isActiveDisConnected\")\n            refreshMutableStateFlow.value = RefreshBleDevice(bleDevice, System.currentTimeMillis())\n            //发送断开的通知\n            val message = MessageEvent()\n            message.data = bleDevice\n            EventBus.getDefault().post(message)\n        }\n        onConnectSuccess { bleDevice, _ ->\n            Toast.makeText(application, \"连接成功(${bleDevice.deviceAddress})\", Toast.LENGTH_SHORT).show()\n            refreshMutableStateFlow.value = RefreshBleDevice(bleDevice, System.currentTimeMillis())\n        }\n    }\n\n    /**\n     * 断开连接\n     */\n    fun disConnect(bleDevice: BleDevice?) {\n        bleDevice?.let { device ->\n            BleManager.get().disConnect(device)\n        }\n    }\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    fun close() {\n        BleManager.get().closeAll()\n    }\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:height=\"108dp\"\n        android:width=\"108dp\"\n        android:viewportHeight=\"108\"\n        android:viewportWidth=\"108\">\n    <path\n            android:fillColor=\"#3DDC84\"\n            android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M9,0L9,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,0L19,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M29,0L29,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M39,0L39,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M49,0L49,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M59,0L59,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M69,0L69,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M79,0L79,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M89,0L89,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M99,0L99,108\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,9L108,9\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,19L108,19\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,29L108,29\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,39L108,39\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,49L108,49\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,59L108,59\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,69L108,69\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,79L108,79\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,89L108,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M0,99L108,99\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,29L89,29\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,39L89,39\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,49L89,49\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,59L89,59\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,69L89,69\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M19,79L89,79\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M29,19L29,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M39,19L39,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M49,19L49,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M59,19L59,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M69,19L69,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n    <path\n            android:fillColor=\"#00000000\"\n            android:pathData=\"M79,19L79,89\"\n            android:strokeColor=\"#33FFFFFF\"\n            android:strokeWidth=\"0.8\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:aapt=\"http://schemas.android.com/aapt\"\n        android:width=\"108dp\"\n        android:height=\"108dp\"\n        android:viewportWidth=\"108\"\n        android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                    android:startY=\"49.59793\"\n                    android:startX=\"42.9492\"\n                    android:endY=\"92.4963\"\n                    android:endX=\"85.84757\"\n                    android:type=\"linear\">\n                <item\n                        android:color=\"#44000000\"\n                        android:offset=\"0.0\" />\n                <item\n                        android:color=\"#00000000\"\n                        android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n            android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n            android:fillColor=\"#FFFFFF\"\n            android:fillType=\"nonZero\"\n            android:strokeWidth=\"1\"\n            android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:fitsSystemWindows=\"true\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.MainActivity\">\n\n    <TextView\n        android:id=\"@+id/tvName\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/black\"\n        android:paddingHorizontal=\"16dp\"\n        android:paddingVertical=\"8dp\"\n        android:textSize=\"16sp\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginVertical=\"12dp\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"折叠或收起\"\n        android:onClick=\"showContent\"\n        android:paddingBottom=\"12dp\"\n        android:gravity=\"center\"/>\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n        android:id=\"@+id/llContent\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"2\"\n        android:orientation=\"vertical\">\n\n        <androidx.appcompat.widget.LinearLayoutCompat\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"end\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_marginBottom=\"5dp\"\n            android:orientation=\"horizontal\">\n\n            <Button\n                android:id=\"@+id/btnConnectionPriority\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:layout_marginEnd=\"16dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:layout_gravity=\"end\"\n                android:textAllCaps=\"false\"\n                android:textSize=\"13sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"Priority\"/>\n\n            <Button\n                android:id=\"@+id/btnReadRssi\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:layout_marginEnd=\"20dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:layout_gravity=\"end\"\n                android:textSize=\"13sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"读取Rssi\"/>\n\n            <Button\n                android:id=\"@+id/btnSetMtu\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:layout_marginEnd=\"20dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:layout_gravity=\"end\"\n                android:textSize=\"13sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"设置Mtu\"/>\n\n            <Button\n                android:id=\"@+id/btnClear\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:layout_gravity=\"end\"\n                android:textSize=\"13sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"清除日志\"/>\n        </androidx.appcompat.widget.LinearLayoutCompat>\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/logRecyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_marginHorizontal=\"16dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"/>\n\n        <androidx.appcompat.widget.LinearLayoutCompat\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"60dp\"\n            android:layout_marginTop=\"6dp\"\n            android:layout_marginHorizontal=\"10dp\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <EditText\n                android:id=\"@+id/etContent\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"数据格式：aa00bb11cc22 (支持空格)\"\n                android:textSize=\"14sp\"\n                android:enabled=\"false\"\n                android:textColor=\"@color/black\"\n                android:textColorHint=\"#999999\"\n                android:maxLines=\"1\"/>\n\n            <Button\n                android:id=\"@+id/btnSend\"\n                android:layout_width=\"60dp\"\n                android:layout_height=\"40dp\"\n                android:layout_marginStart=\"10dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:textSize=\"13sp\"\n                android:padding=\"0dp\"\n                android:enabled=\"false\"\n                android:background=\"@color/black\"\n                android:text=\"发送\"/>\n        </androidx.appcompat.widget.LinearLayoutCompat>\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.MainActivity\">\n\n    <View\n        android:id=\"@+id/vTop\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/black\"/>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"56dp\"\n        android:background=\"@color/black\"\n        android:text=\"BleCore\"\n        android:textSize=\"17sp\"\n        android:textColor=\"@color/white\"\n        android:gravity=\"center_vertical\"\n        android:paddingHorizontal=\"20dp\"/>\n\n    <ProgressBar\n        android:id=\"@+id/pbLoading\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"14dp\"\n        android:visibility=\"invisible\"\n        android:indeterminate=\"true\"\n        android:indeterminateTint=\"@color/black\"/>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_margin=\"20dp\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <EditText\n        android:id=\"@+id/etAddress\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"40dp\"\n        android:layout_marginHorizontal=\"28dp\"\n        android:text=\"49:A6:84:E0:4D:33\"\n        android:hint=\"输入连接指定设备的地址\"/>\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"20dp\"\n        android:gravity=\"center\">\n\n        <Button\n            android:id=\"@+id/btnConnect\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"40dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:gravity=\"center\"\n            android:textSize=\"12sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"连接指定设备\"/>\n\n        <Button\n            android:id=\"@+id/btnSetting\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"40dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:gravity=\"center\"\n            android:textSize=\"14sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"设置\"/>\n\n        <Button\n            android:id=\"@+id/btnStart\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"40dp\"\n            android:layout_marginStart=\"0dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:gravity=\"center\"\n            android:textSize=\"14sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"开启扫描\"/>\n\n        <Button\n            android:id=\"@+id/btnStop\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"40dp\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:enabled=\"false\"\n            android:gravity=\"center\"\n            android:textSize=\"14sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"停着扫描\"/>\n\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/layout/activity_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\"\n        android:orientation=\"vertical\"\n        tools:context=\".ui.MainActivity\">\n\n    <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:paddingHorizontal=\"15dp\">\n\n        <androidx.appcompat.widget.LinearLayoutCompat\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_marginVertical=\"20dp\"\n                android:orientation=\"vertical\">\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"扫描服务UUID：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanServiceUuid\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"设置扫描过滤服务UUID，多个以英文逗号分开，默认空\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"扫描设备名称：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanDeviceName\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"设置扫描过滤设备名称，多个以英文逗号分开，默认空\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"扫描设备地址：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanDeviceAddress\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"设置扫描过设备地址，多个以英文逗号分开，默认空\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"扫描超时时间：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanOutTime\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认10000毫秒，单位毫秒\"\n                        android:text=\"10000\"\n                        android:textSize=\"16sp\"\n                        android:inputType=\"number\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"扫描重试次数和间隔：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanRetryCount\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认0次\"\n                        android:text=\"0\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n                <EditText\n                        android:id=\"@+id/etScanRetryInterval\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认1000毫秒，单位毫秒\"\n                        android:text=\"1000\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"连接超时时间：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etConnectOutTime\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认10000毫秒，单位毫秒\"\n                        android:text=\"10000\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"连接重试次数和间隔：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etConnectRetryCount\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认0次\"\n                        android:text=\"0\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n                <EditText\n                        android:id=\"@+id/etConnectRetryInterval\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认1000毫秒，单位毫秒\"\n                        android:text=\"1000\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"交互超时时间：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etOperateMillisTimeOut\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认5000毫秒，单位毫秒\"\n                        android:inputType=\"number\"\n                        android:text=\"5000\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"写数据间隔时间：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etOperateInterval\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认100毫秒，单位毫秒\"\n                        android:text=\"100\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"最大连接设备数：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etMaxConnectNum\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认7个\"\n                        android:inputType=\"number\"\n                        android:text=\"7\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"MTU值：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <EditText\n                        android:id=\"@+id/etMTU\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"默认20\"\n                        android:text=\"20\"\n                        android:inputType=\"number\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:textColorHint=\"#999999\"\n                        android:maxLines=\"4\"/>\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"是否连接后自动设置mtu：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <CheckBox\n                        android:id=\"@+id/cbMtu\"\n                        android:layout_width=\"40dp\"\n                        android:layout_height=\"40dp\"\n                        android:checked=\"true\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"是否模糊匹配设备名称：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <CheckBox\n                        android:id=\"@+id/cbContainScanDeviceName\"\n                        android:layout_width=\"40dp\"\n                        android:layout_height=\"40dp\"\n                        android:checked=\"false\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:gravity=\"center_vertical\"\n                android:layout_marginVertical=\"10dp\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"扫描设备是否需要检测GPS开关：\"\n                    android:textSize=\"16sp\"\n                    android:textColor=\"@color/black\"\n                    android:layout_marginEnd=\"12dp\"/>\n\n                <CheckBox\n                    android:id=\"@+id/cbNeedCheckGPS\"\n                    android:layout_width=\"40dp\"\n                    android:layout_height=\"40dp\"\n                    android:checked=\"true\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginVertical=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"是否日志输出：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <CheckBox\n                        android:id=\"@+id/cbLogger\"\n                        android:layout_width=\"40dp\"\n                        android:layout_height=\"40dp\"\n                        android:checked=\"true\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center_vertical\"\n                    android:layout_marginTop=\"10dp\"\n                    android:orientation=\"horizontal\">\n\n                <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"非主动断开后是否自动连接：\"\n                        android:textSize=\"16sp\"\n                        android:textColor=\"@color/black\"\n                        android:layout_marginEnd=\"12dp\"/>\n\n                <CheckBox\n                        android:id=\"@+id/cbAutoConnect\"\n                        android:layout_width=\"40dp\"\n                        android:layout_height=\"40dp\"\n                        android:checked=\"false\"/>\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <Spinner\n                    android:id=\"@+id/spTaskQueueType\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"50dp\"\n                    android:layout_marginTop=\"10dp\"/>\n\n        </androidx.appcompat.widget.LinearLayoutCompat>\n\n    </androidx.core.widget.NestedScrollView>\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:layout_margin=\"20dp\"\n            android:gravity=\"center\">\n\n        <Button\n                android:id=\"@+id/btnReSet\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:layout_marginStart=\"0dp\"\n                android:layout_marginEnd=\"10dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:textSize=\"14sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"重置\"/>\n\n        <Button\n                android:id=\"@+id/btnSave\"\n                android:layout_width=\"0dp\"\n                android:layout_weight=\"1\"\n                android:layout_height=\"40dp\"\n                android:layout_marginStart=\"10dp\"\n                android:layout_marginEnd=\"10dp\"\n                android:textColor=\"@color/white\"\n                android:gravity=\"center\"\n                android:textSize=\"14sp\"\n                android:padding=\"0dp\"\n                android:background=\"@color/black\"\n                android:text=\"保存\"/>\n\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/layout/layout_recycler_characteristic.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:paddingVertical=\"10dp\"\n        android:gravity=\"center_vertical\">\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"10dp\"\n            android:gravity=\"center_vertical\">\n\n        <TextView\n                android:id=\"@+id/tvCharacteristicName\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/color_main\"\n                android:textSize=\"14sp\"\n                android:textIsSelectable=\"true\"\n                android:gravity=\"center_vertical\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"3\"\n                tools:text=\"特征值名称\"/>\n\n        <TextView\n                android:id=\"@+id/tvCharacteristicUUID\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#666666\"\n                android:layout_marginTop=\"4dp\"\n                android:textSize=\"12sp\"\n                android:textIsSelectable=\"true\"\n                android:gravity=\"center_vertical\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"3\"\n                tools:text=\"特征值名称\"/>\n\n        <TextView\n                android:id=\"@+id/tvCharacteristicProperties\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#666666\"\n                android:layout_marginTop=\"4dp\"\n                android:textSize=\"12sp\"\n                android:textIsSelectable=\"true\"\n                android:gravity=\"center_vertical\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"3\"\n                tools:text=\"特征值属性\"/>\n\n    </androidx.appcompat.widget.LinearLayoutCompat>\n\n    <Button\n            android:id=\"@+id/btnReadData\"\n            android:layout_width=\"70dp\"\n            android:layout_height=\"40dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:gravity=\"center\"\n            android:textSize=\"13sp\"\n            android:padding=\"0dp\"\n            android:visibility=\"gone\"\n            android:background=\"@color/black\"\n            android:text=\"读数据\"/>\n\n    <CheckBox\n            android:id=\"@+id/cbWrite\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"40dp\"\n            android:text=\"写\"\n            android:textColor=\"@color/black\"\n            android:textSize=\"14sp\"\n            android:visibility=\"gone\"\n            android:layout_marginEnd=\"10dp\"\n            android:checked=\"false\"/>\n\n    <CheckBox\n            android:id=\"@+id/cbNotify\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"40dp\"\n            android:text=\"Notify\"\n            android:textColor=\"@color/black\"\n            android:textSize=\"14sp\"\n            android:visibility=\"gone\"\n            android:layout_marginEnd=\"10dp\"\n            android:checked=\"false\"/>\n\n    <CheckBox\n            android:id=\"@+id/cbIndicate\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"40dp\"\n            android:text=\"Indicate\"\n            android:textColor=\"@color/black\"\n            android:textSize=\"14sp\"\n            android:visibility=\"gone\"\n            android:layout_marginEnd=\"10dp\"\n            android:checked=\"false\"/>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_recycler_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_vertical\"\n        tools:ignore=\"UseCompoundDrawables\">\n\n    <TextView\n            android:id=\"@+id/tvName\"\n            android:layout_width=\"0dp\"\n            android:layout_weight=\"1\"\n            android:layout_height=\"60dp\"\n            android:textColor=\"@color/black\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"4dp\"\n            android:textSize=\"14sp\"\n            android:textIsSelectable=\"true\"\n            android:gravity=\"center_vertical\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"3\"\n            android:text=\"设备名称\"/>\n\n    <ImageView\n            android:id=\"@+id/ivRssi\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"12dp\"\n            android:gravity=\"center\"/>\n\n    <TextView\n            android:id=\"@+id/btnConnect\"\n            android:layout_width=\"80dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginStart=\"0dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:gravity=\"center\"\n            android:textSize=\"13sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"连接\"/>\n\n    <Button\n            android:id=\"@+id/btnOperate\"\n            android:layout_width=\"80dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginStart=\"0dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:textColor=\"@color/white\"\n            android:enabled=\"false\"\n            android:gravity=\"center\"\n            android:textSize=\"13sp\"\n            android:padding=\"0dp\"\n            android:background=\"@color/black\"\n            android:text=\"操作\"/>\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/layout/layout_recycler_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        tools:ignore=\"UseCompoundDrawables\">\n\n    <TextView\n            android:id=\"@+id/tvTime\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textColor=\"#666666\"\n            android:textSize=\"12sp\"\n            android:textIsSelectable=\"true\"/>\n\n    <TextView\n            android:id=\"@+id/tvName\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:minHeight=\"12dp\"\n            android:layout_marginHorizontal=\"5dp\"\n            android:textColor=\"#666666\"\n            android:textSize=\"12sp\"\n            android:textIsSelectable=\"true\"/>\n\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/layout/layout_recycler_service.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.LinearLayoutCompat xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:paddingVertical=\"10dp\"\n        android:gravity=\"center_vertical\">\n\n    <ImageView\n            android:id=\"@+id/ivExpand\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:src=\"@drawable/icon_right\"\n            android:visibility=\"invisible\"/>\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:gravity=\"center_vertical\">\n\n        <TextView\n                android:id=\"@+id/tvServiceName\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/red\"\n                android:textSize=\"16sp\"\n                android:textIsSelectable=\"true\"\n                android:gravity=\"center_vertical\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"3\"\n                tools:text=\"服务名称\"/>\n\n        <TextView\n                android:id=\"@+id/tvServiceUUID\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"#666666\"\n                android:layout_marginTop=\"4dp\"\n                android:textSize=\"14sp\"\n                android:textIsSelectable=\"true\"\n                android:gravity=\"center_vertical\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"3\"\n                tools:text=\"服务名称\"/>\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</androidx.appcompat.widget.LinearLayoutCompat>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"red\">#FFFF0000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">BleCore</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.BleCore\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/black</item>\n        <item name=\"colorPrimaryVariant\">@color/black</item>\n        <item name=\"colorOnPrimary\">@color/white</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_700</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\">@color/black</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.BleCore\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/black</item>\n        <item name=\"colorPrimaryVariant\">@color/black</item>\n        <item name=\"colorOnPrimary\">@color/black</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_200</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\">@color/black</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!--\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "app/src/test/java/com/bhm/demo/ExampleUnitTest.kt",
    "content": "package com.bhm.demo\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "ble/.gitignore",
    "content": "/build"
  },
  {
    "path": "ble/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'org.jetbrains.kotlin.android'\n    id 'maven-publish'\n}\n\n//ext.GROUP = \"com.lute.ble\"\n//ext.POM_ARTIFACT_ID = \"BleCore\"\n//ext.VERSION_NAME = \"2.6.1\"\n////引用gradle_upload.gradle\n//apply from: \"${project.rootDir}/maven_upload.gradle\"\n\nafterEvaluate {\n    publishing {\n        publications {\n            // 这个mavenJava可以随便填，只是一个任务名字而已\n            // MavenPublication必须有，这个是调用的任务类\n            mavenJava(MavenPublication) {\n                // 这里头是artifacts的配置信息，不填会采用默认的\n                groupId = 'com.github.buhuiming'\n                artifactId = 'BleCore'\n                version = '2.6.1'\n\n                from components.release\n                if (!project.plugins.hasPlugin('kotlin-android')) {\n                    artifact tasks.named(\"androidSourcesJar\").get()\n                }\n            }\n        }\n    }\n    // 显式声明任务依赖关系\n    tasks.matching { it.name.startsWith(\"generateMetadataFileFor\") }.configureEach {\n        dependsOn tasks.named(\"androidSourcesJar\")\n    }\n}\n// 用于打包源代码的任务\ntasks.register('androidSourcesJar', Jar) {\n    archiveClassifier.set('sources')\n    from android.sourceSets.main.java.srcDirs\n}\n\nandroid {\n    namespace 'com.bhm.ble'\n    compileSdk 35\n\n    defaultConfig {\n        minSdk 24\n        targetSdk 35\n\n    }\n    publishing {\n        singleVariant(\"release\")\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    kotlinOptions {\n        jvmTarget = '17'\n    }\n}\n\ndependencies {\n//    implementation \"org.jetbrains.kotlin:kotlin-stdlib:1.8.10\"\n    implementation 'androidx.lifecycle:lifecycle-common:2.9.2'\n}"
  },
  {
    "path": "ble/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "ble/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "ble/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n\n    <!-- android 30之前 静态声明的蓝牙权限 -->\n    <uses-permission\n        android:name=\"android.permission.BLUETOOTH\"\n        tools:remove=\"android:maxSdkVersion\" />\n\n    <uses-permission\n        android:name=\"android.permission.BLUETOOTH_ADMIN\"\n        android:maxSdkVersion=\"30\" />\n    <!-- 使用蓝牙还需要 静态声明+动态申请位置（前台位置）权限 -->\n    <uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>\n    <uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>\n    <!-- android 30之后 静态声明+动态申请的蓝牙权限 -->\n    <uses-permission\n        android:name=\"android.permission.BLUETOOTH_SCAN\"\n        android:usesPermissionFlags=\"neverForLocation\"\n        tools:targetApi=\"s\" />\n    <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\n\n    <!--使用动态注册，避免国内应用市场对于此注册导致的应用自启动问题，而拒绝上架-->\n    <!--<application>\n        &lt;!&ndash;注册系统蓝牙广播&ndash;&gt;\n        <receiver android:name=\".receiver.BluetoothReceiver\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.bluetooth.adapter.action.STATE_CHANGED\"/>\n            </intent-filter>\n        </receiver>\n    </application>-->\n</manifest>"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/BleManager.kt",
    "content": "@file:Suppress(\"SENSELESS_COMPARISON\", \"unused\")\n\npackage com.bhm.ble\n\nimport android.annotation.SuppressLint\nimport android.app.Application\nimport android.bluetooth.BluetoothDevice\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothManager\nimport android.bluetooth.BluetoothProfile\nimport android.content.Context\nimport android.util.SparseArray\nimport com.bhm.ble.attribute.BleOptions\nimport com.bhm.ble.callback.*\nimport com.bhm.ble.data.BleDescriptorGetType\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.BleBaseRequest\nimport com.bhm.ble.request.base.BleRequestImp\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\n\n\n/**\n * Android蓝牙低功耗核心类\n * @author Buhuiming\n * @date 2023年05月18日 13时37分\n */\nclass BleManager private constructor() {\n\n    private var application: Application? = null\n\n    private var bleOptions: BleOptions? = null\n\n    private var bluetoothManager: BluetoothManager? = null\n\n    private var bleBaseRequest: BleBaseRequest? = null\n\n    companion object {\n\n        private var instance: BleManager = BleManager()\n\n        fun get(): BleManager {\n            if (instance == null) {\n                instance = BleManager()\n            }\n            return instance\n        }\n    }\n\n    /**\n     * 初始化，使用BleManager其他方法前，需先调用此方法\n     */\n    fun init(context: Application, option: BleOptions? = null) {\n        application = context\n        bleOptions = option\n        if (bleOptions == null) {\n            bleOptions = BleOptions.getDefaultBleOptions()\n        }\n        bleBaseRequest = BleRequestImp.get()\n        bluetoothManager = application?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?\n        BleLogger.isLogger = bleOptions?.enableLog?: false\n        BleLogger.i(\"ble Successful initialization\")\n    }\n\n    /**\n     * 设备是否支持蓝牙\n     *  @return true = 支持\n     */\n    fun isBleSupport(): Boolean {\n        return BleUtil.isBleSupport(application?.applicationContext)\n    }\n\n    /**\n     * 蓝牙是否打开\n     * @return true = 打开\n     */\n    fun isBleEnable(): Boolean {\n        val bluetoothAdapter = bluetoothManager?.adapter\n        return isBleSupport() && (bluetoothAdapter?.isEnabled?: false)\n    }\n\n    /**\n     * 开始扫描\n     * @param scanMillisTimeOut 扫描超时时间，单位毫秒，只对单次扫描有效\n     * @param scanRetryCount 设置扫描重试次数，只对单次扫描有效\n     * @param scanRetryInterval 设置扫描重试间隔，单位毫秒，只对单次扫描有效\n     */\n    @Synchronized\n    fun startScan(\n        scanMillisTimeOut: Long?,\n        scanRetryCount: Int?,\n        scanRetryInterval: Long?,\n        bleScanCallback: BleScanCallback.() -> Unit\n    ) {\n        checkInitialize()\n        bleBaseRequest?.startScan(\n            scanMillisTimeOut,\n            scanRetryCount,\n            scanRetryInterval,\n            bleScanCallback\n        )\n    }\n\n    /**\n     * 开始扫描\n     */\n    fun startScan(\n        bleScanCallback: BleScanCallback.() -> Unit\n    ) {\n        startScan(null, null, null, bleScanCallback)\n    }\n\n    /**\n     * 是否扫描中\n     * @return true = 扫描中\n     */\n    fun isScanning(): Boolean {\n        checkInitialize()\n        return bleBaseRequest?.isScanning()?: false\n    }\n\n    /**\n     * 停止扫描\n     */\n    @Synchronized\n    fun stopScan() {\n        checkInitialize()\n        bleBaseRequest?.stopScan()\n    }\n\n    /**\n     * 是否已连接，确保已获取到权限\n     *\n     * 操作断开连接后，getConnectionState马上回去到的状态还是连接状态，所以需要bleBaseRequest?.isConnected判断\n     *  @param simplySystemStatus 为true，只根据系统的状态规则；为false，会根据sdk的状态；\n     *  此字段的意义在于：有时，sdk资源被系统回收(状态未连接)，但是系统的状态是已连接。\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun isConnected(bleDeviceAddress: String, simplySystemStatus: Boolean = true): Boolean {\n        return isConnected(buildBleDeviceByDeviceAddress(bleDeviceAddress), simplySystemStatus)\n    }\n\n    /**\n     * 是否已连接，确保已获取到权限\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun isConnected(bleDevice: BleDevice?, simplySystemStatus: Boolean = true): Boolean {\n        checkInitialize()\n        if (!BleUtil.isPermission(application)) {\n            return false\n        }\n        bleDevice?.let {\n            val connectedDevices: List<BluetoothDevice>? = bluetoothManager?.getConnectedDevices(BluetoothProfile.GATT)\n            if (connectedDevices.isNullOrEmpty()) {\n                return false\n            }\n            for (connectedDevice in connectedDevices) {\n                if (it.deviceAddress == connectedDevice.address) {\n                    return if (simplySystemStatus) {\n                        true\n                    } else {\n                        bleBaseRequest?.isConnected(it) == true\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    /**\n     * 连接\n     * @param connectMillisTimeOut 连接超时时间，单位毫秒，只对单次连接有效\n     * @param connectRetryCount 设置连接重试次数，只对单次连接有效\n     * @param connectRetryInterval 设置连接重试间隔，只对单次连接有效\n     * @param isForceConnect 是否强制连接(针对已连接情况，是否重连)\n     */\n    @Synchronized\n    fun connect(bleDevice: BleDevice,\n                connectMillisTimeOut: Long?,\n                connectRetryCount: Int?,\n                connectRetryInterval: Long?,\n                isForceConnect: Boolean = false,\n                bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        checkInitialize()\n        if (getOptions()?.stopScanWhenStartConnect == true) {\n            stopScan()\n        }\n        bleBaseRequest?.connect(\n            bleDevice,\n            connectMillisTimeOut,\n            connectRetryCount,\n            connectRetryInterval,\n            isForceConnect,\n            bleConnectCallback\n        )\n    }\n\n    /**\n     * 通过地址连接\n     * @param connectMillisTimeOut 连接超时时间，单位毫秒，只对单次连接有效\n     * @param connectRetryCount 设置连接重试次数，只对单次连接有效\n     * @param connectRetryInterval 设置连接重试间隔，单位毫秒，只对单次连接有效\n     */\n    fun connect(address: String,\n                connectMillisTimeOut: Long?,\n                connectRetryCount: Int?,\n                connectRetryInterval: Long?,\n                isForceConnect: Boolean = false,\n                bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        connect(\n            buildBleDeviceByDeviceAddress(address),\n            connectMillisTimeOut,\n            connectRetryCount,\n            connectRetryInterval,\n            isForceConnect,\n            bleConnectCallback\n        )\n    }\n\n    /**\n     * 连接\n     */\n    fun connect(\n        bleDevice: BleDevice,\n        isForceConnect: Boolean = false,\n        bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        connect(\n            bleDevice,\n            null,\n            null,\n            null,\n            isForceConnect,\n            bleConnectCallback\n        )\n    }\n\n    /**\n     * 通过地址连接\n     */\n    fun connect(\n        address: String,\n        isForceConnect: Boolean = false,\n        bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        connect(\n            address,\n            null,\n            null,\n            null,\n            isForceConnect,\n            bleConnectCallback\n        )\n    }\n\n    @Synchronized\n    fun startScanAndConnect(scanMillisTimeOut: Long?,\n                            scanRetryCount: Int?,\n                            scanRetryInterval: Long?,\n                            connectMillisTimeOut: Long?,\n                            connectRetryCount: Int?,\n                            connectRetryInterval: Long?,\n                            isForceConnect: Boolean = false,\n                            bleScanCallback: BleScanCallback.() -> Unit,\n                            bleConnectCallback: BleConnectCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.startScanAndConnect(\n            scanMillisTimeOut,\n            scanRetryCount,\n            scanRetryInterval,\n            connectMillisTimeOut,\n            connectRetryCount,\n            connectRetryInterval,\n            isForceConnect,\n            bleScanCallback,\n            bleConnectCallback\n        )\n    }\n\n    fun startScanAndConnect(\n        isForceConnect: Boolean = false,\n        bleScanCallback: BleScanCallback.() -> Unit,\n        bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        startScanAndConnect(\n            null,\n            null,\n            null,\n            null,\n            null,\n            null,\n            isForceConnect,\n            bleScanCallback,\n            bleConnectCallback\n        )\n    }\n\n    /**\n     * 取消/停止连接\n     */\n    @Synchronized\n    fun stopConnect(bleDevice: BleDevice?) {\n        checkInitialize()\n        bleDevice?.let {\n            bleBaseRequest?.stopConnect(it)\n        }\n    }\n\n    /**\n     * 断开连接\n     */\n    @Synchronized\n    fun disConnect(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.disConnect(bleDevice)\n    }\n\n    /**\n     * 通过地址断开连接\n     */\n    fun disConnect(address: String) {\n        disConnect(buildBleDeviceByDeviceAddress(address))\n    }\n\n    /**\n     * 获取设备的BluetoothGatt对象\n     */\n    @Synchronized\n    fun getBluetoothGatt(bleDevice: BleDevice): BluetoothGatt? {\n        checkInitialize()\n        return bleBaseRequest?.getBluetoothGatt(bleDevice)\n    }\n\n    /**\n     * notify\n     */\n    @Deprecated(message = \"请使用BleDescriptorGetType参数方式\",\n        replaceWith = ReplaceWith(\n            \"notify(BleDevice, String, String, BleDescriptorGetType, BleNotifyCallback)\"\n        )\n    )\n    fun notify(bleDevice: BleDevice,\n               serviceUUID: String,\n               notifyUUID: String,\n               useCharacteristicDescriptor: Boolean = false,\n               bleNotifyCallback: BleNotifyCallback.() -> Unit) {\n        notify(\n            bleDevice = bleDevice,\n            serviceUUID = serviceUUID,\n            notifyUUID = notifyUUID,\n            bleDescriptorGetType = if (useCharacteristicDescriptor) {\n                BleDescriptorGetType.CharacteristicDescriptor\n            } else {\n                BleDescriptorGetType.Default\n            },\n            bleNotifyCallback = bleNotifyCallback,\n        )\n    }\n\n    /**\n     * notify\n     */\n    fun notify(bleDevice: BleDevice,\n               serviceUUID: String,\n               notifyUUID: String,\n               timeoutMillis: Long? = bleOptions?.operateMillisTimeOut,\n               bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n               bleNotifyCallback: BleNotifyCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.notify(\n            bleDevice,\n            serviceUUID,\n            notifyUUID,\n            timeoutMillis,\n            bleDescriptorGetType,\n            bleNotifyCallback\n        )\n    }\n\n    /**\n     * notify\n     */\n    fun notify(bleDevice: BleDevice,\n               serviceUUID: String,\n               notifyUUID: String,\n               bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n               bleNotifyCallback: BleNotifyCallback.() -> Unit) {\n        notify(\n            bleDevice,\n            serviceUUID,\n            notifyUUID,\n            null,\n            bleDescriptorGetType,\n            bleNotifyCallback\n        )\n    }\n\n    /**\n     * stop notify\n     */\n    @Deprecated(message = \"请使用BleDescriptorGetType参数方式\",\n        replaceWith = ReplaceWith(\n            \"stopNotify(BleDevice, String, String, BleDescriptorGetType)\"\n        )\n    )\n    fun stopNotify(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        notifyUUID: String,\n        useCharacteristicDescriptor: Boolean = false\n    ): Boolean? {\n        return stopNotify(\n            bleDevice = bleDevice,\n            serviceUUID = serviceUUID,\n            notifyUUID = notifyUUID,\n            bleDescriptorGetType = if (useCharacteristicDescriptor) {\n                BleDescriptorGetType.CharacteristicDescriptor\n            } else {\n                BleDescriptorGetType.Default\n            },\n        )\n    }\n\n    /**\n     * stop notify\n     */\n    fun stopNotify(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        notifyUUID: String,\n        bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n    ): Boolean? {\n        checkInitialize()\n        return bleBaseRequest?.stopNotify(\n            bleDevice,\n            serviceUUID,\n            notifyUUID,\n            bleDescriptorGetType\n        )\n    }\n\n    /**\n     * indicate\n     */\n    @Deprecated(message = \"请使用BleDescriptorGetType参数方式\",\n        replaceWith = ReplaceWith(\n            \"indicate(BleDevice, String, String, BleDescriptorGetType, BleIndicateCallback)\"\n        )\n    )\n    fun indicate(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 indicateUUID: String,\n                 useCharacteristicDescriptor: Boolean = false,\n                 bleIndicateCallback: BleIndicateCallback.() -> Unit) {\n        indicate(\n            bleDevice = bleDevice,\n            serviceUUID = serviceUUID,\n            indicateUUID = indicateUUID,\n            bleDescriptorGetType = if (useCharacteristicDescriptor) {\n                BleDescriptorGetType.CharacteristicDescriptor\n            } else {\n                BleDescriptorGetType.Default\n            },\n            bleIndicateCallback = bleIndicateCallback\n        )\n    }\n\n    /**\n     * indicate\n     */\n    fun indicate(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 indicateUUID: String,\n                 timeoutMillis: Long? = bleOptions?.operateMillisTimeOut,\n                 bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n                 bleIndicateCallback: BleIndicateCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.indicate(\n            bleDevice,\n            serviceUUID,\n            indicateUUID,\n            timeoutMillis,\n            bleDescriptorGetType,\n            bleIndicateCallback\n        )\n    }\n\n    /**\n     * indicate\n     */\n    fun indicate(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 indicateUUID: String,\n                 bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n                 bleIndicateCallback: BleIndicateCallback.() -> Unit) {\n        indicate(\n            bleDevice,\n            serviceUUID,\n            indicateUUID,\n            null,\n            bleDescriptorGetType,\n            bleIndicateCallback\n        )\n    }\n\n    /**\n     * stop indicate\n     */\n    @Deprecated(message = \"请使用BleDescriptorGetType参数方式\",\n        replaceWith = ReplaceWith(\n            \"stopIndicate(BleDevice, String, String, BleDescriptorGetType)\"\n        )\n    )\n    fun stopIndicate(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        indicateUUID: String,\n        useCharacteristicDescriptor: Boolean = false\n    ): Boolean? {\n        return stopIndicate(\n            bleDevice = bleDevice,\n            serviceUUID = serviceUUID,\n            indicateUUID = indicateUUID,\n            bleDescriptorGetType = if (useCharacteristicDescriptor) {\n                BleDescriptorGetType.CharacteristicDescriptor\n            } else {\n                BleDescriptorGetType.Default\n            },\n        )\n    }\n\n    /**\n     * stop indicate\n     */\n    fun stopIndicate(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        indicateUUID: String,\n        bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default\n    ): Boolean? {\n        checkInitialize()\n        return bleBaseRequest?.stopIndicate(\n            bleDevice,\n            serviceUUID,\n            indicateUUID,\n            bleDescriptorGetType\n        )\n    }\n\n    /**\n     * 读取信号值\n     */\n    fun readRssi(bleDevice: BleDevice, bleRssiCallback: BleRssiCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.readRssi(bleDevice, bleRssiCallback)\n    }\n\n    /**\n     * 设置mtu\n     */\n    fun setMtu(bleDevice: BleDevice, bleMtuChangedCallback: BleMtuChangedCallback.() -> Unit) {\n        setMtu(bleDevice, getOptions()?.mtu?: DEFAULT_MTU, bleMtuChangedCallback)\n    }\n\n    /**\n     * 设置mtu\n     */\n    fun setMtu(bleDevice: BleDevice, mtu: Int, bleMtuChangedCallback: BleMtuChangedCallback.() -> Unit) {\n        checkInitialize()\n        if (mtu > 512) {\n            BleLogger.w(\"requiredMtu should lower than 512 !\")\n        }\n\n        if (mtu < 23) {\n            BleLogger.w(\"requiredMtu should higher than 23 !\")\n        }\n        bleBaseRequest?.setMtu(bleDevice, mtu, bleMtuChangedCallback)\n    }\n\n    /**\n     * 设置设备的传输优先级\n     * connectionPriority 必须是以下的其中一个\n     * [BluetoothGatt.CONNECTION_PRIORITY_BALANCED] (默认)、\n     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH] (高优先级，低延迟，传输完请求设置\n     * CONNECTION_PRIORITY_BALANCED，以减少能源使用)、\n     * [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER] (低功耗)\n     *\n     */\n    fun setConnectionPriority(bleDevice: BleDevice, connectionPriority: Int): Boolean {\n        checkInitialize()\n        if (connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_BALANCED &&\n            connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_HIGH &&\n            connectionPriority != BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER) {\n            return false\n        }\n        return bleBaseRequest?.setConnectionPriority(bleDevice, connectionPriority)?: false\n    }\n\n    /**\n     * 读特征值数据\n     */\n    fun readData(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 readUUID: String,\n                 bleReadCallback: BleReadCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.readData(bleDevice, serviceUUID, readUUID, bleReadCallback)\n    }\n\n    /**\n     * 写数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     */\n    fun writeData(bleDevice: BleDevice,\n                  serviceUUID: String,\n                  writeUUID: String,\n                  data: ByteArray,\n                  writeType: Int? = null,\n                  bleWriteCallback: BleWriteCallback.() -> Unit) {\n        writeData(\n            bleDevice,\n            serviceUUID,\n            writeUUID,\n            SparseArray<ByteArray>(1).apply {\n                put(0, data)\n            },\n            writeType,\n            bleWriteCallback\n        )\n    }\n\n    /**\n     * 写数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     */\n    fun writeData(bleDevice: BleDevice,\n                  serviceUUID: String,\n                  writeUUID: String,\n                  dataArray: SparseArray<ByteArray>,\n                  writeType: Int? = null,\n                  bleWriteCallback: BleWriteCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.writeData(bleDevice, serviceUUID, writeUUID, dataArray, writeType, bleWriteCallback)\n    }\n\n    /**\n     * OTA推荐此方法\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     * 与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     *\n     * @param skipErrorPacketData 是否跳过数据长度为0的数据包\n     * @param retryWriteCount 写失败后重试的次数\n     */\n    fun writeQueueData(bleDevice: BleDevice,\n                       serviceUUID: String,\n                       writeUUID: String,\n                       data: ByteArray,\n                       skipErrorPacketData: Boolean = false,\n                       retryWriteCount: Int = 0,\n                       retryDelayTime: Long = 0L,\n                       writeType: Int? = null,\n                       bleWriteCallback: BleWriteCallback.() -> Unit) {\n        writeQueueData(\n            bleDevice,\n            serviceUUID,\n            writeUUID,\n            SparseArray<ByteArray>(1).apply {\n                put(0, data)\n            },\n            skipErrorPacketData,\n            retryWriteCount,\n            retryDelayTime,\n            writeType,\n            bleWriteCallback\n        )\n    }\n\n    /**\n     * OTA推荐此方法\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     * 与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     *\n     * @param skipErrorPacketData 是否跳过数据长度为0的数据包\n     * @param retryWriteCount 写失败后重试的次数\n     */\n    fun writeQueueData(bleDevice: BleDevice,\n                       serviceUUID: String,\n                       writeUUID: String,\n                       dataArray: SparseArray<ByteArray>,\n                       skipErrorPacketData: Boolean = false,\n                       retryWriteCount: Int = 0,\n                       retryDelayTime: Long = 0L,\n                       writeType: Int? = null,\n                       bleWriteCallback: BleWriteCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.writeQueueData(\n            bleDevice,\n            serviceUUID,\n            writeUUID,\n            dataArray,\n            skipErrorPacketData,\n            retryWriteCount,\n            retryDelayTime,\n            writeType,\n            bleWriteCallback\n        )\n    }\n\n    /**\n     * 取消写操作\n     * 注意：不区分具体的特征值，因为不同的写方式和不同的写队列类型处理不一致。同个设备的写队列为Independent时，\n     * 为导致写忙碌(这个时候根据特征值取消写队列没有意义)；其他类型的写队列，是共用一个队列，而队列中的数据没有\n     * 区分特征值，所以无法取消某个特征值的写操作。\n     */\n    fun cancelWriting(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.cancelWriting(bleDevice)\n    }\n\n    /**\n     * 获取所有已连接设备集合(不包含其他应用连接的设备、系统连接的设备)\n     */\n    fun getAllConnectedDevice(): MutableList<BleDevice>? {\n        checkInitialize()\n        return bleBaseRequest?.getAllConnectedDevice()\n    }\n\n    /**\n     * 获取系统已连接设备集合，确保已获取到权限\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun getSystemAllConnectedDevice(): MutableList<BluetoothDevice>? {\n        checkInitialize()\n        if (!BleUtil.isPermission(application)) {\n            return null\n        }\n        return bluetoothManager?.getConnectedDevices(BluetoothProfile.GATT)\n    }\n\n    /**\n     * 添加设备的连接状态发生变化、indicate/notify收到数据、mtu改变的回调\n     *  这个回调会独立存在，与[connect]的bleConnectCallback、[notify]的bleNotifyCallback、\n     *  [indicate]的bleIndicateCallback、[setMtu]的bleMtuChangedCallback不冲突\n     */\n    fun addBleEventCallback(bleDevice: BleDevice, bleEventCallback: BleEventCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.addBleEventCallback(bleDevice, bleEventCallback)\n    }\n\n    /**\n     * 移除该设备的连接回调\n     */\n    fun removeBleConnectCallback(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.removeBleConnectCallback(bleDevice)\n    }\n\n    /**\n     * 替换该设备的连接回调\n     */\n    fun replaceBleConnectCallback(bleDevice: BleDevice, bleConnectCallback: BleConnectCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.replaceBleConnectCallback(bleDevice, bleConnectCallback)\n    }\n\n    /**\n     * 替换该设备的连接回调\n     */\n    fun replaceBleConnectCallback(address: String, bleConnectCallback: BleConnectCallback.() -> Unit) {\n        replaceBleConnectCallback(buildBleDeviceByDeviceAddress(address), bleConnectCallback)\n    }\n\n    /**\n     * 移除该设备的Indicate回调\n     */\n    fun removeBleIndicateCallback(bleDevice: BleDevice, indicateUUID: String) {\n        checkInitialize()\n        bleBaseRequest?.removeBleIndicateCallback(bleDevice, indicateUUID)\n    }\n\n    /**\n     * 移除该设备的Notify回调\n     */\n    fun removeBleNotifyCallback(bleDevice: BleDevice, notifyUUID: String) {\n        checkInitialize()\n        bleBaseRequest?.removeBleNotifyCallback(bleDevice, notifyUUID)\n    }\n\n    /**\n     * 移除该设备的Read回调\n     */\n    fun removeBleReadCallback(bleDevice: BleDevice, readUUID: String) {\n        checkInitialize()\n        bleBaseRequest?.removeBleReadCallback(bleDevice, readUUID)\n    }\n\n    /**\n     * 移除该设备的MtuChanged回调\n     */\n    fun removeBleMtuChangedCallback(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.removeBleMtuChangedCallback(bleDevice)\n    }\n\n    /**\n     * 移除该设备的Rssi回调\n     */\n    fun removeBleRssiCallback(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.removeBleRssiCallback(bleDevice)\n    }\n\n    /**\n     * 移除该设备的Write回调\n     * bleWriteCallback为空，则会移除writeUUID下的所有callback\n     */\n    fun removeBleWriteCallback(bleDevice: BleDevice,\n                               writeUUID: String,\n                               bleWriteCallback: BleWriteCallback? = null\n    ) {\n        checkInitialize()\n        bleBaseRequest?.removeBleWriteCallback(bleDevice, writeUUID, bleWriteCallback)\n    }\n\n    /**\n     * 移除该设备的Scan回调\n     */\n    @Synchronized\n    fun removeBleScanCallback() {\n        checkInitialize()\n        bleBaseRequest?.removeBleScanCallback()\n    }\n\n    /**\n     * 添加一个新的扫描回调\n     */\n    fun addBleScanCallback(bleScanCallback: BleScanCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.addBleScanCallback(bleScanCallback)\n    }\n\n    /**\n     * 移除该设备回调，BleConnectCallback除外\n     */\n    fun removeAllCharacterCallback(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.removeAllCharacterCallback(bleDevice)\n    }\n\n    /**\n     * 移除该设备Event回调\n     */\n    fun removeBleEventCallback(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.removeBleEventCallback(bleDevice)\n    }\n\n    /**\n     * 断开所有设备的连接，先回调状态，再close\n     */\n    @Synchronized\n    fun disConnectAll() {\n        checkInitialize()\n        bleBaseRequest?.disConnectAll()\n    }\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    @Synchronized\n    fun closeAll() {\n        checkInitialize()\n        bleBaseRequest?.closeAll()\n        application = null\n        bleOptions = null\n        bluetoothManager = null\n        bleBaseRequest = null\n        BleLogger.i(\"资源释放完毕，BleCore SDK退出\")\n    }\n\n    /**\n     * 注册系统蓝牙广播\n     */\n    fun registerBluetoothStateReceiver(bluetoothCallback: BluetoothCallback.() -> Unit) {\n        checkInitialize()\n        bleBaseRequest?.registerBluetoothStateReceiver(bluetoothCallback)\n    }\n\n    /**\n     * 取消注册系统蓝牙广播\n     */\n    fun unRegisterBluetoothStateReceiver() {\n        checkInitialize()\n        bleBaseRequest?.unRegisterBluetoothStateReceiver()\n    }\n\n    /**\n     * 断开某个设备的连接 释放资源\n     */\n    @Synchronized\n    fun close(bleDevice: BleDevice) {\n        checkInitialize()\n        bleBaseRequest?.close(bleDevice)\n        BleLogger.i(\"${bleDevice}资源释放完毕\")\n    }\n\n    fun getOptions() = bleOptions\n\n    fun getContext() = application\n\n    fun getBluetoothManager() = bluetoothManager\n\n    private fun checkInitialize() {\n        if (bleBaseRequest == null) {\n            BleLogger.e(\"未初始化，请调用BleManager.init()\")\n        }\n    }\n\n    /**\n     * 通过设备地址构建BleDevice对象，确保已获取到权限\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun buildBleDeviceByDeviceAddress(deviceAddress: String): BleDevice {\n        val deviceInfo = bluetoothManager?.adapter?.getRemoteDevice(deviceAddress)\n        return BleDevice(\n            deviceInfo,\n            deviceInfo?.name?: \"\",\n            deviceAddress,\n            0,\n            0,\n            null,\n            null\n        )\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/attribute/BleOptions.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.attribute\n\nimport com.bhm.ble.data.BleTaskQueueType\nimport com.bhm.ble.data.Constants.AUTO_CONNECT\nimport com.bhm.ble.data.Constants.CONTAIN_SCAN_DEVICE_NAME\nimport com.bhm.ble.data.Constants.DEFAULT_AUTO_SET_MTU\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_RETRY_COUNT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_RETRY_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_MAX_CONNECT_NUM\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_RETRY_COUNT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_RETRY_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_TASK_QUEUE_TYPE\nimport com.bhm.ble.data.Constants.ENABLE_LOG\nimport com.bhm.ble.data.Constants.SCAN_NEED_CHECK_GPS\nimport com.bhm.ble.data.Constants.STOP_SCAN_WHEN_START_CONNECT\n\n\n/**\n * 配置项\n *\n * @author Buhuiming\n * @date 2023年05月18日 15时04分\n */\nclass BleOptions private constructor(builder: Builder) {\n\n    var scanServiceUuids = builder.scanServiceUuids\n\n    var scanDeviceNames = builder.scanDeviceNames\n\n    var scanDeviceAddresses = builder.scanDeviceAddresses\n\n    var containScanDeviceName = builder.containScanDeviceName\n\n    var autoConnect = builder.autoConnect\n\n    var enableLog = builder.enableLog\n\n    var scanMillisTimeOut = builder.scanMillisTimeOut\n\n    var scanRetryCount = builder.scanRetryCount\n\n    var scanRetryInterval = builder.scanRetryInterval\n\n    var connectMillisTimeOut = builder.connectMillisTimeOut\n\n    var connectRetryCount = builder.connectRetryCount\n\n    var connectRetryInterval = builder.connectRetryInterval\n\n    var operateMillisTimeOut = builder.operateMillisTimeOut\n\n    var operateInterval = builder.operateInterval\n\n    var maxConnectNum = builder.maxConnectNum\n\n    var mtu = builder.mtu\n\n    var autoSetMtu = builder.autoSetMtu\n\n    var taskQueueType = builder.taskQueueType\n\n    var stopScanWhenStartConnect = builder.stopScanWhenStartConnect\n\n    var needCheckGps = builder.needCheckGps\n\n    companion object {\n\n        @JvmStatic\n        fun getDefaultBleOptions() : BleOptions = BleOptions(Builder())\n\n        @JvmStatic\n        fun builder() = Builder()\n    }\n\n    class Builder {\n\n        internal var scanServiceUuids: ArrayList<String> = ArrayList(1)\n\n        internal var scanDeviceNames: ArrayList<String> = ArrayList(1)\n\n        internal var scanDeviceAddresses: ArrayList<String> = ArrayList(1)\n\n        internal var containScanDeviceName = CONTAIN_SCAN_DEVICE_NAME\n\n        internal var autoConnect = AUTO_CONNECT\n\n        internal var enableLog = ENABLE_LOG\n\n        internal var scanMillisTimeOut: Long = DEFAULT_SCAN_MILLIS_TIMEOUT\n\n        internal var scanRetryCount: Int = DEFAULT_SCAN_RETRY_COUNT\n\n        internal var scanRetryInterval: Long = DEFAULT_SCAN_RETRY_INTERVAL\n\n        internal var connectMillisTimeOut: Long = DEFAULT_CONNECT_MILLIS_TIMEOUT\n\n        internal var connectRetryCount: Int = DEFAULT_CONNECT_RETRY_COUNT\n\n        internal var connectRetryInterval: Long = DEFAULT_CONNECT_RETRY_INTERVAL\n\n        internal var operateMillisTimeOut: Long = DEFAULT_OPERATE_MILLIS_TIMEOUT\n\n        internal var operateInterval: Long = DEFAULT_OPERATE_INTERVAL\n\n        internal var maxConnectNum: Int = DEFAULT_MAX_CONNECT_NUM\n\n        internal var mtu: Int = DEFAULT_MTU\n\n        internal var autoSetMtu: Boolean = DEFAULT_AUTO_SET_MTU\n\n        internal var taskQueueType: BleTaskQueueType = DEFAULT_TASK_QUEUE_TYPE\n\n        internal var stopScanWhenStartConnect = STOP_SCAN_WHEN_START_CONNECT\n\n        internal var needCheckGps = SCAN_NEED_CHECK_GPS\n\n        /**\n         * 设置扫描过滤规则：只查询对应ServiceUuid的设备\n         */\n        fun setScanServiceUuid(vararg scanServiceUuids: String) = apply {\n            scanServiceUuids.forEach {\n                if (it.isNotEmpty()) {\n                    this.scanServiceUuids.add(it)\n                }\n            }\n        }\n\n        /**\n         * 设置扫描过滤规则：只查询对应设备名的设备\n         */\n        fun setScanDeviceName(vararg scanDeviceNames: String) = apply {\n            scanDeviceNames.forEach {\n                if (it.isNotEmpty()) {\n                    this.scanDeviceNames.add(it)\n                }\n            }\n        }\n\n        /**\n         * 设置扫描过滤规则：只查询对应设备Mac的设备\n         */\n        fun setScanDeviceAddress(vararg scanDeviceAddresses: String) = apply {\n            scanDeviceAddresses.forEach {\n                if (it.isNotEmpty()) {\n                    this.scanDeviceAddresses.add(it)\n                }\n            }\n        }\n\n        /**\n         * 设置扫描过滤规则：是否模糊匹配设备名，默认[CONTAIN_SCAN_DEVICE_NAME]\n         */\n        fun isContainScanDeviceName(containScanDeviceName: Boolean) = apply {\n            this.containScanDeviceName = containScanDeviceName\n        }\n\n        /**\n         * 非主动断开后是否自动连接，默认为[AUTO_CONNECT]\n         */\n        @Deprecated(message = \"请在业务层处理自动重连，autoConnect设计的初衷是为了断开重连，利用bluetoothGatt\" +\n                \"的重连，但BleCore在断开连接或连接失败后，bluetoothGatt会被close掉释放资源，bluetoothGatt的重连\" +\n                \"不再作用，此函数将会被删除\",\n            replaceWith = ReplaceWith(\n                \"BleConnectCallback.onDisConnected、BleManager.connect\"\n            )\n        )\n        fun setAutoConnect(autoConnect: Boolean) = apply {\n            this.autoConnect = autoConnect\n        }\n\n        /**\n         * 扫描超时时间，单位毫秒，默认为[DEFAULT_SCAN_MILLIS_TIMEOUT]\n         */\n        fun setScanMillisTimeOut(scanMillisTimeOut: Long) = apply {\n            this.scanMillisTimeOut = scanMillisTimeOut\n        }\n\n        /**\n         * 这个机制是：不会因为扫描的次数导致上一次扫描到的数据被清空，也就是onScanStart和onScanComplete\n         * 都只会回调一次，而且扫描到的数据是所有扫描次数的总和\n         * 设置扫描重试次数，默认为[DEFAULT_SCAN_RETRY_COUNT]次，总扫描次数=scanRetryCount+1次\n         * 设置扫描重试间隔，默认为[DEFAULT_SCAN_RETRY_INTERVAL]\n         */\n        fun setScanRetryCountAndInterval(scanRetryCount: Int, scanRetryInterval: Long) = apply {\n            this.scanRetryCount = scanRetryCount\n            this.scanRetryInterval = scanRetryInterval\n        }\n\n        /**\n         * 默认打开库中的运行日志 默认[ENABLE_LOG]\n         */\n        fun setEnableLog(enableLog: Boolean) = apply {\n            this.enableLog = enableLog\n        }\n\n        /**\n         * 连接超时时间，单位毫秒，默认为[DEFAULT_CONNECT_MILLIS_TIMEOUT]\n         */\n        fun setConnectMillisTimeOut(connectMillisTimeOut: Long) = apply {\n            this.connectMillisTimeOut = connectMillisTimeOut\n        }\n\n        /**\n         * 设置连接重试次数，默认为[DEFAULT_CONNECT_RETRY_COUNT]次\n         * 设置连接重试间隔，单位毫秒，默认为[DEFAULT_CONNECT_RETRY_INTERVAL]\n         */\n        fun setConnectRetryCountAndInterval(connectRetryCount: Int, connectRetryInterval: Long) = apply {\n            this.connectRetryCount = connectRetryCount\n            this.connectRetryInterval = connectRetryInterval\n        }\n\n        /**\n         * 设置readRssi、setMtu、write、read、notify、indicate的超时时间，\n         * 单位毫秒，默认为[DEFAULT_OPERATE_MILLIS_TIMEOUT]\n         */\n        fun setOperateMillisTimeOut(operateMillisTimeOut: Long) = apply {\n            this.operateMillisTimeOut = operateMillisTimeOut\n        }\n\n        /**\n         * 设置操作之间的间隔，单位毫秒，默认为[DEFAULT_OPERATE_INTERVAL]\n         */\n        fun setOperateInterval(operateInterval: Long) = apply {\n            this.operateInterval = operateInterval\n        }\n\n        /**\n         * 设置最大连接数，默认为[DEFAULT_MAX_CONNECT_NUM]\n         */\n        fun setMaxConnectNum(maxConnectNum: Int) = apply {\n            this.maxConnectNum = maxConnectNum\n        }\n\n        /**\n         * 设置mtu，默认为[DEFAULT_MTU]\n         */\n        fun setMtu(mtu: Int) = apply {\n            setMtu(mtu, DEFAULT_AUTO_SET_MTU)\n        }\n\n        /**\n         * 设置mtu，默认为[DEFAULT_MTU]\n         * @param autoSetMtu 是否自动设置mtu，true：连接成功之后会自动设置mtu，默认为[DEFAULT_AUTO_SET_MTU]\n         */\n        fun setMtu(mtu: Int, autoSetMtu: Boolean) = apply {\n            this.mtu = mtu\n            this.autoSetMtu = autoSetMtu\n        }\n\n        /**\n         * 设置任务队列类型，默认为[BleTaskQueueType.Default]，设置完需断开所有设备才可生效\n         * [BleTaskQueueType.Default] 一个设备的Notify\\Indicate\\Read\\Write\\mtu操作所对应的\n         * 任务共享同一个任务队列(共享队列)(不区分特征值)，rssi在rssi队列\n         *\n         * [BleTaskQueueType.Operate] 一个设备每个操作独立一个任务队列(不区分特征值)\n         * Notify在Notify队列中，Indicate在Indicate队列中，Read在Read队列中，\n         * Write在Write队列中，mtu在共享队列，rssi在rssi队列中，\n         * 不同操作任务之间相互不影响，相同操作任务之间先进先出按序执行\n         * 例如特征值1的写操作和特征值2的写操作，在同一个任务队列当中；特征值1的写操作和特征值1的读操作，\n         * 在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n         *\n         * [BleTaskQueueType.Independent] 一个设备每个特征值下的每个操作独立一个任务队列(区分特征值)\n         * Notify\\Indicate\\Read\\Write所对应的任务分别放入到独立的任务队列中，\n         * mtu在共享队列，rssi在rssi队列中，\n         * 且按特征值区分，不同操作任务之间相互不影响，相同操作任务之间相互不影响\n         * 例如特征值1的写操作和特征值2的写操作，在两个不同的任务队列当中；特征值1的写操作和特征值1的读操作，\n         * 在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n         */\n        fun setTaskQueueType(taskQueueType: BleTaskQueueType) = apply {\n            this.taskQueueType = taskQueueType\n        }\n\n        /**\n         * 当连接设备时是否停止扫描，默认为[STOP_SCAN_WHEN_START_CONNECT]\n         * 注意：该参数对startScanAndConnect方法无效\n         */\n        fun setStopScanWhenStartConnect(stopScanWhenStartConnect: Boolean) = apply {\n            this.stopScanWhenStartConnect = stopScanWhenStartConnect\n        }\n\n        /**\n         * 设置是否需要打开GPS，默认为[SCAN_NEED_CHECK_GPS]\n         *  有些设备不具备GPS模块，如果需要打开或关闭GPS，请调用该方法设置\n         */\n        fun setNeedCheckGps(needCheckGps: Boolean) = apply {\n            this.needCheckGps = needCheckGps\n        }\n\n        fun build(): BleOptions {\n            return BleOptions(this)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleBaseCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.request.base.BleRequestImp\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\n\n\n/**\n * 回调的基类\n *\n * @author Buhuiming\n * @date 2023年05月26日 16时04分\n */\nopen class BleBaseCallback {\n\n    private val mainScope = BleRequestImp.get().getMainScope()\n\n    private val ioScope = BleRequestImp.get().getIOScope()\n\n    private val defaultScope = BleRequestImp.get().getDefaultScope()\n\n    private var key: String? = null\n\n    fun launchInMainThread(block: suspend CoroutineScope.() -> Unit): Job {\n        return mainScope.launch {\n            block.invoke(this)\n        }\n    }\n\n    fun launchInIOThread(block: suspend CoroutineScope.() -> Unit): Job {\n        return ioScope.launch {\n            block.invoke(this)\n        }\n    }\n\n    fun launchInDefaultThread(block: suspend CoroutineScope.() -> Unit): Job {\n        return defaultScope.launch {\n            block.invoke(this)\n        }\n    }\n\n    open fun setKey(key: String) {\n        this.key = key\n    }\n\n    open fun getKey() = key\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleConnectCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport android.bluetooth.BluetoothGatt\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.data.BleConnectFailType\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport kotlinx.coroutines.delay\n\n\n/**\n * Ble连接回调\n * 在某些型号手机上，connectGatt必须在主线程才能有效，所以把连接过程放在主线程，回调也在主线程\n * @author Buhuiming\n * @date 2023年05月24日 14时00分\n */\nopen class BleConnectCallback : BleBaseCallback() {\n\n    private var start: ((bleDevice: BleDevice) -> Unit)? = null\n\n    private var connectSuccess: ((bleDevice: BleDevice, gatt: BluetoothGatt?) -> Unit)? = null\n\n    private var connectFail: ((bleDevice: BleDevice, connectFailType: BleConnectFailType) -> Unit)? = null\n\n    private var disConnecting: ((isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                                gatt: BluetoothGatt?, status: Int) -> Unit)? = null\n\n    private var disConnected: ((isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                                gatt: BluetoothGatt?, status: Int) -> Unit)? = null\n\n    /**\n     * 开始连接\n     */\n    fun onConnectStart(value: (bleDevice: BleDevice) -> Unit) {\n        start = value\n    }\n\n    /**\n     * 连接成功\n     */\n    fun onConnectSuccess(value: (bleDevice: BleDevice, gatt: BluetoothGatt?) -> Unit) {\n        connectSuccess = value\n    }\n\n    /**\n     * 连接失败\n     */\n    fun onConnectFail(value: (bleDevice: BleDevice, connectFailType: BleConnectFailType) -> Unit) {\n        connectFail = value\n    }\n\n    /**\n     * 触发断开，此时的设备有可能还是连接状态，未完全断开\n     */\n    fun onDisConnecting(value: (isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                               gatt: BluetoothGatt?, status: Int) -> Unit) {\n        disConnecting = value\n    }\n\n    /**\n     * 连接断开，特指连接后再断开的情况。在这里可以监控设备的连接状态，一旦连接断开，可以根据自身情况考虑对BleDevice\n     * 对象进行重连操作。需要注意的是，断开和重连之间最好间隔一段时间，否则可能会出现长时间连接不上的情况。此外，\n     * 如果通过调用[com.bhm.ble.BleManager.disConnect]方法，主动断开蓝牙连接的结果也会在这个方法中回调，\n     * 此时isActiveDisConnected将会是true。\n     */\n    fun onDisConnected(value: (isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                               gatt: BluetoothGatt?, status: Int) -> Unit) {\n        disConnected = value\n    }\n\n    open fun callConnectStart(bleDevice: BleDevice) {\n        launchInMainThread {\n            start?.invoke(bleDevice)\n        }\n    }\n\n    open fun callConnectFail(bleDevice: BleDevice, connectFailType: BleConnectFailType) {\n        launchInMainThread {\n            connectFail?.invoke(bleDevice, connectFailType)\n        }\n    }\n\n    open fun callConnectSuccess(bleDevice: BleDevice, gatt: BluetoothGatt?) {\n        launchInMainThread {\n            connectSuccess?.invoke(bleDevice, gatt)\n        }\n    }\n\n    open fun callDisConnecting(isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                              gatt: BluetoothGatt?, status: Int) {\n        launchInMainThread {\n            disConnecting?.invoke(isActiveDisConnected, bleDevice, gatt, status)\n        }\n        callDisConnected(isActiveDisConnected, bleDevice, gatt, status)\n    }\n\n    open fun callDisConnected(isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                              gatt: BluetoothGatt?, status: Int) {\n        launchInMainThread {\n            val start = System.currentTimeMillis()\n            while (BleManager.get().isConnected(bleDevice, true)) {\n                //主动断开，需要等待gatt释放的时间更长一些\n                delay(if (isActiveDisConnected) 80 else 4)\n            }\n            val end = System.currentTimeMillis()\n            BleLogger.i(\"触发onDisConnecting，${(end - start)}毫秒后触发onDisConnected\")\n            disConnected?.invoke(isActiveDisConnected, bleDevice, gatt, status)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleEventCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport android.bluetooth.BluetoothGatt\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.data.BleConnectFailType\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport kotlinx.coroutines.delay\n\n/**\n * @description 这是个独立的回调，设备的连接状态发生变化、\n * indicate/notify收到数据、mtu改变会触发\n * 不影响其他callback的回调\n * @author Buhuiming\n * @date 2023年10月31日 15:07:38\n */\nopen class BleEventCallback : BleBaseCallback() {\n\n    private var startConnect: ((bleDevice: BleDevice) -> Unit)? = null\n\n    private var connectFail: ((bleDevice: BleDevice, connectFailType: BleConnectFailType) -> Unit)? = null\n\n    private var disConnecting: ((isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                                 gatt: BluetoothGatt?, status: Int) -> Unit)? = null\n\n    private var connected: ((bleDevice: BleDevice, gatt: BluetoothGatt?) -> Unit)? = null\n\n    private var disConnected: ((isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                                gatt: BluetoothGatt?, status: Int) -> Unit)? = null\n\n    private var characteristicChanged: ((uuid: String?, type: Int, bleDevice: BleDevice, data: ByteArray) -> Unit)? = null\n\n    private var mtuChanged: ((mtu: Int, bleDevice: BleDevice) -> Unit)? = null\n\n    /**\n     * 已连接\n     */\n    fun onConnected(value: (bleDevice: BleDevice, gatt: BluetoothGatt?) -> Unit) {\n        connected = value\n    }\n\n    /**\n     * 收到数据\n     * type = 1 notify方式；type = 2 indicate方式\n     */\n    fun onCharacteristicChanged(value: ((uuid: String?, type: Int, bleDevice: BleDevice, data: ByteArray) -> Unit)) {\n        characteristicChanged = value\n    }\n\n    fun onMtuChanged(value: ((mtu: Int, bleDevice: BleDevice) -> Unit)) {\n        mtuChanged = value\n    }\n\n    /**\n     * 开始连接\n     */\n    fun onConnectStart(value: (bleDevice: BleDevice) -> Unit) {\n        startConnect = value\n    }\n\n    /**\n     * 连接失败\n     */\n    fun onConnectFail(value: (bleDevice: BleDevice, connectFailType: BleConnectFailType) -> Unit) {\n        connectFail = value\n    }\n\n    /**\n     * 触发断开，此时的设备有可能还是连接状态，未完全断开\n     */\n    fun onDisConnecting(value: (isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                                gatt: BluetoothGatt?, status: Int) -> Unit) {\n        disConnecting = value\n    }\n\n    /**\n     * 连接断开，特指连接后再断开的情况。在这里可以监控设备的连接状态，一旦连接断开，可以根据自身情况考虑对BleDevice\n     * 对象进行重连操作。需要注意的是，断开和重连之间最好间隔一段时间，否则可能会出现长时间连接不上的情况。此外，\n     * 如果通过调用[com.bhm.ble.BleManager.disConnect]方法，主动断开蓝牙连接的结果也会在这个方法中回调，\n     * 此时isActiveDisConnected将会是true。\n     */\n    fun onDisConnected(value: (isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                               gatt: BluetoothGatt?, status: Int) -> Unit) {\n        disConnected = value\n    }\n\n    open fun callConnectStart(bleDevice: BleDevice) {\n        launchInMainThread {\n            startConnect?.invoke(bleDevice)\n        }\n    }\n\n    open fun callConnectFail(bleDevice: BleDevice, connectFailType: BleConnectFailType) {\n        launchInMainThread {\n            connectFail?.invoke(bleDevice, connectFailType)\n        }\n    }\n\n    open fun callDisConnecting(isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                               gatt: BluetoothGatt?, status: Int) {\n        launchInMainThread {\n            disConnecting?.invoke(isActiveDisConnected, bleDevice, gatt, status)\n        }\n        callDisConnected(isActiveDisConnected, bleDevice, gatt, status)\n    }\n\n    open fun callDisConnected(isActiveDisConnected: Boolean, bleDevice: BleDevice,\n                              gatt: BluetoothGatt?, status: Int) {\n        launchInMainThread {\n            val start = System.currentTimeMillis()\n            while (BleManager.get().isConnected(bleDevice, true)) {\n                //主动断开，需要等待gatt释放的时间更长一些\n                delay(if (isActiveDisConnected) 80 else 4)\n            }\n            val end = System.currentTimeMillis()\n            BleLogger.i(\"触发onDisConnecting，${(end - start)}毫秒后触发onDisConnected\")\n            disConnected?.invoke(isActiveDisConnected, bleDevice, gatt, status)\n        }\n    }\n\n    open fun callConnected(bleDevice: BleDevice, gatt: BluetoothGatt?) {\n        launchInMainThread {\n            connected?.invoke(bleDevice, gatt)\n        }\n    }\n\n    open fun callCharacteristicChanged(uuid: String?, type: Int, bleDevice: BleDevice, data: ByteArray) {\n        //数据处理的线程需要自行切换\n        characteristicChanged?.invoke(uuid, type, bleDevice, data)\n    }\n\n    open fun callMtuChanged(mtu: Int, bleDevice: BleDevice) {\n        launchInMainThread {\n            mtuChanged?.invoke(mtu, bleDevice)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleIndicateCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * 订阅通知回调\n * Indicate方式\n *\n * @author Buhuiming\n * @date 2023年05月29日 08时47分\n */\nopen class BleIndicateCallback : BleBaseCallback() {\n\n    private var indicateSuccess: ((bleDevice: BleDevice, indicateUUID: String) -> Unit)? = null\n\n    private var indicateFail: ((bleDevice: BleDevice, indicateUUID: String, throwable: Throwable) -> Unit)? = null\n\n    private var characteristicChanged: ((bleDevice: BleDevice, indicateUUID: String, data: ByteArray) -> Unit)? = null\n\n    fun onIndicateFail(value: ((bleDevice: BleDevice, indicateUUID: String, throwable: Throwable) -> Unit)) {\n        indicateFail = value\n    }\n\n    fun onIndicateSuccess(value: ((bleDevice: BleDevice, indicateUUID: String) -> Unit)) {\n        indicateSuccess = value\n    }\n\n    fun onCharacteristicChanged(value: ((bleDevice: BleDevice, indicateUUID: String, data: ByteArray) -> Unit)) {\n        characteristicChanged = value\n    }\n\n    open fun callIndicateFail(bleDevice: BleDevice, indicateUUID: String, throwable: Throwable) {\n        launchInMainThread {\n            indicateFail?.invoke(bleDevice, indicateUUID, throwable)\n        }\n    }\n\n    open fun callIndicateSuccess(bleDevice: BleDevice, indicateUUID: String) {\n        launchInMainThread {\n            indicateSuccess?.invoke(bleDevice, indicateUUID)\n        }\n    }\n\n    open fun callCharacteristicChanged(bleDevice: BleDevice, indicateUUID: String, data: ByteArray) {\n        //数据处理的线程需要自行切换\n        characteristicChanged?.invoke(bleDevice, indicateUUID, data)\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleMtuChangedCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * mtu值变化回调\n *\n * @author Buhuiming\n * @date 2023年05月26日 16时17分\n */\nopen class BleMtuChangedCallback : BleBaseCallback() {\n\n    private var mtuChanged: ((bleDevice: BleDevice, mtu: Int) -> Unit)? = null\n\n    private var fail: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)? = null\n\n    fun onSetMtuFail(value: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)) {\n        fail = value\n    }\n\n    fun onMtuChanged(value: ((bleDevice: BleDevice, mtu: Int) -> Unit)) {\n        mtuChanged = value\n    }\n\n    open fun callSetMtuFail(bleDevice: BleDevice, throwable: Throwable) {\n        launchInMainThread {\n            fail?.invoke(bleDevice, throwable)\n        }\n    }\n\n    open fun callMtuChanged(bleDevice: BleDevice, mtu: Int) {\n        launchInMainThread {\n            mtuChanged?.invoke(bleDevice, mtu)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleNotifyCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * 订阅通知回调\n * Notify方式\n *\n * @author Buhuiming\n * @date 2023年05月29日 08时47分\n */\nopen class BleNotifyCallback : BleBaseCallback() {\n\n    private var notifySuccess: ((bleDevice: BleDevice, notifyUUID: String) -> Unit)? = null\n\n    private var notifyFail: ((bleDevice: BleDevice, notifyUUID: String, throwable: Throwable) -> Unit)? = null\n\n    private var characteristicChanged: ((bleDevice: BleDevice, notifyUUID: String, data: ByteArray) -> Unit)? = null\n\n    fun onNotifyFail(value: ((bleDevice: BleDevice, notifyUUID: String, throwable: Throwable) -> Unit)) {\n        notifyFail = value\n    }\n\n    fun onNotifySuccess(value: ((bleDevice: BleDevice, notifyUUID: String) -> Unit)) {\n        notifySuccess = value\n    }\n\n    fun onCharacteristicChanged(value: ((bleDevice: BleDevice, notifyUUID: String, data: ByteArray) -> Unit)) {\n        characteristicChanged = value\n    }\n\n    open fun callNotifyFail(bleDevice: BleDevice, notifyUUID: String, throwable: Throwable) {\n        launchInMainThread {\n            notifyFail?.invoke(bleDevice, notifyUUID, throwable)\n        }\n    }\n\n    open fun callNotifySuccess(bleDevice: BleDevice, notifyUUID: String) {\n        launchInMainThread {\n            notifySuccess?.invoke(bleDevice, notifyUUID)\n        }\n    }\n\n    open fun callCharacteristicChanged(bleDevice: BleDevice, notifyUUID: String, data: ByteArray) {\n        //数据处理的线程需要自行切换\n        characteristicChanged?.invoke(bleDevice, notifyUUID, data)\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleReadCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * 读回调\n *\n * @author Buhuiming\n * @date 2023年05月26日 15时54分\n */\nopen class BleReadCallback : BleBaseCallback() {\n\n    private var readSuccess: ((bleDevice: BleDevice, data: ByteArray) -> Unit)? = null\n\n    private var readFail: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)? = null\n\n    fun onReadFail(value: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)) {\n        readFail = value\n    }\n\n    fun onReadSuccess(value: ((bleDevice: BleDevice, data: ByteArray) -> Unit)) {\n        readSuccess = value\n    }\n\n    open fun callReadFail(bleDevice: BleDevice, throwable: Throwable) {\n        launchInMainThread {\n            readFail?.invoke(bleDevice, throwable)\n        }\n    }\n\n    open fun callReadSuccess(bleDevice: BleDevice, data: ByteArray) {\n        launchInMainThread {\n            readSuccess?.invoke(bleDevice, data)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleRssiCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * Rssi信号值回调\n *\n * @author Buhuiming\n * @date 2023年05月26日 15时54分\n */\nopen class BleRssiCallback : BleBaseCallback() {\n\n    private var success: ((bleDevice: BleDevice, rssi: Int) -> Unit)? = null\n\n    private var fail: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)? = null\n\n    fun onRssiFail(value: ((bleDevice: BleDevice, throwable: Throwable) -> Unit)) {\n        fail = value\n    }\n\n    fun onRssiSuccess(value: ((bleDevice: BleDevice, rssi: Int) -> Unit)) {\n        success = value\n    }\n\n    open fun callRssiFail(bleDevice: BleDevice, throwable: Throwable) {\n        launchInMainThread {\n            fail?.invoke(bleDevice, throwable)\n        }\n    }\n\n    open fun callRssiSuccess(bleDevice: BleDevice, rssi: Int) {\n        launchInMainThread {\n            success?.invoke(bleDevice, rssi)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleScanCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.data.BleScanFailType\n\n\n/**\n * Ble扫描回调\n *\n * @author Buhuiming\n * @date 2023年05月22日 09时08分\n */\nopen class BleScanCallback : BleBaseCallback() {\n\n    private var start: (() -> Unit)? = null\n\n    private var leScan: ((bleDevice: BleDevice, currentScanCount: Int) -> Unit)? = null\n\n    //相同设备只会出现一次\n    private var leScanDuplicateRemoval: ((bleDevice: BleDevice, currentScanCount: Int) -> Unit)? = null\n\n    private var scanFail: ((scanFailType: BleScanFailType) -> Unit)? = null\n\n    private var scanComplete: ((bleDeviceList: MutableList<BleDevice>,\n                                bleDeviceDuplicateRemovalList: MutableList<BleDevice>) -> Unit)? = null\n\n    /**\n     * 扫描开始\n     */\n    fun onScanStart(value: () -> Unit) {\n        start = value\n    }\n\n    /**\n     * 扫描过程中所有被扫描到的结果回调(同一个设备会在不同的时间，携带自身不同的状态（比如信号强度等），\n     * 出现在这个回调方法中，出现次数取决于周围的设备量及外围设备的广播间隔。)\n     */\n    fun onLeScan(value: (bleDevice: BleDevice, currentScanCount: Int) -> Unit) {\n        leScan = value\n    }\n\n    /**\n     * 扫描过程中的所有过滤后的结果回调。与onLeScan区别之处在于：同一个设备只会出现一次；\n     * 出现的设备是经过扫描过滤规则过滤后的设备。\n     */\n    fun onLeScanDuplicateRemoval(value: (bleDevice: BleDevice, currentScanCount: Int) -> Unit) {\n        leScanDuplicateRemoval = value\n    }\n\n    /**\n     * 扫描失败\n     */\n    fun onScanFail(value: (scanFailType: BleScanFailType) -> Unit) {\n        scanFail = value\n    }\n\n    /**\n     * 扫描完成\n     * bleDeviceList [onLeScan]扫描到的设备之和\n     * bleDeviceDuplicateRemovalList [onLeScanDuplicateRemoval]扫描到的设备之和\n     */\n    fun onScanComplete(value: (bleDeviceList: MutableList<BleDevice>,\n                               bleDeviceDuplicateRemovalList: MutableList<BleDevice>) -> Unit) {\n        scanComplete = value\n    }\n\n    open fun callScanStart() {\n        //MainScope是CoroutineScope类型，为协同作用域，子协程取消后，父协程也会取消\n        launchInMainThread {\n            start?.invoke()\n        }\n    }\n\n    open fun callLeScan(bleDevice: BleDevice, currentScanCount: Int) {\n        leScan?.invoke(bleDevice, currentScanCount)\n    }\n\n    open fun callLeScanDuplicateRemoval(bleDevice: BleDevice, currentScanCount: Int) {\n        leScanDuplicateRemoval?.invoke(bleDevice, currentScanCount)\n    }\n\n    open fun callScanFail(scanFailType: BleScanFailType) {\n        launchInMainThread {\n            scanFail?.invoke(scanFailType)\n        }\n    }\n\n    open fun callScanComplete(bleDeviceList: MutableList<BleDevice>,\n                              bleDeviceDuplicateRemovalList: MutableList<BleDevice>) {\n        launchInMainThread {\n            scanComplete?.invoke(bleDeviceList, bleDeviceDuplicateRemovalList)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BleWriteCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * 写回调\n *\n * @author Buhuiming\n * @date 2023年05月26日 15时54分\n */\nopen class BleWriteCallback : BleBaseCallback() {\n\n    private var writeSuccess: ((bleDevice: BleDevice, current: Int, total: Int, justWrite: ByteArray) -> Unit)? = null\n\n    private var writeFail: ((bleDevice: BleDevice, current: Int, total: Int, throwable: Throwable) -> Unit)? = null\n\n    private var writeComplete: ((bleDevice: BleDevice, allSuccess: Boolean) -> Unit)? = null\n\n    fun onWriteFail(value: ((bleDevice: BleDevice, current: Int, total: Int, throwable: Throwable) -> Unit)) {\n        writeFail = value\n    }\n\n    fun onWriteSuccess(value: ((bleDevice: BleDevice, current: Int, total: Int, justWrite: ByteArray) -> Unit)) {\n        writeSuccess = value\n    }\n\n    fun onWriteComplete(value: ((bleDevice: BleDevice, allSuccess: Boolean) -> Unit)) {\n        writeComplete = value\n    }\n\n    open fun callWriteFail(bleDevice: BleDevice, current: Int, total: Int, throwable: Throwable) {\n        launchInMainThread {\n            writeFail?.invoke(bleDevice, current, total, throwable)\n        }\n    }\n\n    open fun callWriteSuccess(bleDevice: BleDevice, current: Int, total: Int, justWrite: ByteArray) {\n        launchInMainThread {\n            writeSuccess?.invoke(bleDevice, current, total, justWrite)\n        }\n    }\n\n    open fun callWriteComplete(bleDevice: BleDevice, allSuccess: Boolean) {\n        launchInMainThread {\n            writeComplete?.invoke(bleDevice, allSuccess)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/callback/BluetoothCallback.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.callback\n\n\n/**\n * 系统蓝牙状态回调\n * @author Buhuiming\n * @date 2023年12月15日 12时07分\n */\nopen class BluetoothCallback : BleBaseCallback() {\n\n    private var stateOn: (() -> Unit)? = null\n\n    private var stateOff: (() -> Unit)? = null\n\n    private var stateTurningOn: (() -> Unit)? = null\n\n    private var stateTurningOff: (() -> Unit)? = null\n\n    /**\n     * 蓝牙打开\n     */\n    fun onStateOn(value: () -> Unit) {\n        stateOn = value\n    }\n\n    /**\n     * 正在打开蓝牙\n     */\n    fun onStateTurningOn(value: () -> Unit) {\n        stateTurningOn = value\n    }\n\n    /**\n     * 蓝牙关闭\n     */\n    fun onStateOff(value: () -> Unit) {\n        stateOff = value\n    }\n\n    /**\n     * 正在关闭蓝牙\n     */\n    fun onStateTurningOff(value: () -> Unit) {\n        stateTurningOff = value\n    }\n\n\n    open fun callStateOn() {\n        stateOn?.invoke()\n    }\n\n    open fun callStateTurningOn() {\n        stateTurningOn?.invoke()\n    }\n\n    open fun callStateOff() {\n        stateOff?.invoke()\n    }\n\n    open fun callStateTurningOff() {\n        stateTurningOff?.invoke()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/control/BleLruHashMap.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.control\n\nimport com.bhm.ble.device.BleConnectedDevice\nimport com.bhm.ble.log.BleLogger\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\n\n\n/**\n * 存放BleConnectRequest的容器，控制连接设备数量\n *\n * @author Buhuiming\n * @date 2023年05月26日 08时59分\n */\ninternal class BleLruHashMap(\n    private val maxSize: Int\n) : ConcurrentHashMap<String, BleConnectedDevice?>(maxSize) {\n\n    private val keyLists = Collections.synchronizedList(LinkedList<String>())\n\n    override fun put(key: String, value: BleConnectedDevice): BleConnectedDevice? {\n        if (size == maxSize) {\n            BleLogger.w(\"超出最大连接设备数：${maxSize}，断开第一个设备的连接\")\n            get(keyLists.firstOrNull())?.disConnect()\n            remove(keyLists.firstOrNull())\n            keyLists.removeFirstOrNull()\n        }\n        keyLists.add(key)\n        return super.put(key, value)\n    }\n\n    override fun remove(key: String): BleConnectedDevice? {\n        keyLists.remove(key)\n        return super.remove(key)\n    }\n\n    override fun clear() {\n        super.clear()\n        keyLists.clear()\n    }\n\n    override fun toString(): String {\n        val sb = StringBuilder()\n        for ((key, value) in entries) {\n            sb.append(String.format(\"%s:%s \", key, value))\n        }\n        return sb.toString()\n    }\n\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/control/BleTask.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.control\n\nimport com.bhm.ble.data.CancelException\nimport com.bhm.ble.data.Constants.CANCEL_UN_COMPLETE\nimport com.bhm.ble.data.Constants.COMPLETED\nimport com.bhm.ble.data.Constants.UN_COMPLETE\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.withContext\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.concurrent.atomic.AtomicInteger\n\n\n/**\n * Ble任务，主要是用来处理rssi、mtu、write、read、notify、indicate操作\n * @param taskId 任务Id\n * @param durationTimeMillis： Long 任务执行的时间，0：表示任务执行时间不固定\n * @param operateInterval：间隔上个任务的时间\n * @param callInMainThread：指定在主线程执行，默认为false\n * @param autoDoNextTask = true自动执行下一个任务，= false，则需要调用doNextTask()执行下一个任务\n * @param canceled 是否已经取消\n * @param block：任务执行函数\n * @param interrupt：中断任务执行\n * @param callback：任务执行回调，成功throwable = CompleteException，超时throwable = TimeoutCancelException，\n * 任务中断抛异常 throwable = CancellationException\n * @author Buhuiming\n * @date 2023年06月02日 11时01分\n */\ninternal class BleTask(val taskId: String,\n              val durationTimeMillis: Long = 0,\n              val operateInterval: Long = 100,\n              val callInMainThread: Boolean = false,\n              val autoDoNextTask: Boolean = true,\n              var canceled: AtomicBoolean = AtomicBoolean(false),\n              private val block: suspend BleTask.() -> Unit,\n              private val interrupt: ((task: BleTask, throwable: Throwable?) -> Unit)? = null,\n              val callback: ((task: BleTask, throwable: Throwable?) -> Unit)? = null\n) {\n\n    private var completed = AtomicInteger(UN_COMPLETE)\n\n    private var timingJob: Job? = null\n\n    fun setCompleted(complete: Int) {\n        if (complete == COMPLETED || complete == CANCEL_UN_COMPLETE) {\n            timingJob?.cancel()\n        }\n        completed.set(complete)\n    }\n\n    fun completed() = completed.get()\n\n    /**\n     * 任务计时器，用于超时\n     */\n    fun setTimingJob(job: Job?) {\n        timingJob = job\n    }\n\n    /**\n     * 执行任务\n     */\n    suspend fun doTask() {\n        if (callInMainThread) {\n            withContext(SupervisorJob() + Dispatchers.Main) {\n                block.invoke(this@BleTask)\n            }\n        } else {\n            block.invoke(this@BleTask)\n        }\n    }\n\n    /**\n     * 中断任务\n     */\n    fun remove() {\n        interrupt?.invoke(this@BleTask, CancelException(\"主动取消任务\"))\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/control/BleTaskList.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.control\n\nimport java.util.*\n\n\n/**\n * 存放操作任务\n *\n * @author Buhuiming\n * @date 2023年06月06日 10时41分\n */\ninternal class BleTaskList {\n\n    private val taskIdList = Collections.synchronizedList(LinkedList<String>())\n\n    private val list = Collections.synchronizedList(LinkedList<BleTask>())\n\n    fun list(): MutableList<BleTask> = list\n\n    fun add(element: BleTask) {\n        taskIdList.add(element.taskId)\n        list.add(element)\n    }\n\n    /**\n     * 循环的移除的话，需要添加synchronized锁，使用Iterator.remove()方法\n     */\n    fun remove(element: BleTask?) {\n        if (taskIdList.contains(element?.taskId)) {\n            taskIdList.remove(element?.taskId)\n        }\n        list.remove(element)\n    }\n\n    fun iterator() = list.iterator()\n\n    fun size() = list.size\n\n    fun contains(task: BleTask?) = list.contains(task)\n\n    fun clear() {\n        synchronized(list) {\n            list.clear()\n        }\n        synchronized(taskIdList) {\n            taskIdList.clear()\n        }\n    }\n\n    fun firstOrNull() = list.firstOrNull()\n\n    fun containsTaskId(taskId: String) = taskIdList.contains(taskId)\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/control/BleTaskQueue.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n\npackage com.bhm.ble.control\n\nimport com.bhm.ble.data.CancelException\nimport com.bhm.ble.data.CompleteException\nimport com.bhm.ble.data.Constants.CANCEL_UN_COMPLETE\nimport com.bhm.ble.data.Constants.COMPLETED\nimport com.bhm.ble.data.Constants.UN_COMPLETE\nimport com.bhm.ble.data.TimeoutCancelException\nimport com.bhm.ble.log.BleLogger\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.newSingleThreadContext\nimport kotlinx.coroutines.withTimeout\nimport java.util.concurrent.ExecutorService\n\n\n/**\n * 请求队列\n *\n * @author Buhuiming\n * @date 2023年06月02日 08时32分\n */\ninternal class BleTaskQueue(private val tag: String = \"\") {\n\n    private var mCoroutineScope: CoroutineScope? = null\n\n    /*\n    * 只有一个线程的线程池，不使用CoroutineScope(Dispatchers.IO)，是因为不同设备的请求可能\n    * 使用相同的线程，从而导致一个设备的请求被阻塞，影响其他设备的请求。\n    */\n    @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)\n    private val threadContext = newSingleThreadContext(tag)\n\n    private val taskList = BleTaskList()\n\n    init {\n        initLoop()\n    }\n\n    private fun initLoop() {\n        mCoroutineScope = CoroutineScope(threadContext)\n    }\n\n    /**\n     * 从队列中取任务处理任务\n     */\n    private suspend fun tryHandleTask(task: BleTask) {\n        //防止有task抛出异常，用CoroutineExceptionHandler捕获异常之后父coroutine关闭了，之后的send的Task不执行了\n        try {\n            BleLogger.i(\"($tag) 开始执行任务：$task\")\n            task.doTask()\n            if (task.completed() == UN_COMPLETE) {\n                task.setCompleted(COMPLETED)\n                task.callback?.invoke(task, CompleteException())\n            } else if (task.completed() == CANCEL_UN_COMPLETE) {\n                task.callback?.invoke(task, CancelException())\n            }\n            task.canceled.set(true)\n            taskList.remove(task)\n            BleLogger.d(\"($tag) 任务：${task}结束完毕，剩下${taskList.size()}个任务\")\n            if (task.autoDoNextTask) {\n                sendTask(taskList.firstOrNull())\n            }\n        } catch (e: Exception) {\n            BleLogger.i(\"($tag) 任务执行中断：$task，\\r\\n ${e.message}\")\n            task.setCompleted(CANCEL_UN_COMPLETE)\n            task.callback?.invoke(task, CancellationException(e.message))\n            task.canceled.set(true)\n            taskList.remove(task)\n            if (task.autoDoNextTask) {\n                sendTask(taskList.firstOrNull())\n            }\n        }\n    }\n\n    /**\n     * 添加任务\n     * @param task ITask\n     */\n    @Synchronized\n    fun addTask(task: BleTask) {\n        if (mCoroutineScope == null) {\n            initLoop()\n        }\n        BleLogger.d(\"($tag) 当前任务数量：${taskList.size()}, 添加任务：$task\")\n        task.setCompleted(UN_COMPLETE)\n        taskList.add(task)\n        taskForTiming(task)\n        if (taskList.size() == 1) {\n            sendTask(task)\n        }\n    }\n\n    fun getTaskList() = taskList\n\n    /**\n     * 任务的超时计时\n     */\n    private fun taskForTiming(task: BleTask) {\n        if (task.durationTimeMillis <= 0) {\n            return\n        }\n        val context = if (task.callInMainThread) {\n            SupervisorJob() + Dispatchers.Main\n        } else {\n            SupervisorJob() + Dispatchers.IO\n        }\n        val timingJob = CoroutineScope(context).launch {\n            withTimeout(task.durationTimeMillis) {\n                delay(task.durationTimeMillis)\n            }\n        }\n        timingJob.invokeOnCompletion {\n            if (it is TimeoutCancellationException &&\n                task.completed() == UN_COMPLETE) {\n                task.canceled.set(true)\n                removeTask(task)\n                task.callback?.invoke(task, TimeoutCancelException())\n            } else if (it is CancellationException) {\n                task.canceled.set(true)\n                BleLogger.i(\"($tag) 任务完成，未超时：$task\")\n            }\n        }\n        task.setTimingJob(timingJob)\n    }\n\n    /**\n     * 移除任务\n     */\n    @Synchronized\n    private fun removeTask(task: BleTask?) {\n        if (taskList.contains(task)) {\n            task?.setCompleted(CANCEL_UN_COMPLETE)\n            if (task == taskList.firstOrNull()) {\n                //正在执行\n                BleLogger.e(\"($tag) 任务正在执行，但超时，移除任务：$task\")\n                task?.remove()\n                taskList.remove(task)\n            } else {\n                BleLogger.e(\"($tag) 任务在队列未执行，但超时，移除任务：${task}\")\n                taskList.remove(task)\n            }\n        }\n    }\n\n//    fun doNextTask() {\n//        val task = taskList.firstOrNull()\n//        if (task?.completed() == UN_COMPLETE || task?.completed() == CANCEL_UN_COMPLETE) {\n//            doNextTask()\n//            return\n//        }\n//        sendTask(task)\n//    }\n\n    /**\n     * 移除任务\n     */\n    @Synchronized\n    fun removeTask(taskId: String): Boolean {\n        if (!taskList.containsTaskId(taskId)) {\n            return false\n        }\n        var success = false\n        synchronized(taskList.list()) {\n            val iterator = taskList.iterator()\n            while (iterator.hasNext()) {\n                val task = iterator.next()\n                if (task.taskId == taskId) {\n                    task.canceled.set(true)\n                    task.setCompleted(CANCEL_UN_COMPLETE)\n                    success = true\n                    if (task == taskList.firstOrNull()) {\n                        //正在执行\n                        BleLogger.e(\"($tag) 移除正在执行的任务：$task\")\n                        task.remove()\n                    } else {\n                        BleLogger.e(\"($tag) 移除队列中的任务：${task}\")\n                        iterator.remove()\n                    }\n                }\n            }\n        }\n        return success\n    }\n\n    /**\n     * 发送执行任务\n     */\n    @Synchronized\n    private fun sendTask(task: BleTask?) {\n        if (task == null) {\n            BleLogger.i(\"($tag) 所有任务执行完毕\")\n            return\n        }\n        mCoroutineScope?.launch {\n            //两次操作之间最好间隔一小段时间，如100ms（具体时间可以根据自己实际蓝牙外设自行尝试延长或缩短）\n            delay(task.operateInterval)\n            //任务还在队列中并且未取消才执行\n            if (taskList.contains(task) && !task.canceled.get()) {\n                tryHandleTask(task)\n            }\n        }\n    }\n\n    fun removeAllTask() {\n        synchronized(taskList.list()) {\n            val iterator = taskList.iterator()\n            while (iterator.hasNext()) {\n                val task = iterator.next()\n                task.setTimingJob(null)\n                task.setCompleted(CANCEL_UN_COMPLETE)\n                if (task == taskList.firstOrNull()) {\n                    task.remove()\n                } else {\n                    iterator.remove()\n                }\n            }\n        }\n        taskList.clear()\n    }\n\n    /**\n     * 关闭并释放资源\n     */\n    fun clear() {\n        removeAllTask()\n        mCoroutineScope?.cancel()\n        mCoroutineScope = null\n        (threadContext.executor as ExecutorService).shutdown()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleConnectFailType.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 连接失败类型\n *\n * @author Buhuiming\n * @date 2023年05月26日 11时10分\n */\nsealed class BleConnectFailType {\n\n    /**\n     * BluetoothDevice为空\n     */\n    object NullableBluetoothDevice: BleConnectFailType()\n\n    /**\n     * 设备不支持Ble\n     */\n    object UnSupportBle: BleConnectFailType()\n\n    /**\n     * 设备未打开蓝牙\n     */\n    object BleDisable: BleConnectFailType()\n\n    /**\n     * 未申请权限\n     */\n    object NoBlePermission: BleConnectFailType()\n\n    /**\n     * 连接异常\n     */\n    class ConnectException(val throwable: Throwable): BleConnectFailType()\n\n    /**\n     * 连接超时\n     */\n    object ConnectTimeOut: BleConnectFailType()\n\n    /**\n     * 连接中\n     */\n    object AlreadyConnecting: BleConnectFailType()\n\n    /**\n     * 扫描并连接时，扫描到的BluetoothDevice为空\n     */\n    object ScanNullableBluetoothDevice: BleConnectFailType()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleConnectLastState.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 连接的最后状态\n *\n * @author Buhuiming\n * @date 2023年05月29日 09时02分\n */\ninternal sealed class BleConnectLastState {\n\n    /**\n     * 初始状态\n     */\n    object ConnectIdle : BleConnectLastState()\n\n    /**\n     * 连接中状态\n     */\n    object Connecting : BleConnectLastState()\n\n    /**\n     * 已连接状态\n     */\n    object Connected : BleConnectLastState()\n\n    /**\n     * 连接失败状态\n     */\n    object ConnectFailure : BleConnectLastState()\n\n    /**\n     * 断开状态\n     */\n    object Disconnect : BleConnectLastState()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleDescriptorGetType.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 用来配置打开或关闭notify、indicate时，获取的描述符(Descriptor)的类型\n * 原理：正常情况下，每个特征值下至少有一个默认描述符，并且遵循蓝牙联盟定义的UUID规则，如\n * [com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR]便是蓝牙联盟定义的\n * 客户端特性配置的描述符UUID，这样做是方便BLE终端在接入不同类型设备时，能够获取到正确的配置。比如有一个APP，需要\n * 接入A商家的智能手表和B商家的智能手表来监听用户的心跳，而如果A商家的智能手表或者B商家的智能手表不遵循蓝牙联盟定义关于\n * 心跳相关的UUID，则对APP来说就要分别去获取A商家的智能手表或者B商家的智能手表对应特征值的描述符UUID，显然是不合理的。\n * 当然这个是需要硬件设备支持的，也就是说硬件设备可以自定义UUID，但需要遵循规则。\n * 在开发过程中，我们会遇到不同硬件设备定义UUID的情况，有的硬件设备通过特征值的UUID来获取描述符(用来writeDescriptor，\n * 打开或关闭notify、indicate)，而非是通过系统提供接受通知自带的UUID获取描述符。此外特征值有多个描述符时，获取其中\n * 一个描述符来写入数据，可能会导致onCharacteristicChanged函数没有回调，我不确定是否是硬件设备需要支持修改的问题。\n * 因此[AllDescriptor]方式则是简单粗暴的将特征值下所有的描述符都写入数据，以保证onCharacteristicChanged函数回调，\n * 这个方法经过了一系列设备的验证可行，但不保证是完全有效的。\n * @author Buhuiming\n * @date 2023年07月19日 16时07分\n */\nsealed class BleDescriptorGetType {\n\n    /**\n     * 默认方式，将通过系统提供接受通知自带的UUID获取Descriptor\n     * [com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR]\n     */\n    object Default : BleDescriptorGetType()\n\n    /**\n     * 将通过特征值的UUID获取Descriptor\n     */\n    object CharacteristicDescriptor : BleDescriptorGetType()\n\n    /**\n     * 将获取特征值下所有的Descriptor\n     */\n    object AllDescriptor : BleDescriptorGetType()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleException.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"unused\")\n\npackage com.bhm.ble.data\n\nimport kotlinx.coroutines.CancellationException\n\n\n/**\n * 完成时抛出的Exception\n * @author Buhuiming\n * @date 2023年05月29日 16时18分\n */\nopen class CompleteException(msg: String? = null) : CancellationException(msg)\n\n/**\n * 主动取消\n * @author Buhuiming\n * @date :2023/6/5 14:32\n */\nopen class CancelException(msg: String? = null) : CancellationException(msg)\n\n/**\n * 超时抛出的Exception\n * @author Buhuiming\n * @date :2023/6/5 10:24\n */\nopen class TimeoutCancelException(msg: String? = null) : CancellationException(msg)\n\n/**\n * 设备未连接抛出的Exception\n * @author Buhuiming\n * @date :2023/6/5 10:24\n */\nopen class UnConnectedException(msg: String? = \"设备未连接\") : CancellationException(msg)\n\n/**\n * 设备缺乏权限抛出的Exception\n * @author Buhuiming\n * @date :2023/6/5 10:24\n */\nopen class NoBlePermissionException(msg: String? = \"缺乏权限\") : CancellationException(msg)\n\n/**\n * 设备不支持某个功能抛出的Exception\n * @author Buhuiming\n * @date :2023/6/5 10:24\n */\nopen class UnSupportException(msg: String? = \"设备不支持此功能\") : CancellationException(msg)\n\n/**\n * 主动断开连接时抛出的Exception\n *\n * @author Buhuiming\n * @date 2023年05月29日 16时18分\n */\nopen class ActiveDisConnectedException(msg: String? = null) : CancellationException(msg)\n\n/**\n * 连接中，主动取消/停止连接时抛出的Exception\n *\n * @author Buhuiming\n * @date 2024年01月16日 09时18分\n */\nopen class ActiveStopConnectedException(msg: String? = null) : CancellationException(msg)\n\n/**\n * 未定义的Exception\n *\n * @author Buhuiming\n * @date 2023年05月29日 16时18分\n */\nopen class UnDefinedException(msg: String? = null, var code: Int? = null) : Exception(msg)\n"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleScanFailType.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 扫描失败类型\n *\n * @author Buhuiming\n * @date 2023年05月22日 09时59分\n */\nsealed class BleScanFailType {\n\n    /**\n     * 设备不支持Ble\n     */\n    object UnSupportBle: BleScanFailType()\n\n    /**\n     * 设备未打开蓝牙\n     */\n    object BleDisable: BleScanFailType()\n\n    /**\n     * 设备未打开GPS定位\n     */\n    object GPSDisable: BleScanFailType()\n\n    /**\n     * 未申请权限\n     */\n    object NoBlePermission: BleScanFailType()\n\n    /**\n     * 已开启扫描，不能再次开启\n     */\n    object AlReadyScanning: BleScanFailType()\n\n    /**\n     * 扫描错误(这里不再详细区分，具体错误码如下)\n     * 1、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED]\n     * 无法启动扫描，因为应用程序已启动具有相同设置的 BLE 扫描。\n     * 2、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED]\n     * 无法开始扫描，因为无法注册应用程序。\n     * 3、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR]\n     * 由于内部错误无法开始扫描。\n     * 4、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED]\n     * 无法启动电源优化扫描，因为不支持此功能。\n     * 5、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES]\n     * 由于硬件资源不足，无法启动扫描。\n     * 6、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY]\n     * 由于应用程序尝试扫描过于频繁，无法开始扫描。\n     * 7、errorCode = -1，具体看throwable\n     */\n    data class ScanError(val errorCode: Int, val throwable: Throwable?): BleScanFailType()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleTaskQueueType.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 任务队列类型，针对notify\\indicate\\read\\write操作\n * 提供可选的任务队列类型\n *\n * @author Buhuiming\n * @date 2023年06月13日 14时49分\n */\nsealed class BleTaskQueueType {\n\n    /**\n     * 默认值\n     * 一个设备的Notify\\Indicate\\Read\\Write\\mtu操作所对应的任务共享同一个任务\n     * 队列(共享队列)(不区分特征值)，rssi在rssi队列\n     */\n    object Default : BleTaskQueueType()\n\n    /**\n     * 一个设备每个操作独立一个任务队列(不区分特征值)\n     * Notify在Notify队列中，Indicate在Indicate队列中，Read在Read队列中，\n     * Write在Write队列中，mtu在共享队列，rssi在rssi队列中，\n     * 不同操作任务之间相互不影响，相同操作任务之间先进先出按序执行\n     * 例如特征值1的写操作和特征值2的写操作，在同一个任务队列当中；特征值1的写操作和特征值1的读操作，\n     * 在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n     */\n    object Operate : BleTaskQueueType()\n\n    /**\n     * 一个设备每个特征值下的每个操作独立一个任务队列(区分特征值)\n     * Notify\\Indicate\\Read\\Write所对应的任务分别放入到独立的任务队列中，\n     * mtu在共享队列，rssi在rssi队列中，\n     * 且按特征值区分，不同操作任务之间相互不影响，相同操作任务之间相互不影响\n     * 例如特征值1的写操作和特征值2的写操作，在两个不同的任务队列当中；特征值1的写操作和特征值1的读操作，\n     * 在两个不同的任务队列当中，特征值1的读操作和特征值2的写操作，在两个不同的任务队列当中。\n     */\n    object Independent : BleTaskQueueType()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleWriteData.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\nimport com.bhm.ble.callback.BleWriteCallback\nimport java.util.concurrent.atomic.AtomicBoolean\n\n\n/**\n * 写数据\n * @param operateRandomID 数据id，以时间戳生成\n * @param serviceUUID 服务UUID\n * @param writeUUID 特征值UUID\n * @param currentPackage 当前第几数据包\n * @param totalPackage 总数据包数量\n * @param data 数据包\n * @param isWriting 是否正在写\n * @param isWriteFail 是否写失败\n * @param bleWriteCallback 写回调\n *\n * @author Buhuiming\n * @date 2023年06月12日 09时45分\n */\ninternal data class BleWriteData(\n    var operateRandomID: String,\n    var serviceUUID: String,\n    var writeUUID: String,\n    var currentPackage: Int,\n    var totalPackage: Int,\n    var data: ByteArray,\n    var isWriting: AtomicBoolean = AtomicBoolean(false),\n    var isWriteFail: AtomicBoolean = AtomicBoolean(false),\n    var bleWriteCallback: BleWriteCallback,\n    var writeType: Int? = null\n) {\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as BleWriteData\n\n        if (operateRandomID != other.operateRandomID) return false\n        if (serviceUUID != other.serviceUUID) return false\n        if (writeUUID != other.writeUUID) return false\n        if (currentPackage != other.currentPackage) return false\n        if (totalPackage != other.totalPackage) return false\n        if (!data.contentEquals(other.data)) return false\n        if (isWriting != other.isWriting) return false\n        if (bleWriteCallback != other.bleWriteCallback) return false\n        if (writeType != other.writeType) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = operateRandomID.hashCode()\n        result = 31 * result + serviceUUID.hashCode()\n        result = 31 * result + writeUUID.hashCode()\n        result = 31 * result + currentPackage\n        result = 31 * result + totalPackage\n        result = 31 * result + data.contentHashCode()\n        result = 31 * result + isWriting.hashCode()\n        result = 31 * result + isWriteFail.hashCode()\n        result = 31 * result + bleWriteCallback.hashCode()\n        result = 31 * result + (writeType ?: 0)\n        return result\n    }\n\n\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/BleWriteQueueData.kt",
    "content": "package com.bhm.ble.data\n\n/**\n * @description [com.bhm.ble.request.BleWriteRequest.writeQueueData]方法中队列存放的数据结构\n * @author Buhuiming\n * @date 2024/10/18/ 18:04\n */\ninternal data class BleWriteQueueData(\n    var operateRandomID: String,\n    var serviceUUID: String,\n    var writeUUID: String,\n    var data: ByteArray,\n    var skipErrorPacketData: Boolean,\n    var retryWriteCount: Int,\n    var retryDelayTime: Long,\n    var writeType: Int?,\n) {\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as BleWriteQueueData\n\n        if (operateRandomID != other.operateRandomID) return false\n        if (serviceUUID != other.serviceUUID) return false\n        if (writeUUID != other.writeUUID) return false\n        if (!data.contentEquals(other.data)) return false\n        if (skipErrorPacketData != other.skipErrorPacketData) return false\n        if (retryWriteCount != other.retryWriteCount) return false\n        if (retryDelayTime != other.retryDelayTime) return false\n        if (writeType != other.writeType) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = operateRandomID.hashCode()\n        result = 31 * result + serviceUUID.hashCode()\n        result = 31 * result + writeUUID.hashCode()\n        result = 31 * result + data.contentHashCode()\n        result = 31 * result + skipErrorPacketData.hashCode()\n        result = 31 * result + retryWriteCount\n        result = 31 * result + retryDelayTime.hashCode()\n        result = 31 * result + (writeType ?: 0)\n        return result\n    }\n}\n"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/data/Constants.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.data\n\n\n/**\n * 常量\n *\n * @author Buhuiming\n * @date 2023年06月07日 13时52分\n */\nobject Constants {\n\n    const val CONTAIN_SCAN_DEVICE_NAME = false\n\n    const val AUTO_CONNECT = false\n\n    const val ENABLE_LOG = true\n\n    const val DEFAULT_SCAN_MILLIS_TIMEOUT: Long = 10000\n\n    const val DEFAULT_SCAN_RETRY_COUNT: Int = 0\n\n    const val DEFAULT_SCAN_RETRY_INTERVAL: Long = 1000\n\n    const val DEFAULT_CONNECT_MILLIS_TIMEOUT: Long = 10000\n\n    const val DEFAULT_CONNECT_RETRY_COUNT: Int = 0\n\n    const val DEFAULT_CONNECT_RETRY_INTERVAL: Long = 1000\n\n    const val DEFAULT_OPERATE_MILLIS_TIMEOUT: Long = 10000\n\n    const val DEFAULT_OPERATE_INTERVAL: Long = 100\n\n    const val DEFAULT_MAX_CONNECT_NUM: Int = 7\n\n    const val DEFAULT_MTU: Int = 23\n\n    const val DEFAULT_AUTO_SET_MTU = false\n\n    val DEFAULT_TASK_QUEUE_TYPE = BleTaskQueueType.Default\n\n    const val STOP_SCAN_WHEN_START_CONNECT = true\n\n    //系统提供接受通知自带的UUID\n    const val UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR =\n        \"00002902-0000-1000-8000-00805f9b34fb\"\n\n    const val NOTIFY_TASK_ID = \"1000\"\n\n    const val INDICATE_TASK_ID = \"1001\"\n\n    const val SET_RSSI_TASK_ID = \"1002\"\n\n    const val SET_MTU_TASK_ID = \"1003\"\n\n    const val READ_TASK_ID = \"1004\"\n\n    const val WRITE_TASK_ID = \"1005\"\n\n    const val MARK = \"#######----> \"\n\n    const val UN_COMPLETE = 0\n\n    const val COMPLETED = 1\n\n    const val CANCEL_UN_COMPLETE = 2\n\n    const val CANCEL_WAIT_JOB_MESSAGE = \"cancelWaitJobMessage\"\n\n    //Descriptor写数据失败\n    const val EXCEPTION_CODE_DESCRIPTOR_FAIL = 100\n\n    //设置通知失败，SetCharacteristicNotificationFail\n    const val EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL = 101\n\n    const val SCAN_NEED_CHECK_GPS = true\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/device/BleConnectedDevice.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.device\n\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCallback\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.bluetooth.BluetoothGattDescriptor\nimport android.util.SparseArray\nimport com.bhm.ble.callback.BleConnectCallback\nimport com.bhm.ble.callback.BleEventCallback\nimport com.bhm.ble.callback.BleIndicateCallback\nimport com.bhm.ble.callback.BleMtuChangedCallback\nimport com.bhm.ble.callback.BleNotifyCallback\nimport com.bhm.ble.callback.BleReadCallback\nimport com.bhm.ble.callback.BleRssiCallback\nimport com.bhm.ble.callback.BleWriteCallback\nimport com.bhm.ble.control.BleTaskQueue\nimport com.bhm.ble.data.BleDescriptorGetType\nimport com.bhm.ble.request.BleConnectRequest\nimport com.bhm.ble.request.BleIndicateRequest\nimport com.bhm.ble.request.BleMtuRequest\nimport com.bhm.ble.request.BleNotifyRequest\nimport com.bhm.ble.request.BleReadRequest\nimport com.bhm.ble.request.BleRssiRequest\nimport com.bhm.ble.request.BleSetPriorityRequest\nimport com.bhm.ble.request.BleWriteRequest\n\n\n/**\n * 每个连接设备对应一个BleConnectedDevice对象\n * 每一个BleConnectedDevice对象包含一个请求队列、连接请求、Notify请求、Indicate请求、Rssi请求、mtu请求、\n * 设置优先级请求、读特征值数据请求、写数据请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 11时48分\n */\ninternal class BleConnectedDevice(val bleDevice: BleDevice) : BluetoothGattCallback() {\n\n    private var bleTaskQueue = BleTaskQueue(\"${bleDevice.deviceAddress}共享队列\")\n\n    private var bleConnectRequest: BleConnectRequest? = null\n\n    private var bleSetPriorityRequest: BleSetPriorityRequest? = null\n\n    private var bleRssiRequest: BleRssiRequest? = null\n\n    private var bleMtuRequest: BleMtuRequest? = null\n\n    private var bleNotifyRequest: BleNotifyRequest? = null\n\n    private var bleIndicateRequest: BleIndicateRequest? = null\n\n    private var bleReadRequest: BleReadRequest? = null\n\n    private var bleWriteRequest: BleWriteRequest? = null\n\n    private var bleEventCallback: BleEventCallback? = null\n\n    private fun initBleConnectRequest() {\n        if (bleConnectRequest == null) {\n            bleConnectRequest = BleConnectRequest(bleDevice, this)\n        }\n    }\n\n    private fun initBleSetPriorityRequest() {\n        if (bleSetPriorityRequest == null) {\n            bleSetPriorityRequest = BleSetPriorityRequest(bleDevice)\n        }\n    }\n\n    private fun initBleRssiRequest() {\n        if (bleRssiRequest == null) {\n            bleRssiRequest = BleRssiRequest(bleDevice)\n        }\n    }\n\n    private fun initBleMtuRequest() {\n        if (bleMtuRequest == null) {\n            bleMtuRequest = BleMtuRequest(bleDevice, bleTaskQueue)\n        }\n    }\n\n    private fun initBleNotifyRequest() {\n        if (bleNotifyRequest == null) {\n            bleNotifyRequest = BleNotifyRequest(bleDevice)\n        }\n    }\n\n    private fun initBleIndicateRequest() {\n        if (bleIndicateRequest == null) {\n            bleIndicateRequest = BleIndicateRequest(bleDevice)\n        }\n    }\n\n    private fun initBleReadRequest() {\n        if (bleReadRequest == null) {\n            bleReadRequest = BleReadRequest(bleDevice)\n        }\n    }\n\n    private fun initBleWriteRequest() {\n        if (bleWriteRequest == null) {\n            bleWriteRequest = BleWriteRequest(bleDevice)\n        }\n    }\n\n    /**\n     * 当连接上设备或者失去连接时会触发\n     */\n    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {\n        super.onConnectionStateChange(gatt, status, newState)\n        bleConnectRequest?.onConnectionStateChange(gatt, status, newState)\n    }\n\n    /**\n     * 当设备是否找到服务[bluetoothGatt?.discoverServices()]时会触发\n     */\n    override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {\n        super.onServicesDiscovered(gatt, status)\n        bleConnectRequest?.onServicesDiscovered(gatt, status)\n    }\n\n    /**\n     * 设备发出通知时会时会触发\n     */\n    override fun onCharacteristicChanged(\n        gatt: BluetoothGatt,\n        characteristic: BluetoothGattCharacteristic,\n        value: ByteArray\n    ) {\n        /*android 13调用的方法*/\n//        super.onCharacteristicChanged(gatt, characteristic, value)\n        val properties = characteristic.properties\n        if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {\n            // 这是 Indicate\n            bleIndicateRequest?.onCharacteristicChanged(characteristic, value)\n            bleEventCallback?.callCharacteristicChanged(characteristic.uuid?.toString(), 2, bleDevice, value)\n        } else if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {\n            // 这是 Notify\n            bleNotifyRequest?.onCharacteristicChanged(characteristic, value)\n            bleEventCallback?.callCharacteristicChanged(characteristic.uuid?.toString(), 1, bleDevice,  value)\n        }\n    }\n\n    @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\")\n    override fun onCharacteristicChanged(\n        gatt: BluetoothGatt?,\n        characteristic: BluetoothGattCharacteristic?\n    ) {\n        super.onCharacteristicChanged(gatt, characteristic)\n        /*android 13过时的方法*/\n//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n//            return\n//        }\n        characteristic?.let {\n            val properties = characteristic.properties\n            if (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) {\n                // 这是 Indicate\n                bleIndicateRequest?.onCharacteristicChanged(it, it.value)\n                bleEventCallback?.callCharacteristicChanged(it.uuid?.toString(), 2, bleDevice, it.value)\n            } else if (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) {\n                // 这是 Notify\n                bleNotifyRequest?.onCharacteristicChanged(it, it.value)\n                bleEventCallback?.callCharacteristicChanged(it.uuid?.toString(), 1, bleDevice, it.value)\n            } else {\n\n            }\n        }\n    }\n\n    /**\n     * 当向设备Descriptor中写数据时会触发\n     */\n    override fun onDescriptorWrite(\n        gatt: BluetoothGatt?,\n        descriptor: BluetoothGattDescriptor?,\n        status: Int\n    ) {\n        super.onDescriptorWrite(gatt, descriptor, status)\n        bleNotifyRequest?.onDescriptorWrite(descriptor, status)\n        bleIndicateRequest?.onDescriptorWrite(descriptor, status)\n    }\n\n    /**\n     * 当读取设备数据时会触发\n     */\n    override fun onCharacteristicRead(\n        gatt: BluetoothGatt,\n        characteristic: BluetoothGattCharacteristic,\n        value: ByteArray,\n        status: Int\n    ) {\n        //必须注释掉，否则会导致bleReadRequest?.onCharacteristicRead(characteristic, value, status)方法调用两次\n//        super.onCharacteristicRead(gatt, characteristic, value, status)\n        bleReadRequest?.onCharacteristicRead(characteristic, value, status)\n    }\n\n    /**\n     * 兼容老的android系统版本，特别是华为(鸿蒙)系统个别版本会调用这个方法，而不触发[onCharacteristicRead]方法\n     */\n    @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\")\n    override fun onCharacteristicRead(\n        gatt: BluetoothGatt?,\n        characteristic: BluetoothGattCharacteristic?,\n        status: Int\n    ) {\n        super.onCharacteristicRead(gatt, characteristic, status)\n        characteristic?.let {\n            bleReadRequest?.onCharacteristicRead(characteristic, characteristic.value?: byteArrayOf(), status)\n        }\n    }\n\n    /**\n     * 当向Characteristic写数据时会触发\n     */\n    override fun onCharacteristicWrite(\n        gatt: BluetoothGatt?,\n        characteristic: BluetoothGattCharacteristic?,\n        status: Int\n    ) {\n        super.onCharacteristicWrite(gatt, characteristic, status)\n        bleWriteRequest?.onCharacteristicWrite(characteristic, status)\n    }\n\n    /**\n     * 读取信号值后会触发\n     */\n    override fun onReadRemoteRssi(gatt: BluetoothGatt?, rssi: Int, status: Int) {\n        super.onReadRemoteRssi(gatt, rssi, status)\n        bleRssiRequest?.onReadRemoteRssi(rssi, status)\n    }\n\n    /**\n     * 设置Mtu值后会触发\n     */\n    override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {\n        super.onMtuChanged(gatt, mtu, status)\n        bleMtuRequest?.onMtuChanged(mtu, status)\n        bleEventCallback?.callMtuChanged(mtu, bleDevice)\n    }\n\n    /**\n     * 连接设备\n     */\n    fun connect(\n        connectMillisTimeOut: Long?,\n        connectRetryCount: Int?,\n        connectRetryInterval: Long?,\n        isForceConnect: Boolean = false,\n        bleConnectCallback: BleConnectCallback\n    ) {\n        initBleConnectRequest()\n        bleConnectRequest?.connect(\n            connectMillisTimeOut,\n            connectRetryCount,\n            connectRetryInterval,\n            isForceConnect,\n            bleConnectCallback\n        )\n    }\n\n    /**\n     * 主动断开连接，上层API调用\n     */\n    fun disConnect() {\n        bleConnectRequest?.disConnect()\n    }\n\n    /**\n     * 取消/停止连接\n     */\n    fun stopConnect() {\n        bleConnectRequest?.stopConnect()\n    }\n\n    /**\n     * 获取设备的BluetoothGatt对象\n     */\n    fun getBluetoothGatt(): BluetoothGatt? {\n        initBleConnectRequest()\n        return bleConnectRequest?.getBluetoothGatt()\n    }\n\n    /**\n     * notify\n     */\n    @Synchronized\n    fun enableCharacteristicNotify(serviceUUID: String,\n                                   notifyUUID: String,\n                                   timeoutMillis: Long?,\n                                   bleDescriptorGetType: BleDescriptorGetType,\n                                   bleNotifyCallback: BleNotifyCallback\n    ) {\n        initBleNotifyRequest()\n        bleNotifyRequest?.enableCharacteristicNotify(\n            serviceUUID,\n            notifyUUID,\n            timeoutMillis,\n            bleDescriptorGetType,\n            bleNotifyCallback\n        )\n    }\n\n    /**\n     * stop notify\n     */\n    @Synchronized\n    fun disableCharacteristicNotify(serviceUUID: String,\n                                    notifyUUID: String,\n                                    bleDescriptorGetType: BleDescriptorGetType\n    ): Boolean {\n        initBleNotifyRequest()\n        return bleNotifyRequest?.disableCharacteristicNotify(\n            serviceUUID,\n            notifyUUID,\n            bleDescriptorGetType\n        )?: false\n    }\n\n    /**\n     * indicate\n     */\n    @Synchronized\n    fun enableCharacteristicIndicate(serviceUUID: String,\n                                     indicateUUID: String,\n                                     timeoutMillis: Long?,\n                                     bleDescriptorGetType: BleDescriptorGetType,\n                                     bleIndicateCallback: BleIndicateCallback\n    ) {\n        initBleIndicateRequest()\n        bleIndicateRequest?.enableCharacteristicIndicate(\n            serviceUUID,\n            indicateUUID,\n            timeoutMillis,\n            bleDescriptorGetType,\n            bleIndicateCallback\n        )\n    }\n\n    /**\n     * stop indicate\n     */\n    @Synchronized\n    fun disableCharacteristicIndicate(serviceUUID: String,\n                                      indicateUUID: String,\n                                      bleDescriptorGetType: BleDescriptorGetType\n    ): Boolean {\n        initBleIndicateRequest()\n        return bleIndicateRequest?.disableCharacteristicIndicate(\n            serviceUUID,\n            indicateUUID,\n            bleDescriptorGetType\n        )?: false\n    }\n\n    /**\n     * 读取信号值\n     */\n    @Synchronized\n    fun readRemoteRssi(bleRssiCallback: BleRssiCallback) {\n        initBleRssiRequest()\n        bleRssiRequest?.readRemoteRssi(bleRssiCallback)\n    }\n\n    /**\n     * 设置mtu\n     */\n    @Synchronized\n    fun setMtu(mtu: Int, bleMtuChangedCallback: BleMtuChangedCallback) {\n        initBleMtuRequest()\n        bleMtuRequest?.setMtu(mtu, bleMtuChangedCallback)\n    }\n\n    /**\n     * 设置设备的传输优先级\n     * connectionPriority 必须是 [BluetoothGatt.CONNECTION_PRIORITY_BALANCED]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER]的其中一个\n     *\n     */\n    @Synchronized\n    fun setConnectionPriority(connectionPriority: Int): Boolean {\n        initBleSetPriorityRequest()\n        return bleSetPriorityRequest?.setConnectionPriority(connectionPriority)?: false\n    }\n\n    /**\n     * 读特征值数据\n     */\n    @Synchronized\n    fun readData(serviceUUID: String,\n                 readUUID: String,\n                 bleIndicateCallback: BleReadCallback) {\n        initBleReadRequest()\n        bleReadRequest?.readCharacteristic(serviceUUID, readUUID, bleIndicateCallback)\n    }\n\n    /**\n     * 写数据\n     */\n    @Synchronized\n    fun writeData(serviceUUID: String,\n                  writeUUID: String,\n                  dataArray: SparseArray<ByteArray>,\n                  writeType: Int?,\n                  bleWriteCallback: BleWriteCallback) {\n        //以时间戳为id，来标记一次写操作\n        initBleWriteRequest()\n        bleWriteRequest?.writeData(\n            serviceUUID,\n            writeUUID,\n            System.currentTimeMillis().toString(),\n            dataArray,\n            writeType,\n            bleWriteCallback\n        )\n    }\n\n    /**\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     * 与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     */\n    @Synchronized\n    fun writeQueueData(serviceUUID: String,\n                       writeUUID: String,\n                       dataArray: SparseArray<ByteArray>,\n                       skipErrorPacketData: Boolean = false,\n                       retryWriteCount: Int = 0,\n                       retryDelayTime: Long = 0L,\n                       writeType: Int? = null,\n                       bleWriteCallback: BleWriteCallback) {\n        //以时间戳为id，来标记一次写操作\n        initBleWriteRequest()\n        bleWriteRequest?.writeQueueData(\n            serviceUUID,\n            writeUUID,\n            System.currentTimeMillis().toString(),\n            dataArray,\n            skipErrorPacketData,\n            retryWriteCount,\n            retryDelayTime,\n            writeType,\n            bleWriteCallback\n        )\n    }\n\n    fun cancelWriting() {\n        bleWriteRequest?.cancelWriteQueueJob()\n    }\n\n    fun getShareBleTaskQueue() = bleTaskQueue\n\n    @Synchronized\n    fun addBleEventCallback(bleEventCallback: BleEventCallback) {\n        this.bleEventCallback = bleEventCallback\n    }\n\n    fun getBleEventCallback() = bleEventCallback\n\n    @Synchronized\n    fun removeNotifyCallback(uuid: String?) {\n        bleNotifyRequest?.removeNotifyCallback(uuid)\n    }\n\n    @Synchronized\n    fun removeIndicateCallback(uuid: String?) {\n        bleIndicateRequest?.removeIndicateCallback(uuid)\n    }\n\n    @Synchronized\n    fun removeWriteCallback(uuid: String?, bleWriteCallback: BleWriteCallback? = null) {\n        bleWriteRequest?.removeWriteCallback(uuid, bleWriteCallback)\n    }\n\n    @Synchronized\n    fun removeReadCallback(uuid: String?) {\n        bleReadRequest?.removeReadCallback(uuid)\n    }\n\n    @Synchronized\n    fun removeRssiCallback() {\n        bleRssiRequest?.removeRssiCallback()\n    }\n\n    @Synchronized\n    fun removeMtuChangedCallback() {\n        bleMtuRequest?.removeMtuChangedCallback()\n    }\n\n    @Synchronized\n    fun removeBleConnectCallback() {\n        bleConnectRequest?.removeBleConnectCallback()\n    }\n\n    fun removeBleEventCallback() {\n        bleEventCallback = null\n    }\n\n    @Synchronized\n    fun replaceBleConnectCallback(bleConnectCallback: BleConnectCallback) {\n        bleConnectRequest?.removeBleConnectCallback()\n        bleConnectRequest?.addBleConnectCallback(bleConnectCallback)\n    }\n\n    @Synchronized\n    fun removeAllCharacterCallback() {\n        removeRssiCallback()\n        removeMtuChangedCallback()\n        clearCharacterCallback()\n    }\n\n    private fun clearCharacterCallback() {\n        bleNotifyRequest?.removeAllNotifyCallback()\n        bleIndicateRequest?.removeAllIndicateCallback()\n        bleWriteRequest?.removeAllWriteCallback()\n        bleReadRequest?.removeAllReadCallback()\n        bleEventCallback = null\n    }\n\n    fun close() {\n        bleNotifyRequest?.close()\n        bleIndicateRequest?.close()\n        bleReadRequest?.close()\n        bleWriteRequest?.close()\n        bleRssiRequest?.close()\n        bleConnectRequest?.close()\n        bleTaskQueue.clear()\n        bleNotifyRequest = null\n        bleIndicateRequest = null\n        bleReadRequest = null\n        bleWriteRequest = null\n        bleRssiRequest = null\n        bleConnectRequest = null\n        bleEventCallback = null\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/device/BleConnectedDeviceManager.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"SENSELESS_COMPARISON\")\n\npackage com.bhm.ble.device\n\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.control.BleLruHashMap\nimport com.bhm.ble.data.Constants.DEFAULT_MAX_CONNECT_NUM\n\n\n/**\n * 连接设备BleConnectedDevice管理池\n *\n * @author Buhuiming\n * @date 2023年05月26日 08时54分\n */\ninternal class BleConnectedDeviceManager private constructor() {\n\n    private val bleLruHashMap: BleLruHashMap =\n        BleLruHashMap(BleManager.get().getOptions()?.maxConnectNum\n            ?: DEFAULT_MAX_CONNECT_NUM)\n\n    companion object {\n\n        private var instance: BleConnectedDeviceManager = BleConnectedDeviceManager()\n\n        fun get(): BleConnectedDeviceManager {\n            if (instance == null) {\n                instance = BleConnectedDeviceManager()\n            }\n            return instance\n        }\n    }\n\n    /**\n     * 添加设备控制器\n     */\n    fun buildBleConnectedDevice(bleDevice: BleDevice): BleConnectedDevice? {\n        if (bleLruHashMap.containsKey(bleDevice.getKey())) {\n            return bleLruHashMap[bleDevice.getKey()]\n        }\n        val bleConnectedDevice = BleConnectedDevice(bleDevice)\n        bleLruHashMap[bleDevice.getKey()] = bleConnectedDevice\n        return bleConnectedDevice\n    }\n\n    /**\n     * 获取设备控制器\n     */\n    fun getBleConnectedDevice(bleDevice: BleDevice): BleConnectedDevice? {\n        if (bleLruHashMap.containsKey(bleDevice.getKey())) {\n            return bleLruHashMap[bleDevice.getKey()]\n        }\n        return null\n    }\n\n    /**\n     * 移除设备控制器\n     */\n    fun removeBleConnectedDevice(key: String) {\n        if (bleLruHashMap.containsKey(key)) {\n            bleLruHashMap.remove(key)\n        }\n    }\n\n    /**\n     * 是否存在该设备\n     */\n    fun isContainDevice(bleDevice: BleDevice): Boolean {\n        return bleLruHashMap.containsKey(bleDevice.getKey())\n    }\n\n    /**\n     * 获取所有已连接设备集合\n     */\n    fun getAllConnectedDevice(): MutableList<BleDevice> {\n        val list = mutableListOf<BleDevice>()\n        bleLruHashMap.forEach {\n            it.value?.let { device ->\n                if (BleManager.get().isConnected(device.bleDevice)) {\n                    list.add(device.bleDevice)\n                }\n            }\n        }\n        return list\n    }\n\n    /**\n     * 断开某个设备的连接 释放资源\n     */\n    fun close(bleDevice: BleDevice) {\n        getBleConnectedDevice(bleDevice)?.close()\n        bleLruHashMap.remove(bleDevice.getKey())\n    }\n\n    /**\n     * 断开所有设备的连接\n     */\n    fun disConnectAll() {\n        bleLruHashMap.values.forEach {\n            it?.disConnect()\n\n        }\n        closeAll()\n    }\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    fun closeAll() {\n        bleLruHashMap.values.forEach {\n            it?.close()\n        }\n        bleLruHashMap.clear()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/device/BleDevice.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"DEPRECATION\")\n\npackage com.bhm.ble.device\n\nimport android.bluetooth.BluetoothDevice\nimport android.os.Bundle\nimport android.os.Parcel\nimport android.os.ParcelUuid\nimport android.os.Parcelable\n\n\n/**\n * BleDevice信息\n * @param deviceInfo 设备信息\n * @param deviceName 蓝牙广播名\n * @param deviceAddress 蓝牙Mac地址\n * @param rssi 被扫描到时候的信号强度\n * @param timestampNanos 当扫描记录被观察到时，返回自启动以来的时间戳。\n * @param scanRecord 被扫描到时候携带的广播数据\n * @param tag 预留字段\n *\n * @author Buhuiming\n * @date 2023年05月22日 09时11分\n */\ndata class BleDevice(\n    val deviceInfo: BluetoothDevice?,\n    val deviceName: String?,\n    val deviceAddress: String?,\n    val rssi: Int?,\n    val timestampNanos: Long?,\n    val scanRecord: ByteArray?,\n    val tag: Bundle?,\n    val serviceUuids: List<ParcelUuid>? = null\n) : Parcelable {\n\n    constructor(parcel: Parcel) : this(\n        parcel.readParcelable(BluetoothDevice::class.java.classLoader),\n        parcel.readString(),\n        parcel.readString(),\n        parcel.readValue(Int::class.java.classLoader) as? Int,\n        parcel.readValue(Long::class.java.classLoader) as? Long,\n        parcel.createByteArray(),\n        parcel.readBundle(Bundle::class.java.classLoader),\n        parcel.createTypedArrayList(ParcelUuid.CREATOR)\n    )\n\n    override fun writeToParcel(parcel: Parcel, flags: Int) {\n        parcel.writeParcelable(deviceInfo, flags)\n        parcel.writeString(deviceName)\n        parcel.writeString(deviceAddress)\n        parcel.writeValue(rssi)\n        parcel.writeValue(timestampNanos)\n        parcel.writeByteArray(scanRecord)\n        parcel.writeBundle(tag)\n        parcel.writeTypedList(serviceUuids)\n    }\n\n    override fun describeContents(): Int {\n        return 0\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as BleDevice\n\n        if (deviceName != other.deviceName) return false\n        if (deviceAddress != other.deviceAddress) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = deviceName?.hashCode() ?: 0\n        result = 31 * result + (deviceAddress?.hashCode() ?: 0)\n        return result\n    }\n\n    companion object CREATOR : Parcelable.Creator<BleDevice> {\n        override fun createFromParcel(parcel: Parcel): BleDevice {\n            return BleDevice(parcel)\n        }\n\n        override fun newArray(size: Int): Array<BleDevice?> {\n            return arrayOfNulls(size)\n        }\n    }\n\n    fun getKey() = deviceAddress.toString()\n\n    fun getFirstServiceUuid() = serviceUuids?.firstOrNull()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/log/BleLogEvent.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.log\n\n/**\n * @description 日志事件接口\n * @author Buhuiming\n * @date 2024/05/08/ 10:27\n */\ninterface BleLogEvent {\n    /**\n     * 注意该方法是否是线程安全的(有可能在子线程中调用)\n     */\n    fun onLog(level: BleLogLevel, tag: String, message: String?)\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/log/BleLogLevel.kt",
    "content": "package com.bhm.ble.log\n\n/**\n * @description 日志级别\n * @author Buhuiming\n * @date 2024/05/08/ 10:28\n */\nsealed class BleLogLevel {\n    object Debug : BleLogLevel()\n    object Info : BleLogLevel()\n    object Warn : BleLogLevel()\n    object Error : BleLogLevel()\n}\n"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/log/BleLogManager.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"SENSELESS_COMPARISON\")\n\npackage com.bhm.ble.log\n\nimport java.util.concurrent.ConcurrentLinkedQueue\n\n/**\n * @description 日志管理器\n * @author Buhuiming\n * @date 2024/05/08/ 10:30\n */\nclass BleLogManager private constructor() {\n\n    companion object {\n\n        private var instance: BleLogManager = BleLogManager()\n\n        private val listenerList: ConcurrentLinkedQueue<BleLogEvent> = ConcurrentLinkedQueue()\n\n        @Synchronized\n        fun get(): BleLogManager {\n            if (instance == null) {\n                instance = BleLogManager()\n            }\n            return instance\n        }\n    }\n\n    fun addLogListener(listener: BleLogEvent) {\n        listenerList.add(listener)\n    }\n\n    fun removeLogListener(listener: BleLogEvent) {\n        listenerList.remove(listener)\n    }\n\n    fun notifyLog(level: BleLogLevel, tag: String, message: String?) {\n        for (listener in listenerList) {\n            listener.onLog(level, tag, message)\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/log/BleLogger.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.log\n\nimport android.util.Log\nimport com.bhm.ble.data.Constants.MARK\n\n\n/**\n * 日志工具\n *\n * @author Buhuiming\n * @date 2023年05月19日 10时46分\n */\nobject BleLogger {\n\n    var isLogger = true\n\n    fun d(msg: String?) {\n        if (msg.isNullOrEmpty()) {\n            return\n        }\n        val tag = getClassNameForTag()\n        if (isLogger) {\n            Log.d(tag, MARK + msg)\n        }\n        notifyLog(BleLogLevel.Debug, tag, MARK + msg)\n    }\n\n    fun i(msg: String?) {\n        if (msg.isNullOrEmpty()) {\n            return\n        }\n        val tag = getClassNameForTag()\n        if (isLogger) {\n            Log.i(tag, MARK + msg)\n        }\n        notifyLog(BleLogLevel.Info, tag, MARK + msg)\n    }\n\n    fun e(msg: String?) {\n        if (msg.isNullOrEmpty()) {\n            return\n        }\n        val tag = getClassNameForTag()\n        if (isLogger) {\n            Log.e(tag, MARK + msg)\n        }\n        notifyLog(BleLogLevel.Error, tag, MARK + msg)\n    }\n\n    fun w(msg: String?) {\n        if (msg.isNullOrEmpty()) {\n            return\n        }\n        val tag = getClassNameForTag()\n        if (isLogger) {\n            Log.w(tag, MARK + msg)\n        }\n        notifyLog(BleLogLevel.Warn, tag, MARK + msg)\n    }\n\n    private fun notifyLog(logLevel: BleLogLevel, tag: String, message: String?) {\n        BleLogManager.get().notifyLog(logLevel, tag, message)\n    }\n\n    /*\n    * 获取调用方法的类名\n    */\n    private fun getClassNameForTag(): String {\n        // 获取调用的堆栈信息\n        val stackTraces = Throwable().stackTrace\n        for (stackTrace in stackTraces) {\n            // 获取类的全路径\n            val className = stackTrace.className\n            try {\n                val clazz = Class.forName(className)\n                if (this.javaClass != clazz) {\n                    return clazz.simpleName.ifEmpty { BleLogger.javaClass.simpleName }\n                }\n            } catch (e: ClassNotFoundException) {\n                e.printStackTrace()\n            }\n        }\n        return BleLogger.javaClass.simpleName\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/receiver/BluetoothReceiver.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.receiver\n\nimport android.bluetooth.BluetoothAdapter\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport com.bhm.ble.callback.BluetoothCallback\nimport com.bhm.ble.log.BleLogger\n\n/**\n * @description 注册蓝牙广播\n * @author Buhuiming\n * @date 2023/12/29/ 15:07\n */\nopen class BluetoothReceiver : BroadcastReceiver() {\n\n    private var bluetoothCallback: BluetoothCallback? = null\n\n    override fun onReceive(context: Context?, intent: Intent?) {\n        val action = intent?.action\n        if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {\n            when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {\n                BluetoothAdapter.STATE_OFF -> {\n                    BleLogger.i(\"系统蓝牙关闭了!\")\n                    bluetoothCallback?.callStateOff()\n                }\n                BluetoothAdapter.STATE_TURNING_OFF -> {\n                    BleLogger.i(\"系统蓝牙关闭中...\")\n                    bluetoothCallback?.callStateTurningOff()\n                }\n                BluetoothAdapter.STATE_ON -> {\n                    BleLogger.i(\"系统蓝牙打开了\")\n                    bluetoothCallback?.callStateOn()\n                }\n                BluetoothAdapter.STATE_TURNING_ON -> {\n                    BleLogger.i(\"系统蓝牙打开中...\")\n                    bluetoothCallback?.callStateTurningOn()\n                }\n            }\n        }\n    }\n\n    fun setBluetoothCallback(bluetoothCallback: BluetoothCallback?) {\n        this.bluetoothCallback = bluetoothCallback\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleConnectRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothDevice\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCallback\nimport android.bluetooth.BluetoothProfile\nimport com.bhm.ble.callback.BleConnectCallback\nimport com.bhm.ble.callback.BleMtuChangedCallback\nimport com.bhm.ble.data.ActiveDisConnectedException\nimport com.bhm.ble.data.ActiveStopConnectedException\nimport com.bhm.ble.data.BleConnectFailType\nimport com.bhm.ble.data.BleConnectLastState\nimport com.bhm.ble.data.CompleteException\nimport com.bhm.ble.data.Constants.AUTO_CONNECT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_CONNECT_RETRY_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_INTERVAL\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.device.BleConnectedDeviceManager\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.request.base.BleRequestImp\nimport com.bhm.ble.request.base.Request\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withTimeout\nimport java.util.concurrent.atomic.AtomicBoolean\n\n\n/**\n * ble连接、断开连接请求\n *\n * @author Buhuiming\n * @date 2023年05月24日 14时10分\n */\n@SuppressLint(\"MissingPermission\")\ninternal class BleConnectRequest(\n    private val bleDevice: BleDevice,\n    private val coreGattCallback: BluetoothGattCallback,\n) : Request() {\n\n    private var bleConnectCallback: BleConnectCallback? = null\n\n    private var lastState: BleConnectLastState? = null\n\n    private var isActiveDisconnect = AtomicBoolean(false)\n\n    private var bluetoothGatt: BluetoothGatt? = null\n\n    private var currentConnectRetryCount = 0\n\n    private var connectJob: Job? = null\n\n    private var waitConnectJob: Job? = null\n\n    private val autoConnect = getBleOptions()?.autoConnect?: AUTO_CONNECT\n\n    private val waitTime = getBleOptions()?.operateInterval?: DEFAULT_OPERATE_INTERVAL\n\n    private var connectMillisTimeOut: Long? = null\n\n    private var connectRetryCount: Int? = null\n\n    private var connectRetryInterval: Long? = null\n\n    /**\n     * 连接设备\n     */\n    fun connect(\n        connectMillisTimeOut: Long?,\n        connectRetryCount: Int?,\n        connectRetryInterval: Long?,\n        isForceConnect: Boolean = false,\n        bleConnectCallback: BleConnectCallback\n    ) {\n        addBleConnectCallback(bleConnectCallback)\n        if (bleDevice.deviceInfo == null) {\n            BleLogger.e(\"连接失败：BluetoothDevice为空\")\n            removeBleConnectedDevice()\n            bleConnectCallback.callConnectFail(createNewDeviceInfo(), BleConnectFailType.NullableBluetoothDevice)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.NullableBluetoothDevice\n            )\n            return\n        }\n        val bleManager = getBleManager()\n        if (!BleUtil.isPermission(bleManager.getContext()?.applicationContext)) {\n            BleLogger.e(\"权限不足，请检查\")\n            removeBleConnectedDevice()\n            bleConnectCallback.callConnectFail(createNewDeviceInfo(), BleConnectFailType.NoBlePermission)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.NoBlePermission\n            )\n            return\n        }\n        if (!bleManager.isBleSupport()) {\n            BleLogger.e(\"设备不支持蓝牙\")\n            removeBleConnectedDevice()\n            bleConnectCallback.callConnectFail(createNewDeviceInfo(), BleConnectFailType.UnSupportBle)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.UnSupportBle\n            )\n            return\n        }\n        if (!bleManager.isBleEnable()) {\n            BleLogger.e(\"蓝牙未打开\")\n            removeBleConnectedDevice()\n            bleConnectCallback.callConnectFail(createNewDeviceInfo(), BleConnectFailType.BleDisable)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.BleDisable\n            )\n            return\n        }\n        if (lastState == BleConnectLastState.Connecting || lastState == BleConnectLastState.ConnectIdle) {\n            BleLogger.e(\"${bleDevice.deviceAddress}连接中\")\n//            removeBleConnectedDevice()\n            bleConnectCallback.callConnectFail(createNewDeviceInfo(), BleConnectFailType.AlreadyConnecting)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.AlreadyConnecting\n            )\n            return\n        }\n        this.connectMillisTimeOut = connectMillisTimeOut\n        this.connectRetryCount = connectRetryCount\n        this.connectRetryInterval = connectRetryInterval\n        //主要针对某些机型，当触发连接超时回调连接失败并释放资源之后，此时外设开启触发手机系统已连接，但BleCore资源被释放\n        // (bluetoothGatt是null)，或BleCore和系统的连接状态不一致，而导致setMtu和Notify/Indicate都失败。\n        val systemConnectStatus = bleManager.isConnected(bleDevice, true)\n        val bleCoreConnectStatus = bleManager.isConnected(bleDevice, false)\n        BleLogger.e(\"设备[${bleDevice.deviceAddress}]当前连接状态，系统已连接$systemConnectStatus，\" +\n                \"BleCore连接状态$bleCoreConnectStatus，\" +\n                \" 是否强制连接$isForceConnect， \" +\n                \"bluetoothGatt是否为空${bluetoothGatt == null}\")\n        //如果BleCore或者系统对应的状态是未连接、或者强制连接的情况\n        if (!systemConnectStatus || !bleCoreConnectStatus || isForceConnect || bluetoothGatt == null) {\n            val deviceInfo = createNewDeviceInfo()\n            bleConnectCallback.callConnectStart(deviceInfo)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectStart(deviceInfo)\n            startConnectJob()\n        } else {\n            //如果BleCore和系统对应的状态都是已连接，则直接返回状态\n            lastState = BleConnectLastState.Connected\n            val deviceInfo = createNewDeviceInfo()\n            addBleConnectedDevice()\n            bleConnectCallback.callConnectSuccess(deviceInfo, bluetoothGatt)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()\n                ?.callConnected(deviceInfo, bluetoothGatt)\n            autoSetMtu()\n        }\n    }\n\n    /**\n     * 主动断开连接，上层API调用\n     */\n    fun disConnect() {\n        if (bleDevice.deviceInfo == null) {\n            BleLogger.e(\"[${bleDevice.deviceAddress}]断开失败：BluetoothDevice为空\")\n            bleConnectCallback?.callConnectFail(createNewDeviceInfo(), BleConnectFailType.NullableBluetoothDevice)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.NullableBluetoothDevice\n            )\n            return\n        }\n        val bleManager = getBleManager()\n        if (!BleUtil.isPermission(bleManager.getContext()?.applicationContext)) {\n            BleLogger.e(\"权限不足，请检查\")\n            bleConnectCallback?.callConnectFail(createNewDeviceInfo(), BleConnectFailType.NoBlePermission)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.NoBlePermission\n            )\n            return\n        }\n        if (!bleManager.isBleSupport()) {\n            BleLogger.e(\"设备不支持蓝牙\")\n            bleConnectCallback?.callConnectFail(createNewDeviceInfo(), BleConnectFailType.UnSupportBle)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.UnSupportBle\n            )\n            return\n        }\n        if (!bleManager.isBleEnable()) {\n            BleLogger.e(\"蓝牙未打开\")\n            bleConnectCallback?.callConnectFail(createNewDeviceInfo(), BleConnectFailType.BleDisable)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(), BleConnectFailType.BleDisable\n            )\n            return\n        }\n        isActiveDisconnect.set(true)\n        lastState = BleConnectLastState.Disconnect\n        if (lastState == BleConnectLastState.ConnectIdle ||\n            lastState == BleConnectLastState.Connecting) {\n            val throwable = ActiveDisConnectedException(\"连接过程中断开\")\n            connectJob?.cancel(throwable)\n            waitConnectJob?.cancel(throwable)\n        } else {\n            disConnectGatt()\n            BleLogger.e(\"${bleDevice.deviceAddress} -> 主动断开连接\")\n            val deviceInfo = createNewDeviceInfo()\n            bleConnectCallback?.callDisConnecting(\n                isActiveDisconnect.get(),\n                deviceInfo, bluetoothGatt, BluetoothGatt.GATT_SUCCESS\n            )\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callDisConnecting(\n                isActiveDisconnect.get(),\n                deviceInfo, bluetoothGatt, BluetoothGatt.GATT_SUCCESS\n            )\n            BleRequestImp.get().getMainScope().launch {\n                delay(600)\n                removeAllCallback()\n            }\n        }\n    }\n\n    /**\n     * 取消/停止连接\n     */\n    fun stopConnect() {\n        if (lastState == BleConnectLastState.ConnectIdle ||\n            lastState == BleConnectLastState.Connecting) {\n            val throwable = ActiveStopConnectedException(\"[${bleDevice.deviceAddress}]连接过程中取消/停止连接\")\n            connectJob?.cancel(throwable)\n            waitConnectJob?.cancel(throwable)\n        } else {\n            BleLogger.i(\"[${bleDevice.deviceAddress}]非连接过程中，取消/停止连接无效\")\n        }\n    }\n\n    /**\n     * 当连接上设备或者失去连接时会触发\n     */\n    fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {\n        BleLogger.i(\"onConnectionStateChange： status = $status \" +\n                \", newState = $newState , currentThread = ${Thread.currentThread().name} \" +\n                \", bleAddress = ${bleDevice.deviceAddress} , lastState = $lastState\")\n        if (lastState == BleConnectLastState.ConnectFailure) {\n            //上一个状态为null即调用了close，就不再处理，这里出现有些手机，调用了close之后，还会触发onConnectionStateChange\n            disConnectGatt()\n            refreshDeviceCache()\n            closeBluetoothGatt()\n            return\n        }\n        BleLogger.i(\"[${bleDevice.deviceAddress}]连接状态变化BluetoothGatt是否为空：${gatt == null}\")\n        gatt?.let {\n            bluetoothGatt = it\n        }\n        if (newState == BluetoothProfile.STATE_CONNECTED) {\n            //连接成功\n            if (connectJob?.isActive == true || waitConnectJob?.isActive == true) {\n                val throwable = CompleteException()\n                connectJob?.cancel(throwable)\n                waitConnectJob?.cancel(throwable)\n            } else {\n                findService()\n            }\n        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {\n            when (lastState) {\n                BleConnectLastState.ConnectIdle, BleConnectLastState.Connecting -> {\n                    //连接过程中断开，进入判断是否重连\n                    refreshDeviceCache()\n                    closeBluetoothGatt()\n                    lastState = BleConnectLastState.Disconnect\n                    checkIfContinueConnect(UnDefinedException(\"[${bleDevice.deviceAddress}]连接过程中断开\"))\n                }\n                BleConnectLastState.ConnectFailure -> {\n                    BleLogger.i(\"连接失败后，设备[${bleDevice.deviceAddress}]触发断开连接\")\n                    refreshDeviceCache()\n                    closeBluetoothGatt()\n                }\n                //所有断开连接的情况\n                else -> {\n                    refreshDeviceCache()\n                    closeBluetoothGatt()\n                    lastState = BleConnectLastState.Disconnect\n                    if (!isActiveDisconnect.get()) {\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> 自动断开连接\")\n                        val deviceInfo = createNewDeviceInfo()\n                        bleConnectCallback?.callDisConnecting(\n                            isActiveDisconnect.get(),\n                            deviceInfo, gatt, status\n                        )\n                        getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callDisConnecting(\n                            isActiveDisconnect.get(),\n                            deviceInfo, gatt, status\n                        )\n                        BleRequestImp.get().getMainScope().launch {\n                            delay(600)\n                            removeAllCallback()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 当设备是否找到服务[bluetoothGatt?.discoverServices()]时会触发\n     */\n    fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {\n        gatt?.let {\n            bluetoothGatt = it\n        }\n        BleLogger.i(\"[${bleDevice.deviceAddress}]发现服务BluetoothGatt是否为空：${gatt == null}\")\n        if (status == BluetoothGatt.GATT_SUCCESS) {\n            BleLogger.i(\"${bleDevice.deviceAddress} -> 连接成功，发现服务\")\n            currentConnectRetryCount = 0\n            lastState = BleConnectLastState.Connected\n            isActiveDisconnect.set(false)\n            val deviceInfo = createNewDeviceInfo()\n            addBleConnectedDevice()\n            bleConnectCallback?.callConnectSuccess(deviceInfo, bluetoothGatt)\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnected(deviceInfo, bluetoothGatt)\n            autoSetMtu()\n        } else {\n            connectFail()\n            BleLogger.e(\"${bleDevice.deviceAddress} -> 连接失败：未发现服务\")\n            bleConnectCallback?.callConnectFail(\n                createNewDeviceInfo(),\n                BleConnectFailType.ConnectException(UnDefinedException(\"发现服务失败\"))\n            )\n            getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                createNewDeviceInfo(),\n                BleConnectFailType.ConnectException(UnDefinedException(\"发现服务失败\"))\n            )\n        }\n    }\n\n    fun addBleConnectCallback(bleConnectCallback: BleConnectCallback) {\n        this.bleConnectCallback = bleConnectCallback\n    }\n\n    fun removeBleConnectCallback() {\n        bleConnectCallback = null\n    }\n\n    /**\n     * 获取设备的BluetoothGatt对象\n     */\n    fun getBluetoothGatt(): BluetoothGatt? {\n        return bluetoothGatt\n    }\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    fun close() {\n        val deviceInfo = createNewDeviceInfo()\n        bleConnectCallback?.callDisConnecting(\n            isActiveDisconnect.get(),\n            deviceInfo, bluetoothGatt, BluetoothGatt.GATT_SUCCESS\n        )\n        getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callDisConnecting(\n            isActiveDisconnect.get(),\n            deviceInfo, bluetoothGatt, BluetoothGatt.GATT_SUCCESS\n        )\n        lastState = null\n        disConnectGatt()\n        refreshDeviceCache()\n        closeBluetoothGatt()\n        removeBleConnectCallback()\n        removeAllCallback()\n    }\n\n    /**\n     * 开始连接\n     */\n    private fun startConnectJob() {\n        lastState = BleConnectLastState.ConnectIdle\n        isActiveDisconnect.set(false)\n        var connectTime = connectMillisTimeOut?: (getBleOptions()?.connectMillisTimeOut?: DEFAULT_CONNECT_MILLIS_TIMEOUT)\n        if (connectTime <= 0) {\n            connectTime = DEFAULT_CONNECT_MILLIS_TIMEOUT\n        }\n        connectJob = bleConnectCallback?.launchInMainThread {\n            try {\n                withTimeout(connectTime) {\n                    //每次连接之前确保和上一次操作间隔一定时间\n                    delay(waitTime)\n                    lastState = BleConnectLastState.Connecting\n                    bluetoothGatt = bleDevice.deviceInfo?.connectGatt(\n                        getBleManager().getContext(),\n                        autoConnect, coreGattCallback, BluetoothDevice.TRANSPORT_LE\n                    )\n                    BleLogger.i(\"${bleDevice.deviceAddress} -> 开始第${currentConnectRetryCount + 1}次连接\")\n                    if (bluetoothGatt == null) {\n                        cancel(CancellationException(\"连接异常：bluetoothGatt == null\"))\n                    } else {\n                        delay(connectTime + waitTime * 6) //需要加上等待发现服务的时间\n                    }\n                }\n            } catch (e: TimeoutCancellationException) {\n                // 如果是因为主动取消导致的超时，这里替换掉异常\n                if (isActive) {\n                    //如果已经取消的job，则不抛出超时异常\n                    throw e\n                }\n            }\n        }\n        connectJob?.invokeOnCompletion {\n            onCompletion(it)\n        }\n    }\n\n    /**\n     * 处理连接结果，是否重连、或者显示结果\n     */\n    private fun onCompletion(throwable: Throwable?) {\n        if (isContinueConnect(throwable)) {\n            val retryInterval = connectRetryInterval?: (getBleOptions()?.connectRetryInterval?: DEFAULT_CONNECT_RETRY_INTERVAL)\n            waitConnectJob = bleConnectCallback?.launchInMainThread {\n                delay(retryInterval)\n                currentConnectRetryCount ++\n                startConnectJob()\n            }\n            waitConnectJob?.invokeOnCompletion {\n                if (it is ActiveDisConnectedException || it is CompleteException || it is ActiveStopConnectedException) {\n                    onCompletion(it)\n                }\n            }\n        } else {\n            throwable?.let {\n                when (it) {\n                    //连接超时\n                    is TimeoutCancellationException -> {\n                        if (lastState == BleConnectLastState.Connected) {\n                            return\n                        }\n                        connectFail()\n                        val connectTime = connectMillisTimeOut?: (getBleOptions()?.connectMillisTimeOut?: DEFAULT_CONNECT_MILLIS_TIMEOUT)\n                        val retryCount = connectRetryCount?: (getBleOptions()?.connectRetryCount?: 0)\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> 连接失败：超时${connectTime * (retryCount + 1)}ms\")\n                        bleConnectCallback?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectTimeOut\n                        )\n                        getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectTimeOut\n                        )\n                    }\n                    //连接成功\n                    is CompleteException -> {\n                        //发现服务\n                        BleLogger.e(it.message)\n                        findService()\n                    }\n                    //主动断开\n                    is ActiveDisConnectedException -> {\n                        BleLogger.e(it.message)\n                        disConnectGatt()\n                    }\n                    is ActiveStopConnectedException -> {\n                        connectFail()\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> 连接失败：${it.message}\")\n                        bleConnectCallback?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectException(it)\n                        )\n                        getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectException(it)\n                        )\n                    }\n                    //连接失败\n                    else -> {\n                        connectFail()\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> 连接失败：${it.message}\")\n                        bleConnectCallback?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectException(it)\n                        )\n                        getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                            createNewDeviceInfo(),\n                            BleConnectFailType.ConnectException(it)\n                        )\n                        connectJob?.cancel(null)\n                        waitConnectJob?.cancel(null)\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 判断是否进入重连机制，如果当前没有进入重新机制，则进入重连机制；如果进入了重连机制，则不打断重连的顺序\n     */\n    private fun checkIfContinueConnect(throwable: Throwable?) {\n        if (currentConnectRetryCount != 0) {\n            return\n        }\n        onCompletion(throwable)\n    }\n\n    /**\n     * 是否继续连接\n     */\n    private fun isContinueConnect(throwable: Throwable?): Boolean {\n        if (throwable is ActiveDisConnectedException || throwable is CompleteException || throwable is ActiveStopConnectedException) {\n            return false\n        }\n        if (!isActiveDisconnect.get() && lastState != BleConnectLastState.Connected) {\n            var retryCount = connectRetryCount?: (getBleOptions()?.connectRetryCount?: 0)\n            if (retryCount < 0) {\n                retryCount = 0\n            }\n            //满足重连条件，则先把上一次连接的状态close\n            closeBluetoothGatt()\n            if (retryCount > 0 && currentConnectRetryCount < retryCount) {\n                BleLogger.i(\"${bleDevice.deviceAddress} -> 满足重连条件：\" +\n                        \"当前连接失败次数：${currentConnectRetryCount + 1}次\")\n                return true\n            }\n        }\n        return false\n    }\n\n    /**\n     * 发现服务\n     */\n    private fun findService() {\n        bleConnectCallback?.launchInMainThread {\n            delay(waitTime * 5)\n            if (bluetoothGatt == null || bluetoothGatt?.discoverServices() == false) {\n                connectFail()\n                val exception = UnDefinedException(\"${bleDevice.deviceAddress} -> 发现服务失败\")\n                BleLogger.e(exception.message)\n                bleConnectCallback?.callConnectFail(\n                    createNewDeviceInfo(),\n                    BleConnectFailType.ConnectException(exception)\n                )\n                getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n                    createNewDeviceInfo(),\n                    BleConnectFailType.ConnectException(exception)\n                )\n            }\n        }\n    }\n\n    /**\n     * 移除设备管理池中的设备\n     */\n    private fun removeBleConnectedDevice() {\n        BleConnectedDeviceManager.get().removeBleConnectedDevice(bleDevice.getKey())\n    }\n\n    /**\n     * 连接成功，保证管理器中存在该对象。\n     * 因为连接失败后，会删除该对象，然后满足重连条件的时候该对象不存在\n     */\n    private fun addBleConnectedDevice() {\n        BleConnectedDeviceManager.get().buildBleConnectedDevice(bleDevice)\n    }\n\n    /**\n     * 移除设备管理池中的设备\n     */\n    private fun removeAllCallback() {\n        getBleConnectedDevice(bleDevice)?.removeAllCharacterCallback()\n        removeBleConnectedDevice()\n    }\n\n    /**\n     * 自动设置mtu\n     */\n    private fun autoSetMtu() {\n        if (getBleOptions()?.autoSetMtu == true) {\n            getBleConnectedDevice(bleDevice)?.setMtu(getBleOptions()?.mtu?: DEFAULT_MTU,\n                object : BleMtuChangedCallback() {\n                    override fun callMtuChanged(bleDevice: BleDevice, mtu: Int) {\n                        super.callMtuChanged(bleDevice, mtu)\n                        BleLogger.d(\"${bleDevice.deviceAddress} -> 自动设置Mtu成功: $mtu\")\n                    }\n\n                    override fun callSetMtuFail(bleDevice: BleDevice, throwable: Throwable) {\n                        super.callSetMtuFail(bleDevice, throwable)\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> \" +\n                                \"自动设置Mtu失败: ${throwable.message}\")\n                    }\n                })\n        }\n    }\n\n    private fun createNewDeviceInfo(): BleDevice {\n        if (bluetoothGatt == null || bluetoothGatt?.device == null) {\n            return bleDevice\n        }\n        return BleDevice(\n            deviceInfo = bluetoothGatt?.device?: bleDevice.deviceInfo,\n            deviceName = bluetoothGatt?.device?.name?: bleDevice.deviceName,\n            deviceAddress = bluetoothGatt?.device?.address?: bleDevice.deviceAddress,\n            rssi = bleDevice.rssi,\n            timestampNanos = bleDevice.timestampNanos,\n            scanRecord = bleDevice.scanRecord,\n            tag = bleDevice.tag,\n        )\n    }\n\n    /**\n     * 连接失败 更改状态和释放资源\n     */\n    private fun connectFail() {\n        lastState = BleConnectLastState.ConnectFailure\n        refreshDeviceCache()\n        closeBluetoothGatt()\n        removeBleConnectedDevice()\n    }\n\n    /**\n     * Gatt断开连接，需要一段时间才会触发onConnectionStateChange\n     */\n    private fun disConnectGatt() {\n        if (getBleManager().isConnected(bleDevice, true)) {\n            bluetoothGatt?.disconnect()\n        }\n    }\n\n    /**\n     * 刷新缓存\n     */\n    private fun refreshDeviceCache() {\n        try {\n            val refresh = BluetoothGatt::class.java.getMethod(\"refresh\")\n            if (bluetoothGatt != null) {\n                val success = refresh.invoke(bluetoothGatt) as Boolean\n                BleLogger.d(\"refreshDeviceCache, is success:  $success\")\n            }\n        } catch (e: Exception) {\n            BleLogger.e(\"exception occur while refreshing device: \" + e.message)\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * 关闭Gatt\n     */\n    private fun closeBluetoothGatt() {\n        bluetoothGatt?.close()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleIndicateRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.bluetooth.BluetoothGattDescriptor\nimport android.os.Build\nimport com.bhm.ble.callback.BleIndicateCallback\nimport com.bhm.ble.data.*\nimport com.bhm.ble.data.Constants.EXCEPTION_CODE_DESCRIPTOR_FAIL\nimport com.bhm.ble.data.Constants.EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\nimport com.bhm.ble.data.Constants.INDICATE_TASK_ID\nimport com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.BleTaskQueueRequest\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.TimeoutCancellationException\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 设置Indicate请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时35分\n */\ninternal class BleIndicateRequest(\n    private val bleDevice: BleDevice,\n) : BleTaskQueueRequest(bleDevice, \"Indicate队列\") {\n\n    private val bleIndicateCallbackHashMap:\n            ConcurrentHashMap<String, BleIndicateCallback> = ConcurrentHashMap()\n\n    private fun addIndicateCallback(uuid: String, bleIndicateCallback: BleIndicateCallback) {\n        bleIndicateCallbackHashMap[uuid] = bleIndicateCallback\n    }\n\n    fun removeIndicateCallback(uuid: String?) {\n        if (bleIndicateCallbackHashMap.containsKey(uuid)) {\n            bleIndicateCallbackHashMap.remove(uuid)\n        }\n    }\n\n    fun removeAllIndicateCallback() {\n        bleIndicateCallbackHashMap.clear()\n    }\n\n    /**\n     * indicate\n     */\n    fun enableCharacteristicIndicate(serviceUUID: String,\n                                     indicateUUID: String,\n                                     timeoutMillis: Long?,\n                                     bleDescriptorGetType: BleDescriptorGetType,\n                                     bleIndicateCallback: BleIndicateCallback) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleIndicateCallback.callIndicateFail(bleDevice, indicateUUID, NoBlePermissionException())\n            return\n        }\n        val characteristic = getCharacteristic(bleDevice, serviceUUID, indicateUUID)\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0\n        ) {\n            bleIndicateCallback.setKey(indicateUUID)\n            addIndicateCallback(indicateUUID, bleIndicateCallback)\n            var mContinuation: Continuation<Throwable?>? = null\n            val task = getTask(\n                getTaskId(indicateUUID),\n                durationTimeMillis = timeoutMillis?: getOperateTime(),\n                block = {\n                    suspendCoroutine<Throwable?> { continuation ->\n                        mContinuation = continuation\n                        setCharacteristicIndicate(\n                            indicateUUID,\n                            characteristic,\n                            bleDescriptorGetType,\n                            true,\n                            bleIndicateCallback\n                        )\n                    }\n                },\n                interrupt = { _, throwable ->\n                    try {\n                        mContinuation?.resume(throwable)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                },\n                callback = { _, throwable ->\n                    throwable?.let {\n                        BleLogger.e(it.message)\n                        if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                            val exception = TimeoutCancelException(\"$indicateUUID -> 设置Indicate失败，设置超时\")\n                            BleLogger.e(exception.message)\n                            bleIndicateCallback.callIndicateFail(bleDevice, indicateUUID, exception)\n                        }\n                    }\n                }\n            )\n            getTaskQueue(indicateUUID)?.addTask(task)\n        } else {\n            val exception = UnSupportException(\"$indicateUUID -> 设置Indicate失败，此特性不支持通知\")\n            BleLogger.e(exception.message)\n            bleIndicateCallback.callIndicateFail(bleDevice, indicateUUID, exception)\n        }\n    }\n\n    /**\n     * stop indicate\n     */\n    fun disableCharacteristicIndicate(serviceUUID: String,\n                                      indicateUUID: String,\n                                      bleDescriptorGetType: BleDescriptorGetType): Boolean {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            return false\n        }\n        val characteristic = getCharacteristic(bleDevice, serviceUUID, indicateUUID)\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0\n        ) {\n            cancelIndicateJob(indicateUUID, getTaskId(indicateUUID))\n            val success = setCharacteristicIndicate(\n                indicateUUID,\n                characteristic,\n                bleDescriptorGetType,\n                false,\n                null\n            )\n            if (success) {\n                removeIndicateCallback(indicateUUID)\n            }\n            return success\n        }\n        return false\n    }\n\n    /**\n     * 设备发出通知时会时会触发\n     */\n    fun onCharacteristicChanged(\n        characteristic: BluetoothGattCharacteristic,\n        value: ByteArray\n    ) {\n        BleLogger.d(\"${characteristic.uuid} -> \" +\n                \"收到Indicate数据：${BleUtil.bytesToHex(value)}\")\n        bleIndicateCallbackHashMap.values.forEach {\n            if (characteristic.uuid?.toString().equals(it.getKey(), ignoreCase = true)) {\n                it.callCharacteristicChanged(bleDevice, it.getKey().toString(), value)\n            }\n        }\n    }\n\n    /**\n     * 当向设备Descriptor中写数据时会触发\n     */\n    fun onDescriptorWrite(\n        descriptor: BluetoothGattDescriptor?,\n        status: Int\n    ) {\n        bleIndicateCallbackHashMap.values.forEach {\n            if (descriptor?.characteristic?.uuid.toString().equals(it.getKey(), ignoreCase = true)\n                && cancelIndicateJob(it.getKey(), getTaskId(it.getKey()))) {\n                if (status == BluetoothGatt.GATT_SUCCESS) {\n                    BleLogger.i(\"${it.getKey()} -> 设置Indicate成功\")\n                    it.callIndicateSuccess(bleDevice, it.getKey().toString())\n                } else {\n                    val exception = UnDefinedException(\n                        \"${it.getKey()} -> 设置Indicate失败，Descriptor写数据失败\",\n                        EXCEPTION_CODE_DESCRIPTOR_FAIL\n                    )\n                    BleLogger.e(exception.message)\n                    it.callIndicateFail(bleDevice, it.getKey().toString(), exception)\n                }\n            }\n        }\n    }\n\n    /**\n     * 配置Indicate\n     */\n    @SuppressLint(\"MissingPermission\")\n    private fun setCharacteristicIndicate(indicateUUID: String,\n                                          characteristic: BluetoothGattCharacteristic,\n                                          bleDescriptorGetType: BleDescriptorGetType,\n                                          enable: Boolean,\n                                          bleIndicateCallback: BleIndicateCallback?): Boolean {\n        val bluetoothGatt = getBluetoothGatt(bleDevice)\n        val setSuccess = bluetoothGatt?.setCharacteristicNotification(characteristic, enable)\n        if (setSuccess != true) {\n            val exception = UnDefinedException(\n                \"$indicateUUID -> 设置Indicate失败，SetCharacteristicNotificationFail\",\n                EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n            )\n            cancelIndicateJob(indicateUUID, getTaskId(indicateUUID))\n            BleLogger.e(exception.message)\n            bleIndicateCallback?.callIndicateFail(bleDevice, indicateUUID, exception)\n            return false\n        }\n        val descriptorList = characteristic.descriptors\n        BleLogger.d(\"descriptor size is ${descriptorList.size}\")\n        if (bleDescriptorGetType == BleDescriptorGetType.AllDescriptor && descriptorList.isNotEmpty()) {\n            var allFail = true\n            for (descriptor in descriptorList) {\n                descriptor?.let {\n                    BleLogger.d(\"descriptor uuid is ${descriptor.uuid}\")\n                    val writeDescriptorCode = writeDescriptor(bluetoothGatt, descriptor, enable)\n                    if (writeDescriptorCode == 0) {\n                        allFail = false\n                    }\n                }\n            }\n            if (allFail) {\n                val exception = UnDefinedException(\n                    \"$indicateUUID -> 设置Indicate失败，SetCharacteristicNotificationFail\",\n                    EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n                )\n                cancelIndicateJob(indicateUUID, getTaskId(indicateUUID))\n                BleLogger.e(exception.message)\n                bleIndicateCallback?.callIndicateFail(bleDevice, indicateUUID, exception)\n                return false\n            }\n            return true\n        } else {\n            val descriptor = if (bleDescriptorGetType == BleDescriptorGetType.CharacteristicDescriptor) {\n                characteristic.getDescriptor(characteristic.uuid)\n            } else {\n                characteristic.getDescriptor(UUID.fromString(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR))\n            }\n            if (descriptor == null) {\n                val exception = UnDefinedException(\n                    \"$indicateUUID -> 设置Indicate失败，SetCharacteristicNotificationFail\",\n                    EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n                )\n                cancelIndicateJob(indicateUUID, getTaskId(indicateUUID))\n                BleLogger.e(exception.message)\n                bleIndicateCallback?.callIndicateFail(bleDevice, indicateUUID, exception)\n                return false\n            }\n            val writeDescriptorCode = writeDescriptor(bluetoothGatt, descriptor, enable)\n            if (writeDescriptorCode != 0) {\n                //true, if the write operation was initiated successfully Value is\n                // BluetoothStatusCodes.SUCCESS = 0,\n                // BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6,\n                // BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED = 4,\n                // BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND = 8,\n                // BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED = 200,\n                // BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY = 201,\n                // BluetoothStatusCodes.ERROR_UNKNOWN = 2147483647,\n                // BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES = 13,\n                val exception = UnDefinedException(\n                    \"$indicateUUID -> -> 设置Indicate失败，错误可能是没有权限、\" +\n                            \"未连接、服务未绑定、不可写、请求忙碌等，code = $writeDescriptorCode\",\n                    EXCEPTION_CODE_DESCRIPTOR_FAIL\n                )\n                cancelIndicateJob(indicateUUID, getTaskId(indicateUUID))\n                BleLogger.e(exception.message)\n                bleIndicateCallback?.callIndicateFail(bleDevice, indicateUUID, exception)\n                return false\n            }\n        }\n        return true\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun writeDescriptor(bluetoothGatt: BluetoothGatt,\n                                descriptor: BluetoothGattDescriptor,\n                                enable: Boolean): Int {\n        val writeDescriptorCode: Int\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            writeDescriptorCode = if (enable) {\n                bluetoothGatt.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)\n            } else {\n                bluetoothGatt.writeDescriptor(descriptor, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)\n            }\n        } else {\n            @Suppress(\"DEPRECATION\")\n            descriptor.value = if (enable) {\n                BluetoothGattDescriptor.ENABLE_INDICATION_VALUE\n            } else {\n                BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE\n            }\n            @Suppress(\"DEPRECATION\")\n            val success = bluetoothGatt.writeDescriptor(descriptor)\n            writeDescriptorCode = if (success) {\n                0\n            } else {\n                -1\n            }\n        }\n        return writeDescriptorCode\n    }\n\n    private fun getTaskId(uuid: String?) = INDICATE_TASK_ID + uuid\n\n    /**\n     * 取消设置indicate任务\n     */\n    private fun cancelIndicateJob(indicateUUID: String?, taskId: String): Boolean {\n        return getTaskQueue(indicateUUID?: \"\")?.removeTask(taskId)?: false\n    }\n\n    override fun close() {\n        super.close()\n        removeAllIndicateCallback()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleMtuRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport com.bhm.ble.callback.BleMtuChangedCallback\nimport com.bhm.ble.control.BleTaskQueue\nimport com.bhm.ble.data.Constants.SET_MTU_TASK_ID\nimport com.bhm.ble.data.NoBlePermissionException\nimport com.bhm.ble.data.TimeoutCancelException\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.Request\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 设置Mtu请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时25分\n */\ninternal class BleMtuRequest(\n    private val bleDevice: BleDevice,\n    private val bleTaskQueue: BleTaskQueue\n) : Request() {\n\n    private var bleMtuChangedCallback: BleMtuChangedCallback? = null\n\n    private fun addMtuChangedCallback(callback: BleMtuChangedCallback) {\n        bleMtuChangedCallback = callback\n    }\n\n    fun removeMtuChangedCallback() {\n        bleMtuChangedCallback = null\n    }\n\n    /**\n     * 设置mtu\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun setMtu(mtu: Int, bleMtuChangedCallback: BleMtuChangedCallback) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleMtuChangedCallback.callSetMtuFail(bleDevice, NoBlePermissionException())\n            return\n        }\n        cancelSetMtuJob()\n        addMtuChangedCallback(bleMtuChangedCallback)\n        var mContinuation: Continuation<Throwable?>? = null\n        val task = getTask(\n            getTaskId(),\n            block = {\n                suspendCoroutine<Throwable?> { continuation ->\n                    mContinuation = continuation\n                    if (getBluetoothGatt(bleDevice)?.requestMtu(mtu) == false) {\n                        try {\n                            continuation.resume(UnDefinedException(\"Gatt设置mtu失败\"))\n                        } catch (e: Exception) {\n                            BleLogger.e(e.message)\n                        }\n                    }\n                }\n            },\n            interrupt = { _, throwable ->\n                try {\n                    mContinuation?.resume(throwable)\n                } catch (e: Exception) {\n                    BleLogger.e(e.message)\n                }\n            },\n            callback = { _, throwable ->\n                throwable?.let {\n                    BleLogger.e(it.message)\n                    if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                        val exception = TimeoutCancelException(\"${bleDevice.deviceAddress} -> 设置Mtu超时\")\n                        BleLogger.e(exception.message)\n                        bleMtuChangedCallback.callSetMtuFail(bleDevice, exception)\n                    }\n                }\n            }\n        )\n        bleTaskQueue.addTask(task)\n    }\n\n    /**\n     * 设置Mtu值后会触发\n     */\n    fun onMtuChanged(mtu: Int, status: Int) {\n        bleMtuChangedCallback?.let {\n            if (cancelSetMtuJob()) {\n                if (status == BluetoothGatt.GATT_SUCCESS) {\n                    BleLogger.d(\"${bleDevice.deviceAddress} -> 设置Mtu成功：$mtu\")\n                    it.callMtuChanged(bleDevice, mtu)\n                    getBleOptions()?.mtu = mtu\n                } else {\n                    val exception = UnDefinedException(\n                        \"${bleDevice.deviceAddress} -> \" +\n                                \"设置Mtu失败，status = $status\"\n                    )\n                    BleLogger.e(exception.message)\n                    it.callSetMtuFail(bleDevice, exception)\n                }\n            }\n        }\n    }\n\n    private fun getTaskId() = SET_MTU_TASK_ID + bleDevice.deviceAddress\n\n    /**\n     * 取消设置Mtu任务\n     */\n    private fun cancelSetMtuJob(): Boolean {\n        return bleTaskQueue.removeTask(getTaskId())\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleNotifyRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.bluetooth.BluetoothGattDescriptor\nimport android.os.Build\nimport com.bhm.ble.callback.BleNotifyCallback\nimport com.bhm.ble.data.*\nimport com.bhm.ble.data.Constants.EXCEPTION_CODE_DESCRIPTOR_FAIL\nimport com.bhm.ble.data.Constants.EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\nimport com.bhm.ble.data.Constants.NOTIFY_TASK_ID\nimport com.bhm.ble.data.Constants.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.BleTaskQueueRequest\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.TimeoutCancellationException\nimport java.util.*\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 设置Notify请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时35分\n */\ninternal class BleNotifyRequest(\n    private val bleDevice: BleDevice,\n) : BleTaskQueueRequest(bleDevice, \"Notify队列\") {\n\n    private val bleNotifyCallbackHashMap:\n            ConcurrentHashMap<String, BleNotifyCallback> = ConcurrentHashMap()\n\n    private fun addNotifyCallback(uuid: String, bleNotifyCallback: BleNotifyCallback) {\n        bleNotifyCallbackHashMap[uuid] = bleNotifyCallback\n    }\n\n    fun removeNotifyCallback(uuid: String?) {\n        if (bleNotifyCallbackHashMap.containsKey(uuid)) {\n            bleNotifyCallbackHashMap.remove(uuid)\n        }\n    }\n\n    fun removeAllNotifyCallback() {\n        bleNotifyCallbackHashMap.clear()\n    }\n\n    /**\n     * notify\n     */\n    fun enableCharacteristicNotify(serviceUUID: String,\n                                   notifyUUID: String,\n                                   timeoutMillis: Long?,\n                                   bleDescriptorGetType: BleDescriptorGetType,\n                                   bleNotifyCallback: BleNotifyCallback) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleNotifyCallback.callNotifyFail(bleDevice, notifyUUID, NoBlePermissionException())\n            return\n        }\n        val characteristic = getCharacteristic(bleDevice, serviceUUID, notifyUUID)\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0\n        ) {\n            bleNotifyCallback.setKey(notifyUUID)\n            addNotifyCallback(notifyUUID, bleNotifyCallback)\n            var mContinuation: Continuation<Throwable?>? = null\n            val task = getTask(\n                getTaskId(notifyUUID),\n                durationTimeMillis = timeoutMillis?: getOperateTime(),\n                block = {\n                    suspendCoroutine<Throwable?> { continuation ->\n                        mContinuation = continuation\n                        setCharacteristicNotify(\n                            notifyUUID,\n                            characteristic,\n                            bleDescriptorGetType,\n                            true,\n                            bleNotifyCallback\n                        )\n                    }\n                },\n                interrupt = { _, throwable ->\n                    try {\n                        mContinuation?.resume(throwable)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                },\n                callback = { _, throwable ->\n                    throwable?.let {\n                        BleLogger.e(it.message)\n                        if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                            val exception = TimeoutCancelException(\"$notifyUUID -> 设置Notify失败，设置超时\")\n                            BleLogger.e(exception.message)\n                            bleNotifyCallback.callNotifyFail(bleDevice, notifyUUID, exception)\n                        }\n                    }\n                }\n            )\n            getTaskQueue(notifyUUID)?.addTask(task)\n        } else {\n            val exception = UnSupportException(\"$notifyUUID -> 设置Notify失败，此特性不支持通知\")\n            BleLogger.e(exception.message)\n            bleNotifyCallback.callNotifyFail(bleDevice, notifyUUID, exception)\n        }\n    }\n\n    /**\n     * stop notify\n     */\n    fun disableCharacteristicNotify(serviceUUID: String,\n                                    notifyUUID: String,\n                                    bleDescriptorGetType: BleDescriptorGetType): Boolean {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            return false\n        }\n        val characteristic = getCharacteristic(bleDevice, serviceUUID, notifyUUID)\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0\n        ) {\n            cancelNotifyJob(notifyUUID, getTaskId(notifyUUID))\n            val success = setCharacteristicNotify(\n                notifyUUID,\n                characteristic,\n                bleDescriptorGetType,\n                false,\n                null\n            )\n            if (success) {\n                removeNotifyCallback(notifyUUID)\n            }\n            return success\n        }\n        return false\n    }\n\n    /**\n     * 设备发出通知时会时会触发\n     */\n    fun onCharacteristicChanged(\n        characteristic: BluetoothGattCharacteristic,\n        value: ByteArray\n    ) {\n        BleLogger.d(\"${characteristic.uuid} -> \" +\n                \"收到Notify数据：${BleUtil.bytesToHex(value)}\")\n        bleNotifyCallbackHashMap.values.forEach {\n            if (characteristic.uuid?.toString().equals(it.getKey(), ignoreCase = true)) {\n                it.callCharacteristicChanged(bleDevice, it.getKey().toString(), value)\n            }\n        }\n    }\n\n    /**\n     * 当向设备Descriptor中写数据时会触发\n     */\n    fun onDescriptorWrite(\n        descriptor: BluetoothGattDescriptor?,\n        status: Int\n    ) {\n        bleNotifyCallbackHashMap.values.forEach {\n            if (descriptor?.characteristic?.uuid?.toString().equals(it.getKey(), ignoreCase = true)\n                && cancelNotifyJob(it.getKey(), getTaskId(it.getKey()))) {\n                if (status == BluetoothGatt.GATT_SUCCESS) {\n                    BleLogger.i(\"${it.getKey()} -> 设置Notify成功\")\n                    it.callNotifySuccess(bleDevice, it.getKey().toString())\n                } else {\n                    val exception = UnDefinedException(\n                        \"${it.getKey()} -> 设置Notify失败，Descriptor写数据失败\",\n                        EXCEPTION_CODE_DESCRIPTOR_FAIL\n                    )\n                    BleLogger.e(exception.message)\n                    it.callNotifyFail(bleDevice, it.getKey().toString(), exception)\n                }\n            }\n        }\n    }\n\n    /**\n     * 配置Notify\n     */\n    @SuppressLint(\"MissingPermission\")\n    private fun setCharacteristicNotify(notifyUUID: String,\n                                        characteristic: BluetoothGattCharacteristic,\n                                        bleDescriptorGetType: BleDescriptorGetType,\n                                        enable: Boolean,\n                                        bleNotifyCallback: BleNotifyCallback?): Boolean {\n        val bluetoothGatt = getBluetoothGatt(bleDevice)\n        val setSuccess = bluetoothGatt?.setCharacteristicNotification(characteristic, enable)\n        if (setSuccess != true) {\n            val exception = UnDefinedException(\n                \"$notifyUUID -> 设置Notify失败，SetCharacteristicNotificationFail\",\n                EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n            )\n            cancelNotifyJob(notifyUUID, getTaskId(notifyUUID))\n            BleLogger.e(exception.message)\n            bleNotifyCallback?.callNotifyFail(bleDevice, notifyUUID, exception)\n            return false\n        }\n        val descriptorList = characteristic.descriptors\n        BleLogger.d(\"descriptor size is ${descriptorList.size}\")\n        if (bleDescriptorGetType == BleDescriptorGetType.AllDescriptor && descriptorList.isNotEmpty()) {\n            var allFail = true\n            for (descriptor in descriptorList) {\n                descriptor?.let {\n                    BleLogger.d(\"descriptor uuid is ${descriptor.uuid}\")\n                    val writeDescriptorCode = writeDescriptor(bluetoothGatt, descriptor, enable)\n                    if (writeDescriptorCode == 0) {\n                        allFail = false\n                    }\n                }\n            }\n            if (allFail) {\n                val exception = UnDefinedException(\n                    \"$notifyUUID -> 设置Notify失败，SetCharacteristicNotificationFail\",\n                    EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n                )\n                cancelNotifyJob(notifyUUID, getTaskId(notifyUUID))\n                BleLogger.e(exception.message)\n                bleNotifyCallback?.callNotifyFail(bleDevice, notifyUUID, exception)\n                return false\n            }\n            return true\n        } else {\n            val descriptor = if (bleDescriptorGetType == BleDescriptorGetType.CharacteristicDescriptor) {\n                characteristic.getDescriptor(characteristic.uuid)\n            } else {\n                characteristic.getDescriptor(UUID.fromString(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR))\n            }\n            if (descriptor == null) {\n                val exception = UnDefinedException(\n                    \"$notifyUUID -> 设置Notify失败，SetCharacteristicNotificationFail\",\n                    EXCEPTION_CODE_SET_CHARACTERISTIC_NOTIFICATION_FAIL\n                )\n                cancelNotifyJob(notifyUUID, getTaskId(notifyUUID))\n                BleLogger.e(exception.message)\n                bleNotifyCallback?.callNotifyFail(bleDevice, notifyUUID, exception)\n                return false\n            }\n            val writeDescriptorCode = writeDescriptor(bluetoothGatt, descriptor, enable)\n            if (writeDescriptorCode != 0) {\n                //true, if the write operation was initiated successfully Value is\n                // BluetoothStatusCodes.SUCCESS = 0,\n                // BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6,\n                // BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED = 4,\n                // BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND = 8,\n                // BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED = 200,\n                // BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY = 201,\n                // BluetoothStatusCodes.ERROR_UNKNOWN = 2147483647,\n                // BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES = 13,\n                val exception = UnDefinedException(\n                    \"$notifyUUID -> -> 设置Notify失败，错误可能是没有权限、\" +\n                            \"未连接、服务未绑定、不可写、请求忙碌等，code = $writeDescriptorCode\",\n                    EXCEPTION_CODE_DESCRIPTOR_FAIL\n                )\n                cancelNotifyJob(notifyUUID, getTaskId(notifyUUID))\n                BleLogger.e(exception.message)\n                bleNotifyCallback?.callNotifyFail(bleDevice, notifyUUID, exception)\n                return false\n            }\n        }\n        return true\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun writeDescriptor(bluetoothGatt: BluetoothGatt,\n                                descriptor: BluetoothGattDescriptor,\n                                enable: Boolean): Int {\n        val writeDescriptorCode: Int\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            writeDescriptorCode = if (enable) {\n                bluetoothGatt.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)\n            } else {\n                bluetoothGatt.writeDescriptor(descriptor, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)\n            }\n        } else {\n            @Suppress(\"DEPRECATION\")\n            descriptor.value = if (enable) {\n                BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE\n            } else {\n                BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE\n            }\n            @Suppress(\"DEPRECATION\")\n            val success = bluetoothGatt.writeDescriptor(descriptor)\n            writeDescriptorCode = if (success) {\n                0\n            } else {\n                -1\n            }\n        }\n        return writeDescriptorCode\n    }\n\n    private fun getTaskId(uuid: String?) = NOTIFY_TASK_ID + uuid\n\n    /**\n     * 取消设置notify任务\n     */\n    private fun cancelNotifyJob(notifyUUID: String?, taskId: String): Boolean {\n        return getTaskQueue(notifyUUID?: \"\")?.removeTask(taskId)?: false\n    }\n\n    override fun close() {\n        super.close()\n        removeAllNotifyCallback()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleReadRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCharacteristic\nimport com.bhm.ble.callback.BleReadCallback\nimport com.bhm.ble.data.Constants.READ_TASK_ID\nimport com.bhm.ble.data.NoBlePermissionException\nimport com.bhm.ble.data.TimeoutCancelException\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.data.UnSupportException\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.request.base.BleTaskQueueRequest\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.TimeoutCancellationException\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 设备读请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时58分\n */\ninternal class BleReadRequest(\n    private val bleDevice: BleDevice,\n) : BleTaskQueueRequest(bleDevice, \"Read队列\") {\n\n    private val bleReadCallbackHashMap:\n            ConcurrentHashMap<String, BleReadCallback> = ConcurrentHashMap()\n\n    private fun addReadCallback(uuid: String, bleReadCallback: BleReadCallback) {\n        bleReadCallbackHashMap[uuid] = bleReadCallback\n    }\n\n    fun removeReadCallback(uuid: String?) {\n        if (bleReadCallbackHashMap.containsKey(uuid)) {\n            bleReadCallbackHashMap.remove(uuid)\n        }\n    }\n\n    fun removeAllReadCallback() {\n        bleReadCallbackHashMap.clear()\n    }\n\n    /**\n     * read\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun readCharacteristic(serviceUUID: String,\n                           readUUID: String,\n                           bleReadCallback: BleReadCallback\n    ) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleReadCallback.callReadFail(bleDevice, NoBlePermissionException())\n            return\n        }\n        val characteristic = getCharacteristic(bleDevice, serviceUUID, readUUID)\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_READ) != 0\n        ) {\n            cancelReadJob(readUUID, getTaskId(readUUID))\n            bleReadCallback.setKey(readUUID)\n            addReadCallback(readUUID, bleReadCallback)\n            var mContinuation: Continuation<Throwable?>? = null\n            val task = getTask(\n                getTaskId(readUUID),\n                block = {\n                    suspendCoroutine<Throwable?> { continuation ->\n                        mContinuation = continuation\n                        if (getBluetoothGatt(bleDevice)?.readCharacteristic(characteristic) == false) {\n                            try {\n                                continuation.resume(UnDefinedException(\"Gatt读特征值数据失败\"))\n                            } catch (e: Exception) {\n                                BleLogger.e(e.message)\n                            }\n                        }\n                    }\n                },\n                interrupt = { _, throwable ->\n                    try {\n                        mContinuation?.resume(throwable)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                },\n                callback = { _, throwable ->\n                    throwable?.let {\n                        BleLogger.e(it.message)\n                        if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                            val exception = TimeoutCancelException(\"$readUUID -> 读特征值数据失败，超时\")\n                            BleLogger.e(exception.message)\n                            bleReadCallback.callReadFail(bleDevice, exception)\n                        }\n                    }\n                }\n            )\n            getTaskQueue(readUUID)?.addTask(task)\n        } else {\n            val exception = UnSupportException(\"$readUUID -> 读特征值数据失败，此特性不支持读特征值数据\")\n            BleLogger.e(exception.message)\n            bleReadCallback.callReadFail(bleDevice, exception)\n        }\n    }\n\n    /**\n     * 当读取设备数据时会触发\n     */\n    fun onCharacteristicRead(\n        characteristic: BluetoothGattCharacteristic,\n        value: ByteArray,\n        status: Int\n    ) {\n        bleReadCallbackHashMap.values.forEach {\n            if (characteristic.uuid?.toString().equals(it.getKey(), ignoreCase = true) &&\n                cancelReadJob(it.getKey(), getTaskId(it.getKey()))) {\n                if (status == BluetoothGatt.GATT_SUCCESS) {\n                    BleLogger.d(\n                        \"${it.getKey()} -> \" +\n                                \"读特征值数据成功：${BleUtil.bytesToHex(value)}\"\n                    )\n                    it.callReadSuccess(bleDevice, value)\n                } else {\n                    val exception = UnDefinedException(\n                        \"${it.getKey()} -> \" +\n                                \"读特征值数据失败，status = $status\"\n                    )\n                    BleLogger.e(exception.message)\n                    it.callReadFail(bleDevice, exception)\n                }\n            }\n        }\n    }\n\n    private fun getTaskId(uuid: String?) = READ_TASK_ID + uuid\n\n    /**\n     * 取消读特征值数据任务\n     */\n    private fun cancelReadJob(readUUID: String?, taskId: String): Boolean {\n        return getTaskQueue(readUUID?: \"\")?.removeTask(taskId)?: false\n    }\n\n    override fun close() {\n        super.close()\n        removeAllReadCallback()\n        getTaskQueueList()?.forEach { it?.removeAllTask() }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleRssiRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport com.bhm.ble.callback.BleRssiCallback\nimport com.bhm.ble.control.BleTaskQueue\nimport com.bhm.ble.data.Constants.SET_RSSI_TASK_ID\nimport com.bhm.ble.data.NoBlePermissionException\nimport com.bhm.ble.data.TimeoutCancelException\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.Request\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 读取设备Rssi请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 11时05分\n */\ninternal class BleRssiRequest(\n    private val bleDevice: BleDevice,\n) : Request() {\n\n    private var bleRssiCallback: BleRssiCallback? = null\n\n    private val bleTaskQueue: BleTaskQueue = BleTaskQueue(\"${bleDevice.deviceAddress}Rssi队列\")\n\n    private fun addRssiCallback(callback: BleRssiCallback) {\n        bleRssiCallback = callback\n    }\n\n    fun removeRssiCallback() {\n        bleRssiCallback = null\n    }\n\n    /**\n     * 读取信号值\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun readRemoteRssi(bleRssiCallback: BleRssiCallback) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleRssiCallback.callRssiFail(bleDevice, NoBlePermissionException())\n            return\n        }\n        cancelReadRssiJob()\n        addRssiCallback(bleRssiCallback)\n        var mContinuation: Continuation<Throwable?>? = null\n        val task = getTask(\n            getTaskId(),\n            block = {\n                suspendCoroutine<Throwable?> { continuation ->\n                    mContinuation = continuation\n                    if (getBluetoothGatt(bleDevice)?.readRemoteRssi() == false) {\n                        try {\n                            continuation.resume(UnDefinedException(\"Gatt读取Rssi失败\"))\n                        } catch (e: Exception) {\n                            BleLogger.e(e.message)\n                        }\n                    }\n                }\n            },\n            interrupt = { _, throwable ->\n                try {\n                    mContinuation?.resume(throwable)\n                } catch (e: Exception) {\n                    BleLogger.e(e.message)\n                }\n            },\n            callback = { _, throwable ->\n                throwable?.let {\n                    BleLogger.e(it.message)\n                    if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                        BleLogger.e(\"${bleDevice.deviceAddress} -> 读取Rssi超时\")\n                        bleRssiCallback.callRssiFail(bleDevice,\n                            TimeoutCancelException(\"${bleDevice.deviceAddress}\" +\n                                    \" -> 读取Rssi失败，超时\")\n                        )\n                    }\n                }\n            }\n        )\n        bleTaskQueue.addTask(task)\n    }\n\n    /**\n     * 读取信号值后会触发\n     */\n    fun onReadRemoteRssi(rssi: Int, status: Int) {\n        bleRssiCallback?.let {\n            if (cancelReadRssiJob()) {\n                if (status == BluetoothGatt.GATT_SUCCESS) {\n                    BleLogger.d(\"${bleDevice.deviceAddress} -> 读取Rssi成功：$rssi\")\n                    it.callRssiSuccess(bleDevice, rssi)\n                } else {\n                    val exception = UnDefinedException(\n                        \"${bleDevice.deviceAddress} -> \" +\n                                \"读取Rssi失败，status = $status\"\n                    )\n                    BleLogger.e(exception.message)\n                    it.callRssiFail(bleDevice, exception)\n                }\n            }\n        }\n    }\n\n    private fun getTaskId() = SET_RSSI_TASK_ID + bleDevice.deviceAddress\n\n    /**\n     * 取消读取Rssi任务\n     */\n    private fun cancelReadRssiJob(): Boolean {\n        return bleTaskQueue.removeTask(getTaskId())\n    }\n\n    fun close() {\n        bleTaskQueue.clear()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleScanRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"SENSELESS_COMPARISON\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.le.*\nimport android.os.ParcelUuid\nimport com.bhm.ble.callback.BleScanCallback\nimport com.bhm.ble.data.BleScanFailType\nimport com.bhm.ble.data.Constants.CANCEL_WAIT_JOB_MESSAGE\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_MILLIS_TIMEOUT\nimport com.bhm.ble.data.Constants.DEFAULT_SCAN_RETRY_INTERVAL\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.BleRequestImp\nimport com.bhm.ble.request.base.Request\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.*\nimport java.util.*\nimport java.util.concurrent.ConcurrentLinkedQueue\nimport java.util.concurrent.atomic.AtomicBoolean\n\n\n/**\n * Ble扫描\n *\n * @author Buhuiming\n * @date 2023年05月22日 09时49分\n */\n@SuppressLint(\"MissingPermission\")\ninternal class BleScanRequest private constructor() : Request() {\n\n    companion object {\n\n        private var instance: BleScanRequest = BleScanRequest()\n\n        fun get(): BleScanRequest {\n            if (instance == null) {\n                instance = BleScanRequest()\n            }\n            return instance\n        }\n    }\n\n    private val isScanning = AtomicBoolean(false)\n\n    private val cancelScan = AtomicBoolean(false)\n\n    private var scanJob: Job? = null\n\n    private var waitScanJob: Job? = null\n\n    private var bleScanCallback: BleScanCallback? = null\n\n    private val results: ConcurrentLinkedQueue<BleDevice> = ConcurrentLinkedQueue()\n\n    private val duplicateRemovalResults: ConcurrentLinkedQueue<BleDevice> = ConcurrentLinkedQueue()\n\n    private var currentReyCount = 0\n\n    private var scanMillisTimeOut: Long? = null\n\n    private var scanRetryCount: Int? = null\n\n    private var scanRetryInterval: Long? = null\n\n    private var bleScanCallbacksList: MutableList<BleScanCallback>? = null\n\n    /**\n     * 开始扫描\n     */\n    fun startScan(\n        scanMillisTimeOut: Long?,\n        scanRetryCount: Int?,\n        scanRetryInterval: Long?,\n        bleScanCallback: BleScanCallback\n    ) {\n        this.bleScanCallback = bleScanCallback\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            callScanFail(BleScanFailType.NoBlePermission)\n            return\n        }\n        initScannerAndStart(\n            scanMillisTimeOut,\n            scanRetryCount,\n            scanRetryInterval,\n            bleScanCallback\n        )\n    }\n\n    /**\n     * 初始化扫描参数\n     */\n    private fun initScannerAndStart(\n        scanMillisTimeOut: Long?,\n        scanRetryCount: Int?,\n        scanRetryInterval: Long?,\n        bleScanCallback: BleScanCallback\n    ) {\n        this.scanMillisTimeOut = scanMillisTimeOut\n        this.scanRetryCount = scanRetryCount\n        this.scanRetryInterval = scanRetryInterval\n        val bleManager = getBleManager()\n        if (!BleUtil.isPermission(bleManager.getContext()?.applicationContext)) {\n            BleLogger.e(\"权限不足，请检查\")\n            callScanFail(BleScanFailType.NoBlePermission)\n            return\n        }\n        if (!bleManager.isBleSupport()) {\n            BleLogger.e(\"设备不支持蓝牙\")\n            callScanFail(BleScanFailType.UnSupportBle)\n            return\n        }\n        val needGps = getBleOptions()?.needCheckGps?: true\n        if (!BleUtil.isGpsOpen(bleManager.getContext()?.applicationContext) && needGps) {\n            BleLogger.e(\"设备未打开GPS定位\")\n            callScanFail(BleScanFailType.GPSDisable)\n            return\n        }\n        if (!bleManager.isBleEnable()) {\n            BleLogger.e(\"蓝牙未打开\")\n            callScanFail(BleScanFailType.BleDisable)\n            return\n        }\n        if (isScanning.get()) {\n            BleLogger.e(\"已存在相同扫描\")\n            callScanFail(BleScanFailType.AlReadyScanning)\n            return\n        }\n        duplicateRemovalResults.clear()\n        results.clear()\n        val scanFilters = arrayListOf<ScanFilter>()\n        getBleOptions()?.let { options ->\n            try {\n                //设置过滤条件-ServiceUuid\n                options.scanServiceUuids.forEach { serviceUuid ->\n                    val scanFilter = ScanFilter.Builder()\n                        .setServiceUuid(ParcelUuid(UUID.fromString(serviceUuid)))\n                        .build()\n                    scanFilters.add(scanFilter)\n                }\n                //设置过滤条件-设备广播名称\n                //这里先不过滤，扫描到后再根据条件过滤\n//            options.scanDeviceNames.forEach { deviceName ->\n//                val scanFilter = ScanFilter.Builder()\n//                    .setDeviceName(deviceName)\n//                    .build()\n//                scanFilters.add(scanFilter)\n//            }\n                options.scanDeviceAddresses.forEach { deviceAddress ->\n                    val scanFilter = ScanFilter.Builder()\n                        .setDeviceAddress(deviceAddress)\n                        .build()\n                    scanFilters.add(scanFilter)\n                }\n            } catch (e: IllegalArgumentException) {\n                BleLogger.e(\"扫描参数设置有误： ${e.message}\")\n                callScanFail(BleScanFailType.ScanError(-1, e))\n                return\n            }\n        }\n        val scanSetting: ScanSettings? =\n            try {\n                ScanSettings.Builder()\n                    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)\n                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)\n                    .build()\n            } catch (e: IllegalArgumentException) {\n                BleLogger.e(\"扫描参数设置有误： ${e.message}\")\n                callScanFail(BleScanFailType.ScanError(-1, e))\n                return\n            }\n        val scanner = bleManager.getBluetoothManager()?.adapter?.bluetoothLeScanner\n        callScanStart()\n        bleScan(scanner, scanFilters, scanSetting)\n    }\n\n    /**\n     * 执行扫描\n     */\n    private fun bleScan(scanner: BluetoothLeScanner?,\n                        scanFilters: ArrayList<ScanFilter>,\n                        scanSetting: ScanSettings?\n    ) {\n        BleLogger.d(\"开始第${currentReyCount + 1}次扫描\")\n        isScanning.set(true)\n        cancelScan.set(false)\n        var scanTime = scanMillisTimeOut?: (getBleOptions()?.scanMillisTimeOut?: DEFAULT_SCAN_MILLIS_TIMEOUT)\n        //不支持无限扫描，可以设置scanMillisTimeOut + setScanRetryCountAndInterval\n        if (scanTime <= 0) {\n            scanTime = DEFAULT_SCAN_MILLIS_TIMEOUT\n        }\n        scanJob = bleScanCallback?.launchInIOThread {\n            withTimeout(scanTime) {\n                try {\n                    scanner?.startScan(scanFilters, scanSetting, scanCallback)\n                } catch (e: Exception) {\n                    BleLogger.e(\"扫描失败： ${e.message}\")\n                    callScanFail(BleScanFailType.ScanError(-1, e))\n                }\n                delay(scanTime)\n            }\n        }\n        scanJob?.invokeOnCompletion {\n            onCompletion(scanner, scanFilters, scanSetting, it)\n        }\n    }\n\n    /**\n     * 是否继续扫描\n     */\n    private fun ifContinueScan(): Boolean {\n        if (!cancelScan.get()) {\n            var retryCount = scanRetryCount?: (getBleOptions()?.scanRetryCount?: 0)\n            if (retryCount < 0) {\n                retryCount = 0\n            }\n            if (retryCount > 0 && currentReyCount < retryCount) {\n                return true\n            }\n        }\n        return false\n    }\n\n    /**\n     * 完成\n     */\n    private fun onCompletion(scanner: BluetoothLeScanner?,\n                             scanFilters: ArrayList<ScanFilter>,\n                             scanSetting: ScanSettings?,\n                             throwable: Throwable?) {\n        isScanning.set(false)\n        try {\n            scanner?.stopScan(scanCallback)\n        } catch (e: Exception) {\n            BleLogger.e(\"停止扫描失败： ${e.message}\")\n            callScanFail(BleScanFailType.ScanError(-1, e))\n            return\n        }\n        if (ifContinueScan()) {\n            val retryInterval = scanRetryInterval?: (getBleOptions()?.scanRetryInterval?: DEFAULT_SCAN_RETRY_INTERVAL)\n            waitScanJob = bleScanCallback?.launchInDefaultThread {\n                delay(retryInterval)\n                currentReyCount ++\n                bleScan(scanner, scanFilters, scanSetting)\n            }\n            waitScanJob?.invokeOnCompletion {\n                //手动取消，等待扫描任务取消后，要返回最终信息\n                //waitScanJob取消后，会导致scanJob被取消\n                if (CANCEL_WAIT_JOB_MESSAGE == it?.message) {\n                    onCompletion(scanner, scanFilters, scanSetting, it)\n                }\n            }\n        } else {\n            throwable?.let {\n                if (it !is CancellationException) {\n                    BleLogger.e(\"扫描失败： ${it.message}\")\n                    callScanFail(BleScanFailType.ScanError(-1, it))\n                }\n            }\n            callScanComplete(results.toMutableList(), duplicateRemovalResults.toMutableList())\n            if (results.isEmpty()) {\n                BleLogger.w(\"没有扫描到数据\")\n            }\n            bleScanCallback?.launchInDefaultThread {\n                delay(500)\n                BleLogger.d(\"扫描完毕，扫描次数${currentReyCount + 1}次\")\n                currentReyCount = 0\n            }\n        }\n    }\n\n    private val scanCallback = object : ScanCallback() {\n        override fun onScanResult(callbackType: Int, result: ScanResult?) {\n            super.onScanResult(callbackType, result)\n            if (!isScanning.get()) {\n                return\n            }\n            BleRequestImp.get().getIOScope().launch {\n                result?.let {\n                    val bleDevice = BleUtil.scanResultToBleDevice(it)\n                    BleLogger.d(bleDevice.toString())\n                    if ((getBleOptions()?.scanDeviceNames?.size ?: 0) > 0) {\n                        if (!bleDevice.deviceName.isNullOrEmpty()) {\n                            getBleOptions()?.scanDeviceNames?.forEach { scanDeviceName ->\n                                if ((getBleOptions()?.containScanDeviceName == true &&\n                                            bleDevice.deviceName.uppercase()\n                                                .contains(scanDeviceName.uppercase())) ||\n                                    bleDevice.deviceName.uppercase() == scanDeviceName.uppercase()\n                                ) {\n                                    filterData(bleDevice)\n                                }\n                            }\n                        }\n                    } else {\n                        filterData(bleDevice)\n                    }\n                }\n            }\n        }\n\n        override fun onScanFailed(errorCode: Int) {\n            super.onScanFailed(errorCode)\n            /**\n             * 扫描错误(这里不再详细区分，具体错误码如下)\n             * 1、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED]\n             * 无法启动扫描，因为应用程序已启动具有相同设置的 BLE 扫描。\n             * 2、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED]\n             * 无法开始扫描，因为无法注册应用程序。\n             * 3、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR]\n             * 由于内部错误无法开始扫描。\n             * 4、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED]\n             * 无法启动电源优化扫描，因为不支持此功能。\n             * 5、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES]\n             * 由于硬件资源不足，无法启动扫描。\n             * 6、errorCode = [android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY]\n             * 由于应用程序尝试扫描过于频繁，无法开始扫描。\n             * 7、errorCode = -1，具体看throwable\n             */\n            val e = UnDefinedException(\"扫描失败，请查验[android.bluetooth.le.ScanCallback错误码]\")\n            BleLogger.e(e.message)\n            callScanFail(BleScanFailType.ScanError(errorCode, e))\n        }\n    }\n\n    /**\n     * 回调扫描数据\n     */\n    private fun filterData(bleDevice: BleDevice) {\n        results.add(bleDevice)\n        callLeScan(bleDevice, currentReyCount + 1)\n        if (duplicateRemovalResults.isEmpty()) {\n            duplicateRemovalResults.add(bleDevice)\n            callLeScanDuplicateRemoval(bleDevice, currentReyCount + 1)\n        } else {\n            var same = false\n            for (mBleDevice in duplicateRemovalResults) {\n                if (bleDevice == mBleDevice) {\n                    same = true\n                    break\n                }\n            }\n            if (!same) {\n                duplicateRemovalResults.add(bleDevice)\n                callLeScanDuplicateRemoval(bleDevice, currentReyCount + 1)\n            }\n        }\n    }\n\n    private fun callScanStart() {\n        bleScanCallback?.callScanStart()\n        bleScanCallbacksList?.forEach {\n            it.callScanStart()\n        }\n    }\n\n    private fun callScanComplete(results: MutableList<BleDevice>, duplicateRemovalResults: MutableList<BleDevice>) {\n        bleScanCallback?.callScanComplete(results, duplicateRemovalResults)\n        bleScanCallbacksList?.forEach {\n            it.callScanComplete(results, duplicateRemovalResults)\n        }\n    }\n\n    private fun callScanFail(failType: BleScanFailType) {\n        bleScanCallback?.callScanFail(failType)\n        bleScanCallbacksList?.forEach {\n            it.callScanFail(failType)\n        }\n    }\n\n    private fun callLeScan(bleDevice: BleDevice, reyCount: Int) {\n        bleScanCallback?.callLeScan(bleDevice, reyCount)\n        bleScanCallbacksList?.forEach {\n            it.callLeScan(bleDevice, reyCount)\n        }\n    }\n\n    private fun callLeScanDuplicateRemoval(bleDevice: BleDevice, reyCount: Int) {\n        bleScanCallback?.callLeScanDuplicateRemoval(bleDevice, reyCount)\n        bleScanCallbacksList?.forEach {\n            it.callLeScanDuplicateRemoval(bleDevice, reyCount)\n        }\n    }\n\n    fun addBleScanCallback(bleScanCallback: BleScanCallback) {\n        if (bleScanCallbacksList == null) {\n            bleScanCallbacksList = mutableListOf()\n        }\n        bleScanCallbacksList?.add(bleScanCallback)\n    }\n\n    /**\n     * 是否扫描中\n     */\n    fun isScanning() = isScanning.get()\n\n    fun removeBleScanCallback() {\n        this.bleScanCallback = null\n        bleScanCallbacksList?.clear()\n    }\n\n    /**\n     * 停止扫描\n     */\n    fun stopScan() {\n        isScanning.set(false)\n        cancelScan.set(true)\n        scanJob?.cancel()\n        waitScanJob?.cancel(CancellationException(CANCEL_WAIT_JOB_MESSAGE))\n    }\n\n    fun close() {\n        scanJob = null\n        waitScanJob = null\n        bleScanCallback = null\n        bleScanCallbacksList?.clear()\n        bleScanCallbacksList = null\n        results.clear()\n        duplicateRemovalResults.clear()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleSetPriorityRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.request.base.Request\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.utils.BleUtil\n\n\n/**\n * 设置设备的传输优先级请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时45分\n */\ninternal class BleSetPriorityRequest(private val bleDevice: BleDevice) : Request() {\n\n    /**\n     * 设置设备的传输优先级\n     * connectionPriority 必须是 [BluetoothGatt.CONNECTION_PRIORITY_BALANCED]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER]的其中一个\n     *\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun setConnectionPriority(connectionPriority: Int): Boolean {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            BleLogger.e(\"${bleDevice.deviceAddress} -> 设置设备的传输优先级失败，没有权限\")\n            return false\n        }\n        val result = getBluetoothGatt(bleDevice)?.requestConnectionPriority(connectionPriority)?: false\n        if (result) {\n            BleLogger.i(\"${bleDevice.deviceAddress} -> 设置设备的传输优先级成功\")\n        } else {\n            BleLogger.e(\"${bleDevice.deviceAddress} -> 设置设备的传输优先级失败\")\n        }\n        return result\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/BleWriteRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n@file:Suppress(\"RemoveExplicitTypeArguments\")\n\npackage com.bhm.ble.request\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCharacteristic\nimport android.os.Build\nimport android.util.SparseArray\nimport com.bhm.ble.callback.BleWriteCallback\nimport com.bhm.ble.data.BleWriteData\nimport com.bhm.ble.data.BleWriteQueueData\nimport com.bhm.ble.data.Constants.DEFAULT_MTU\nimport com.bhm.ble.data.Constants.WRITE_TASK_ID\nimport com.bhm.ble.data.NoBlePermissionException\nimport com.bhm.ble.data.TimeoutCancelException\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.data.UnSupportException\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport com.bhm.ble.request.base.BleTaskQueueRequest\nimport com.bhm.ble.utils.BleUtil\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.util.Collections\nimport java.util.LinkedList\nimport java.util.UUID\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.LinkedBlockingQueue\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.concurrent.atomic.AtomicInteger\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 设备写请求\n *\n * @author Buhuiming\n * @date 2023年06月07日 15时58分\n */\ninternal class BleWriteRequest(\n    private val bleDevice: BleDevice,\n) : BleTaskQueueRequest(bleDevice, \"Write队列\") {\n\n    private val bleWriteDataHashMap:\n            ConcurrentHashMap<String, MutableList<BleWriteData>> = ConcurrentHashMap()\n\n    /**\n     * 写数据队列。写成功才写下一包。Ble库目前没有这样处理\n     */\n    private val linkedBlockingQueue = LinkedBlockingQueue<BleWriteQueueData>()\n\n    /**\n     * 写数据临时队列\n     */\n    private val linkedBlockingTempQueue = LinkedBlockingQueue<BleWriteQueueData>()\n\n    /**\n     * 添加任务线程\n     */\n    private val addWriteJobScope = CoroutineScope(Dispatchers.IO)\n\n    /**\n     * 写队列线程\n     */\n    private val writeJobScope = CoroutineScope(Dispatchers.IO)\n\n    /**\n     * 当前重写次数\n     */\n    private var currentRetryWriteCount = AtomicInteger(0)\n\n    private val characters = ('a'..'z') + ('A'..'Z') + ('0'..'9')\n\n    private fun addBleWriteData(uuid: String, bleWriteData: BleWriteData) {\n        if (bleWriteDataHashMap.containsKey(uuid) && bleWriteDataHashMap[uuid] != null) {\n            bleWriteDataHashMap[uuid]?.add(bleWriteData)\n        } else {\n            val list = Collections.synchronizedList(LinkedList<BleWriteData>())\n            list.add(bleWriteData)\n            bleWriteDataHashMap[uuid] = list\n        }\n    }\n\n    fun removeWriteCallback(uuid: String?, bleWriteCallback: BleWriteCallback? = null) {\n        if (bleWriteDataHashMap.containsKey(uuid)) {\n            if (bleWriteCallback != null) {\n                bleWriteDataHashMap[uuid]?.let { list ->\n                    synchronized(list) {\n                        val iterator = list.iterator()\n                        while (iterator.hasNext()) {\n                            if (iterator.next().bleWriteCallback == bleWriteCallback) {\n                                iterator.remove()\n                            }\n                        }\n                    }\n                }\n            } else {\n                bleWriteDataHashMap.remove(uuid)\n            }\n        }\n    }\n\n    fun removeAllWriteCallback() {\n        bleWriteDataHashMap.clear()\n    }\n\n    /**\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     *  与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     */\n    fun writeQueueData(\n        serviceUUID: String,\n        writeUUID: String,\n        operateRandomID: String,\n        dataArray: SparseArray<ByteArray>,\n        skipErrorPacketData: Boolean = false,\n        retryWriteCount: Int = 0,\n        retryDelayTime: Long = 0L,\n        writeType: Int?,\n        bleWriteCallback: BleWriteCallback\n    ) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleWriteCallback.callWriteFail(bleDevice, 0, dataArray.size(), NoBlePermissionException())\n            bleWriteCallback.callWriteComplete(bleDevice, false)\n            return\n        }\n        val randomStr =  generateRandomString()\n        if (dataArray.size() == 0) {\n            val exception = UnDefinedException(\n                getTaskId(writeUUID, operateRandomID + randomStr, 0) + \" -> 写数据失败，数据为空\"\n            )\n            BleLogger.e(exception.message)\n            bleWriteCallback.callWriteFail(bleDevice, 0, 0, exception)\n            bleWriteCallback.callWriteComplete(bleDevice, false)\n            return\n        }\n        addWriteJobScope.launch {\n            for (i in 0 until dataArray.size()) {\n                val data = dataArray.valueAt(i)\n                linkedBlockingTempQueue.put(\n                    BleWriteQueueData(\n                        operateRandomID = operateRandomID + randomStr,\n                        serviceUUID = serviceUUID,\n                        writeUUID = writeUUID,\n                        data = data,\n                        skipErrorPacketData = skipErrorPacketData,\n                        retryWriteCount = retryWriteCount,\n                        retryDelayTime = retryDelayTime,\n                        writeType = writeType,\n                    )\n                )\n            }\n            if (getWriteDataFromTemp()) {\n                startWriteQueueJob(bleWriteCallback)\n            }\n        }\n    }\n\n    fun writeData(\n        serviceUUID: String,\n        writeUUID: String,\n        operateRandomID: String,\n        dataArray: SparseArray<ByteArray>,\n        writeType: Int?,\n        bleWriteCallback: BleWriteCallback\n    ) {\n        if (!BleUtil.isPermission(getBleManager().getContext())) {\n            bleWriteCallback.callWriteFail(bleDevice, 0, dataArray.size(), NoBlePermissionException())\n            bleWriteCallback.callWriteComplete(bleDevice, false)\n            return\n        }\n        if (dataArray.size() == 0) {\n            val exception = UnDefinedException(\n                getTaskId(writeUUID, operateRandomID, 0) + \" -> 写数据失败，数据为空\"\n            )\n            BleLogger.e(exception.message)\n            bleWriteCallback.callWriteFail(bleDevice, 0, 0, exception)\n            bleWriteCallback.callWriteComplete(bleDevice, false)\n            return\n        }\n        for (i in 0 until dataArray.size()) {\n            val data = dataArray.valueAt(i)\n            if (data == null || data.isEmpty()) {\n                val exception = UnDefinedException(\n                    getTaskId(writeUUID, operateRandomID, i + 1) +\n                            \" -> 写数据失败，第${i + 1}个数据包为空\"\n                )\n                BleLogger.e(exception.message)\n                bleWriteCallback.callWriteFail(bleDevice, i + 1, dataArray.size(), exception)\n                bleWriteCallback.callWriteComplete(bleDevice, false)\n                return\n            }\n            val mtu = getBleOptions()?.mtu?: DEFAULT_MTU\n            //mtu长度包含了ATT的opcode一个字节以及ATT的handle2个字节\n            val maxWriteLength = mtu - 3\n            if (data.size > maxWriteLength) {\n                val exception = UnDefinedException(\"${getTaskId(writeUUID, operateRandomID\n                    , i + 1)} -> \" + \"写数据失败，第${i + 1}个数据包\" +\n                        \"长度(${data.size}) + 3大于设定Mtu($mtu)\")\n                BleLogger.e(exception.message)\n                bleWriteCallback.callWriteFail(bleDevice, i + 1, dataArray.size(), exception)\n                bleWriteCallback.callWriteComplete(bleDevice, false)\n                return\n            }\n        }\n\n        val characteristic = getCharacteristic(serviceUUID, writeUUID)\n\n        if (characteristic != null &&\n            (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE != 0 ||\n                    characteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0)\n        ) {\n            //循环数据包，生成对应的任务\n            for (i in 0 until dataArray.size()) {\n                val bleWriteData = BleWriteData(\n                    operateRandomID = operateRandomID + generateRandomString(),\n                    serviceUUID = serviceUUID,\n                    writeUUID = writeUUID,\n                    currentPackage = i + 1,\n                    totalPackage = dataArray.size(),\n                    data = dataArray.valueAt(i),\n                    isWriting = AtomicBoolean(false),\n                    bleWriteCallback = bleWriteCallback,\n                    writeType = writeType\n                )\n                addBleWriteData(writeUUID, bleWriteData)\n                startWriteJob(\n                    characteristic,\n                    (getTaskQueue(bleWriteData.writeUUID)?.getTaskList()?.size()?: 0) + i + 1,\n                    bleWriteData\n                )\n            }\n        } else {\n            val exception = UnSupportException(\"$writeUUID -> 写数据失败，此特性不支持写数据\")\n            BleLogger.e(exception.message)\n            bleWriteCallback.callWriteFail(bleDevice, 0, dataArray.size(), exception)\n            bleWriteCallback.callWriteComplete(bleDevice, false)\n        }\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun startWriteJob(\n        characteristic: BluetoothGattCharacteristic,\n        index: Int,\n        bleWriteData: BleWriteData\n    ) {\n        BleLogger.e(\"生成的operateRandomID: ${bleWriteData.operateRandomID}\")\n        var mContinuation: Continuation<Throwable?>? = null\n        val task = getTask(\n            getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID, bleWriteData.currentPackage),\n            getOperateTime() * index,\n            block = {\n                suspendCoroutine<Throwable?> { continuation ->\n                    mContinuation = continuation\n                    bleWriteData.isWriting = AtomicBoolean(true)\n                    startWrite(characteristic, bleWriteData)\n                }\n            },\n            interrupt = { _, throwable ->\n                try {\n                    try {\n                        mContinuation?.resume(throwable)\n                    } catch (e: Exception) {\n                        BleLogger.e(e.message)\n                    }\n                } catch (e: Exception) {\n                    BleLogger.e(e.message)\n                }\n            },\n            callback = { _, throwable ->\n                throwable?.let {\n                    BleLogger.e(it.message)\n                    if (it is TimeoutCancellationException || it is TimeoutCancelException) {\n                        bleWriteData.isWriteFail.set(true)\n                        val exception = TimeoutCancelException(\n                            getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID,\n                                bleWriteData.currentPackage) + \" -> \" +\n                                    \"第${bleWriteData.currentPackage}包数据写失败，超时\")\n                        //移除监听\n                        for ((key, value) in bleWriteDataHashMap) {\n                            if (!bleWriteData.writeUUID.equals(key, ignoreCase = true)) {\n                                continue\n                            }\n                            synchronized(value) {\n                                val iterator = value.iterator()\n                                while (iterator.hasNext()) {\n                                    val writeData = iterator.next()\n                                    if (writeData == bleWriteData) {\n                                        iterator.remove()\n                                    }\n                                }\n                            }\n                        }\n                        BleLogger.e(exception.message)\n                        bleWriteData.bleWriteCallback.callWriteFail(\n                            bleDevice,\n                            bleWriteData.currentPackage,\n                            bleWriteData.totalPackage,\n                            exception\n                        )\n                        //某个数据包写超时，后面的包不需再写\n                        cancelSameWriteJob(bleWriteData)\n//                        if (bleWriteData.currentPackage == bleWriteData.totalPackage) {\n//                            bleWriteData.bleWriteCallback.callWriteComplete(bleDevice, false)\n//                        }\n                    }\n                }\n            }\n        )\n        getTaskQueue(bleWriteData.writeUUID)?.addTask(task)\n    }\n\n    private fun startWriteQueueJob(\n        bleWriteCallback: BleWriteCallback\n    ) {\n        writeJobScope.launch {\n            //写数据间隔，写太快会导致设备忙碌而失败率高\n            delay(getOperateInterval())\n            val currentWriteData = linkedBlockingQueue.peek()\n            //如果这个数据包是空的，同时skipErrorPacketData为true，则跳过这个数据包，写下一个数据包\n            if (currentWriteData != null && currentWriteData.data.isEmpty() && currentWriteData.skipErrorPacketData) {\n                linkedBlockingQueue.poll()\n                if (linkedBlockingQueue.isNotEmpty() || getWriteDataFromTemp()) {\n                    startWriteQueueJob(bleWriteCallback)\n                }\n                return@launch\n            }\n            currentWriteData?.let { data ->\n                writeData(\n                    data.serviceUUID,\n                    data.writeUUID,\n                    data.operateRandomID,\n                    SparseArray<ByteArray>(1).apply {\n                        put(0, data.data)\n                    },\n                    data.writeType,\n                    object : BleWriteCallback() {\n                        override fun callWriteFail(\n                            bleDevice: BleDevice,\n                            current: Int,\n                            total: Int,\n                            throwable: Throwable\n                        ) {\n                            super.callWriteFail(bleDevice, current, total, throwable)\n                            bleWriteCallback.callWriteFail(bleDevice, current, total, throwable)\n                            launchInIOThread {\n                                currentRetryWriteCount.set(currentRetryWriteCount.get() + 1)\n                                val isCancelRetry = currentRetryWriteCount.get() > data.retryWriteCount\n                                BleLogger.e(\"写失败，指定重试${data.retryWriteCount}次，是否进入下一次重试：${!isCancelRetry}\")\n                                if (isCancelRetry) {\n                                    //如果重试了retryWriteCount次还是失败，则丢掉此包，写下一包\n                                    currentRetryWriteCount.set(0)\n                                    if (linkedBlockingQueue.isNotEmpty()) {\n                                        linkedBlockingQueue.poll()\n                                    }\n                                }\n//                                delay(200L + (max(currentRetryWriteCount.get(), 1) - 1)* 1000)\n                                delay(data.retryDelayTime)\n                                if (linkedBlockingQueue.isNotEmpty() || getWriteDataFromTemp()) {\n                                    startWriteQueueJob(bleWriteCallback)\n                                }\n                            }\n                        }\n\n                        override fun callWriteSuccess(\n                            bleDevice: BleDevice,\n                            current: Int,\n                            total: Int,\n                            justWrite: ByteArray\n                        ) {\n                            super.callWriteSuccess(bleDevice, current, total, justWrite)\n                            bleWriteCallback.callWriteSuccess(bleDevice, current, total, justWrite)\n                            currentRetryWriteCount.set(0)\n                            if (linkedBlockingQueue.isNotEmpty()) {\n                                linkedBlockingQueue.poll()\n                            }\n                            if (linkedBlockingQueue.isNotEmpty() || getWriteDataFromTemp()) {\n                                startWriteQueueJob(bleWriteCallback)\n                            }\n                        }\n                    }\n                )\n            }\n        }\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    @Suppress(\"DEPRECATION\")\n    private fun startWrite(\n        characteristic: BluetoothGattCharacteristic,\n        bleWriteData: BleWriteData\n    ) {\n        BleLogger.i(\n            getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID\n                , bleWriteData.currentPackage) + \" - > 开始写第${bleWriteData.currentPackage}包数据\")\n//        bleWriteData.bleWriteCallback.launchInIOThread {\n//            delay(500)\n//            onCharacteristicWrite(characteristic, BluetoothGatt.GATT_SUCCESS)\n//        }\n        //当支持WRITE_NO_RESPONSE和PROPERTY_WRITE时，用户可以指定写类型\n        //否则如果支持WRITE_NO_RESPONSE，则使用WRITE_TYPE_NO_RESPONSE，否则使用WRITE_TYPE_DEFAULT\n        val writeType =\n            if (characteristic.properties and\n                BluetoothGattCharacteristic.PROPERTY_WRITE != 0 &&\n                characteristic.properties and\n                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0 &&\n                bleWriteData.writeType != null) {\n                bleWriteData.writeType!!\n            } else if (characteristic.properties and\n                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) {\n                BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE\n            } else {\n                BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT\n            }\n        var errorCode: Int? = null\n        val success: Boolean? =\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                errorCode = getBluetoothGatt(bleDevice)?.writeCharacteristic(\n                    characteristic,\n                    bleWriteData.data,\n                    writeType\n                )\n                errorCode == 0\n            } else {\n                characteristic.writeType = writeType\n                characteristic.value = bleWriteData.data\n                getBluetoothGatt(bleDevice)?.writeCharacteristic(characteristic)\n            }\n        if (success != true) {\n            //whether the characteristic was successfully written to Value is\n            // BluetoothStatusCodes.SUCCESS = 0,\n            // BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6,\n            // BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED = 4,\n            // BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND = 8,\n            // BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED = 200,\n            // BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY = 201,\n            // BluetoothStatusCodes.ERROR_UNKNOWN = 2147483647,\n            // BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES = 13,\n            bleWriteData.isWriteFail.set(true)\n            val taskId = getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID\n                , bleWriteData.currentPackage)\n            val exception = UnDefinedException(\"$taskId -> 第${bleWriteData.currentPackage}包数据写\" +\n                    \"失败，错误可能是没有权限、未连接、服务未绑定、不可写、请求忙碌等，code = $errorCode\")\n            BleLogger.e(exception.message)\n            bleWriteData.bleWriteCallback.callWriteFail(\n                bleDevice,\n                bleWriteData.currentPackage,\n                bleWriteData.totalPackage,\n                exception\n            )\n            cancelSameWriteJob(bleWriteData)\n        }\n    }\n\n    /**\n     * 当向Characteristic写数据时会触发\n     */\n    fun onCharacteristicWrite(\n        characteristic: BluetoothGattCharacteristic?,\n        status: Int\n    ) {\n        for ((key, value) in bleWriteDataHashMap) {\n            if (!characteristic?.uuid?.toString().equals(key, ignoreCase = true)) {\n                continue\n            }\n            synchronized(value) {\n                val iterator = value.iterator()\n                while (iterator.hasNext()) {\n                    val bleWriteData = iterator.next()\n                    //只处理正在写并且没有失败的监听\n                    if (!bleWriteData.isWriting.get() || bleWriteData.isWriteFail.get()) {\n                        continue\n                    }\n                    val taskId = getTaskId(\n                        bleWriteData.writeUUID,\n                        bleWriteData.operateRandomID,\n                        bleWriteData.currentPackage\n                    )\n                    bleWriteData.isWriting = AtomicBoolean(false)\n                    if (status == BluetoothGatt.GATT_SUCCESS) {\n                        BleLogger.i(\n                            \"$taskId -> 第${bleWriteData.currentPackage}包\" +\n                                    \"数据写成功：\" + BleUtil.bytesToHex(bleWriteData.data)\n                        )\n                        cancelWriteJob(bleWriteData.writeUUID, taskId)\n\n                        bleWriteData.bleWriteCallback.callWriteSuccess(\n                            bleDevice,\n                            bleWriteData.currentPackage,\n                            bleWriteData.totalPackage,\n                            bleWriteData.data\n                        )\n                        if (bleWriteData.currentPackage == bleWriteData.totalPackage) {\n                            bleWriteData.bleWriteCallback.callWriteComplete(bleDevice, true)\n                            iterator.remove()\n                        }\n                    } else {\n                        val exception = UnDefinedException(\n                            \"$taskId -> 第${bleWriteData.currentPackage}包数据写\" +\n                                    \"失败，status = $status\"\n                        )\n                        BleLogger.e(exception.message)\n                        bleWriteData.bleWriteCallback.callWriteFail(\n                            bleDevice,\n                            bleWriteData.currentPackage,\n                            bleWriteData.totalPackage,\n                            exception\n                        )\n                        cancelSameWriteJob(bleWriteData)\n                        if (bleWriteData.currentPackage == bleWriteData.totalPackage) {\n                            iterator.remove()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun getCharacteristic(serviceUUID: String, writeUUID: String): BluetoothGattCharacteristic? {\n        val gattService = getBluetoothGatt(bleDevice)?.getService(UUID.fromString(serviceUUID))\n        return gattService?.getCharacteristic(UUID.fromString(writeUUID))\n    }\n\n    private fun getTaskId(uuid: String?, operateRandomID: String, currentPackage: Int) =\n        \"$WRITE_TASK_ID：$uuid($operateRandomID)($currentPackage)\"\n\n    /**\n     * 某个数据包写失败，后面的包不需再写\n     */\n    private fun cancelSameWriteJob(bleWriteData: BleWriteData) {\n        val currentPackage = bleWriteData.currentPackage\n        val totalPackage = bleWriteData.totalPackage\n        if (currentPackage == totalPackage) {\n            val taskId = getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID\n                , bleWriteData.currentPackage)\n            cancelWriteJob(bleWriteData.writeUUID, taskId)\n            bleWriteData.bleWriteCallback.callWriteComplete(bleDevice, false)\n        } else {\n            for (i in currentPackage..totalPackage) {\n                val id = getTaskId(bleWriteData.writeUUID, bleWriteData.operateRandomID, i)\n                cancelWriteJob(bleWriteData.writeUUID, id)\n                if (i == totalPackage) {\n                    bleWriteData.bleWriteCallback.callWriteComplete(bleDevice, false)\n                }\n            }\n        }\n    }\n\n    /**\n     * 取消写数据任务\n     */\n    private fun cancelWriteJob(writeUUID: String?, taskId: String) {\n        getTaskQueue(writeUUID?: \"\")?.removeTask(taskId)\n    }\n\n    /**\n     * 从临时队列中获取数据\n     */\n    private fun getWriteDataFromTemp(): Boolean {\n        if (linkedBlockingQueue.isEmpty() && linkedBlockingTempQueue.isNotEmpty()) {\n            linkedBlockingQueue.addAll(linkedBlockingTempQueue)\n            linkedBlockingTempQueue.clear()\n            return true\n        }\n        return false\n    }\n\n    /**\n     * 从[characters]中随机生成3个字符组成的字符串\n     */\n    private fun generateRandomString(): String {\n        return (1..3)\n            .map { characters.random() }\n            .joinToString(\"\")\n    }\n\n    fun cancelWriteQueueJob() {\n        removeAllWriteCallback()\n        linkedBlockingTempQueue.clear()\n        linkedBlockingQueue.clear()\n        getTaskQueueList()?.forEach { it?.removeAllTask() }\n        BleLogger.w(\"取消[${bleDevice.deviceAddress}]的所有写数据队列任务\")\n    }\n\n    override fun close() {\n        super.close()\n        removeAllWriteCallback()\n        currentRetryWriteCount.set(0)\n        addWriteJobScope.cancel()\n        writeJobScope.cancel()\n        linkedBlockingQueue.clear()\n        linkedBlockingTempQueue.clear()\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/base/BleBaseRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.request.base\n\nimport android.bluetooth.BluetoothGatt\nimport android.util.SparseArray\nimport com.bhm.ble.callback.*\nimport com.bhm.ble.data.BleDescriptorGetType\nimport com.bhm.ble.device.BleDevice\n\n\n/**\n * 抽象方法\n *\n * @author Buhuiming\n * @date 2023年05月22日 10时37分\n */\ninternal interface BleBaseRequest {\n\n    /**\n     * 开始扫描\n     */\n    fun startScan(\n        scanMillisTimeOut: Long?,\n        scanRetryCount: Int?,\n        scanRetryInterval: Long?,\n        bleScanCallback: BleScanCallback.() -> Unit\n    )\n\n    /**\n     * 是否扫描中\n     */\n    fun isScanning(): Boolean\n\n    /**\n     * 停止扫描\n     */\n    fun stopScan()\n\n    /**\n     * 扫描并连接，如果扫描到多个设备，则会连接第一个\n     */\n    fun startScanAndConnect(scanMillisTimeOut: Long?,\n                            scanRetryCount: Int?,\n                            scanRetryInterval: Long?,\n                            connectMillisTimeOut: Long?,\n                            connectRetryCount: Int?,\n                            connectRetryInterval: Long?,\n                            isForceConnect: Boolean = false,\n                            bleScanCallback: BleScanCallback.() -> Unit,\n                            bleConnectCallback: BleConnectCallback.() -> Unit)\n\n    /**\n     * 开始连接\n     */\n    fun connect(bleDevice: BleDevice,\n                connectMillisTimeOut: Long?,\n                connectRetryCount: Int?,\n                connectRetryInterval: Long?,\n                isForceConnect: Boolean = false,\n                bleConnectCallback: BleConnectCallback.() -> Unit)\n\n    /**\n     * 断开连接\n     */\n    fun disConnect(bleDevice: BleDevice)\n\n    /**\n     * 取消/停止连接\n     */\n    fun stopConnect(bleDevice: BleDevice)\n\n    /**\n     * 是否已连接\n     */\n    fun isConnected(bleDevice: BleDevice): Boolean\n\n    /**\n     * 获取设备的BluetoothGatt对象\n     */\n    fun getBluetoothGatt(bleDevice: BleDevice): BluetoothGatt?\n\n    /**\n     * notify\n     */\n    fun notify(bleDevice: BleDevice,\n               serviceUUID: String,\n               notifyUUID: String,\n               timeoutMillis: Long?,\n               bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n               bleNotifyCallback: BleNotifyCallback.() -> Unit)\n\n    /**\n     * stop notify\n     */\n    fun stopNotify(bleDevice: BleDevice,\n                   serviceUUID: String,\n                   notifyUUID: String,\n                   bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default): Boolean\n\n    /**\n     * indicate\n     */\n    fun indicate(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 indicateUUID: String,\n                 timeoutMillis: Long?,\n                 bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default,\n                 bleIndicateCallback: BleIndicateCallback.() -> Unit)\n\n    /**\n     * stop indicate\n     */\n    fun stopIndicate(bleDevice: BleDevice,\n                     serviceUUID: String,\n                     indicateUUID: String,\n                     bleDescriptorGetType: BleDescriptorGetType = BleDescriptorGetType.Default): Boolean\n\n    /**\n     * 读取信号值\n     */\n    fun readRssi(bleDevice: BleDevice,\n                 bleRssiCallback: BleRssiCallback.() -> Unit)\n\n    /**\n     * 设置mtu\n     */\n    fun setMtu(bleDevice:\n               BleDevice, mtu: Int,\n               bleMtuChangedCallback: BleMtuChangedCallback.() -> Unit)\n\n    /**\n     * 设置设备的传输优先级\n     * connectionPriority 必须是 [BluetoothGatt.CONNECTION_PRIORITY_BALANCED]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER]的其中一个\n     *\n     */\n    fun setConnectionPriority(bleDevice: BleDevice, connectionPriority: Int): Boolean\n\n    /**\n     * 读特征值数据\n     */\n    fun readData(bleDevice: BleDevice,\n                 serviceUUID: String,\n                 readUUID: String,\n                 bleReadCallback: BleReadCallback.() -> Unit)\n\n    /**\n     * 写数据\n     */\n    fun writeData(bleDevice: BleDevice,\n                  serviceUUID: String,\n                  writeUUID: String,\n                  dataArray: SparseArray<ByteArray>,\n                  writeType: Int? = null,\n                  bleWriteCallback: BleWriteCallback.() -> Unit)\n\n    /**\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     * 与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     *\n     * @param skipErrorPacketData 是否跳过数据长度为0的数据包\n     * @param retryWriteCount 写失败后重试的次数\n     */\n    fun writeQueueData(bleDevice: BleDevice,\n                       serviceUUID: String,\n                       writeUUID: String,\n                       dataArray: SparseArray<ByteArray>,\n                       skipErrorPacketData: Boolean = false,\n                       retryWriteCount: Int = 0,\n                       retryDelayTime: Long = 0L,\n                       writeType: Int? = null,\n                       bleWriteCallback: BleWriteCallback.() -> Unit)\n\n    /**\n     * 取消写操作\n     */\n    fun cancelWriting(bleDevice: BleDevice)\n\n    /**\n     * 获取所有已连接设备集合\n     */\n    fun getAllConnectedDevice(): MutableList<BleDevice>\n\n    /**\n     * 添加设备的连接状态发生变化、indicate/notify收到数据、mtu改变的回调\n     */\n    fun addBleEventCallback(bleDevice: BleDevice, bleEventCallback: BleEventCallback.() -> Unit)\n\n    /**\n     * 移除该设备的连接回调\n     */\n    fun removeBleConnectCallback(bleDevice: BleDevice)\n\n    /**\n     * 替换该设备的连接回调\n     */\n    fun replaceBleConnectCallback(bleDevice: BleDevice, bleConnectCallback: BleConnectCallback.() -> Unit)\n\n    /**\n     * 移除该设备的Indicate回调\n     */\n    fun removeBleIndicateCallback(bleDevice: BleDevice, indicateUUID: String)\n\n    /**\n     * 移除该设备的Notify回调\n     */\n    fun removeBleNotifyCallback(bleDevice: BleDevice, notifyUUID: String)\n\n    /**\n     * 移除该设备的Read回调\n     */\n    fun removeBleReadCallback(bleDevice: BleDevice, readUUID: String)\n\n    /**\n     * 移除该设备的MtuChanged回调\n     */\n    fun removeBleMtuChangedCallback(bleDevice: BleDevice)\n\n    /**\n     * 移除该设备的Rssi回调\n     */\n    fun removeBleRssiCallback(bleDevice: BleDevice)\n\n    /**\n     * 移除该设备的Write回调\n     */\n    fun removeBleWriteCallback(\n        bleDevice: BleDevice,\n        writeUUID: String,\n        bleWriteCallback: BleWriteCallback? = null\n    )\n\n    /**\n     * 移除该设备回调，BleConnectCallback除外\n     */\n    fun removeAllCharacterCallback(bleDevice: BleDevice)\n\n    /**\n     * 移除该设备Event回调\n     */\n    fun removeBleEventCallback(bleDevice: BleDevice)\n\n    /**\n     * 断开所有设备的连接\n     */\n    fun disConnectAll()\n\n    /**\n     * 移除该设备的Scan回调\n     */\n    fun removeBleScanCallback()\n\n    /**\n     * 添加一个新的Scan回调\n     */\n    fun addBleScanCallback(bleScanCallback: BleScanCallback.() -> Unit)\n\n    /**\n     * 注册系统蓝牙广播\n     */\n    fun registerBluetoothStateReceiver(bluetoothCallback: BluetoothCallback.() -> Unit)\n\n    /**\n     * 取消注册系统蓝牙广播\n     */\n    fun unRegisterBluetoothStateReceiver()\n\n    /**\n     * 断开某个设备的连接 释放资源\n     */\n    fun close(bleDevice: BleDevice)\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    fun closeAll()\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/base/BleRequestImp.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\n\npackage com.bhm.ble.request.base\n\nimport android.bluetooth.BluetoothAdapter\nimport android.bluetooth.BluetoothGatt\nimport android.content.Context\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.util.SparseArray\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.callback.BleConnectCallback\nimport com.bhm.ble.callback.BleEventCallback\nimport com.bhm.ble.callback.BleIndicateCallback\nimport com.bhm.ble.callback.BleMtuChangedCallback\nimport com.bhm.ble.callback.BleNotifyCallback\nimport com.bhm.ble.callback.BleReadCallback\nimport com.bhm.ble.callback.BleRssiCallback\nimport com.bhm.ble.callback.BleScanCallback\nimport com.bhm.ble.callback.BleWriteCallback\nimport com.bhm.ble.callback.BluetoothCallback\nimport com.bhm.ble.data.BleConnectFailType\nimport com.bhm.ble.data.BleDescriptorGetType\nimport com.bhm.ble.data.UnConnectedException\nimport com.bhm.ble.data.UnDefinedException\nimport com.bhm.ble.device.BleConnectedDeviceManager\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.receiver.BluetoothReceiver\nimport com.bhm.ble.request.BleScanRequest\nimport com.bhm.ble.log.BleLogger\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancel\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n\n/**\n * 操作实现\n *\n * @author Buhuiming\n * @date 2023年05月22日 10时41分\n */\ninternal class BleRequestImp private constructor() : BleBaseRequest {\n\n    private val mainScope = MainScope()\n\n    private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n\n    private val defaultScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)\n\n    private val bleConnectedDeviceManager = BleConnectedDeviceManager.get()\n\n    private var bluetoothReceiver: BluetoothReceiver? = null\n\n    companion object {\n\n        private var instance: BleRequestImp? = BleRequestImp()\n\n        fun get(): BleRequestImp {\n            return instance?: BleRequestImp()\n        }\n    }\n\n    fun getMainScope() = mainScope\n\n    fun getIOScope() = ioScope\n\n    fun getDefaultScope() = defaultScope\n\n    /**\n     * 开始扫描\n     */\n    override fun startScan(\n        scanMillisTimeOut: Long?,\n        scanRetryCount: Int?,\n        scanRetryInterval: Long?,\n        bleScanCallback: BleScanCallback.() -> Unit\n    ) {\n        val callback = BleScanCallback()\n        callback.apply(bleScanCallback)\n        BleScanRequest.get().startScan(\n            scanMillisTimeOut,\n            scanRetryCount,\n            scanRetryInterval,\n            callback\n        )\n    }\n\n    /**\n     * 是否扫描中\n     */\n    override fun isScanning(): Boolean {\n        return BleScanRequest.get().isScanning()\n    }\n\n    /**\n     * 停止扫描\n     */\n    override fun stopScan() {\n        BleScanRequest.get().stopScan()\n    }\n\n    /**\n     * 扫描并连接，如果扫描到多个设备，则会连接第一个\n     */\n    override fun startScanAndConnect(scanMillisTimeOut: Long?,\n                                     scanRetryCount: Int?,\n                                     scanRetryInterval: Long?,\n                                     connectMillisTimeOut: Long?,\n                                     connectRetryCount: Int?,\n                                     connectRetryInterval: Long?,\n                                     isForceConnect: Boolean,\n                                     bleScanCallback: BleScanCallback.() -> Unit,\n                                     bleConnectCallback: BleConnectCallback.() -> Unit) {\n        val scanCallback = BleScanCallback()\n        scanCallback.apply(bleScanCallback)\n        val connectCallback = BleConnectCallback()\n        connectCallback.apply(bleConnectCallback)\n\n        var device: BleDevice? = null\n        connectCallback.launchInMainThread {\n            suspendCoroutine { continuation ->\n                startScan(\n                    scanMillisTimeOut,\n                    scanRetryCount,\n                    scanRetryInterval,\n                ) {\n                    onScanStart {\n                        scanCallback.callScanStart()\n                    }\n                    onLeScan { bleDevice, currentScanCount ->\n                        scanCallback.callLeScan(bleDevice, currentScanCount)\n                        if (device == null) {\n                            device = bleDevice\n                            stopScan()\n                        }\n                    }\n                    onLeScanDuplicateRemoval { bleDevice, currentScanCount ->\n                        scanCallback.callLeScanDuplicateRemoval(bleDevice, currentScanCount)\n                    }\n                    onScanFail {\n                        scanCallback.callScanFail(it)\n                    }\n                    onScanComplete { bleDeviceList, bleDeviceDuplicateRemovalList ->\n                        scanCallback.callScanComplete(bleDeviceList, bleDeviceDuplicateRemovalList)\n                        try {\n                            continuation.resume(device)\n                        } catch (e: Exception) {\n                            BleLogger.e(e.message)\n                        }\n                    }\n                }\n            }\n            if (device == null || device?.deviceInfo == null) {\n                connectCallback.callConnectFail(\n                    BleDevice(null,\n                        \"\",\n                        \"\",\n                        0,\n                        0,\n                        null,\n                        null,\n                    ), BleConnectFailType.ScanNullableBluetoothDevice)\n                return@launchInMainThread\n            }\n            connect(\n                device!!,\n                connectMillisTimeOut,\n                connectRetryCount,\n                connectRetryInterval,\n                isForceConnect\n            ) {\n                onConnectStart { bleDevice ->\n                    connectCallback.callConnectStart(bleDevice)\n                    bleConnectedDeviceManager.getBleConnectedDevice(device!!)?.getBleEventCallback()?.callConnectStart(bleDevice)\n                }\n                onConnectSuccess { bleDevice, gatt ->\n                    connectCallback.callConnectSuccess(bleDevice, gatt)\n                    bleConnectedDeviceManager.getBleConnectedDevice(device!!)?.getBleEventCallback()?.callConnected(bleDevice, gatt)\n                }\n                onDisConnecting { isActiveDisConnected, bleDevice, gatt, status ->\n                    connectCallback.callDisConnecting(isActiveDisConnected, bleDevice, gatt, status)\n                    bleConnectedDeviceManager.getBleConnectedDevice(device!!)?.getBleEventCallback()?.callDisConnecting(\n                        isActiveDisConnected, bleDevice, gatt, status\n                    )\n                }\n                onDisConnected { isActiveDisConnected, bleDevice, gatt, status ->\n                    connectCallback.callDisConnected(isActiveDisConnected, bleDevice, gatt, status)\n                    bleConnectedDeviceManager.getBleConnectedDevice(device!!)?.getBleEventCallback()?.callDisConnected(\n                        isActiveDisConnected, bleDevice, gatt, status\n                    )\n                }\n                onConnectFail { bleDevice, connectFailType ->\n                    connectCallback.callConnectFail(bleDevice, connectFailType)\n                    bleConnectedDeviceManager.getBleConnectedDevice(device!!)?.getBleEventCallback()?.callConnectFail(\n                        bleDevice, connectFailType\n                    )\n                }\n            }\n        }\n    }\n\n    /**\n     * 开始连接\n     */\n    override fun connect(\n        bleDevice: BleDevice,\n        connectMillisTimeOut: Long?,\n        connectRetryCount: Int?,\n        connectRetryInterval: Long?,\n        isForceConnect: Boolean,\n        bleConnectCallback: BleConnectCallback.() -> Unit\n    ) {\n        val callback = BleConnectCallback()\n        callback.apply(bleConnectCallback)\n        val request = bleConnectedDeviceManager.buildBleConnectedDevice(bleDevice)\n        request?.let {\n            it.connect(\n                connectMillisTimeOut,\n                connectRetryCount,\n                connectRetryInterval,\n                isForceConnect,\n                callback\n            )\n            return\n        }\n        val exception = UnDefinedException(\"${bleDevice.deviceAddress} -> 连接失败，BleConnectedDevice为空\")\n        BleLogger.e(exception.message)\n        callback.callConnectFail(bleDevice, BleConnectFailType.ConnectException(exception))\n        bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)?.getBleEventCallback()?.callConnectFail(\n            bleDevice, BleConnectFailType.ConnectException(exception))\n    }\n\n    /**\n     * 断开连接\n     */\n    override fun disConnect(bleDevice: BleDevice) {\n        bleConnectedDeviceManager\n            .getBleConnectedDevice(bleDevice)\n            ?.disConnect()\n    }\n\n    /**\n     * 取消/停止连接\n     */\n    override fun stopConnect(bleDevice: BleDevice) {\n        bleConnectedDeviceManager\n            .getBleConnectedDevice(bleDevice)\n            ?.stopConnect()\n    }\n\n    /**\n     * 是否已连接\n     */\n    override fun isConnected(bleDevice: BleDevice): Boolean {\n        return bleConnectedDeviceManager.isContainDevice(bleDevice)\n    }\n\n    /**\n     * 移除该设备的连接回调\n     */\n    override fun removeBleConnectCallback(bleDevice: BleDevice) {\n        bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)?.removeBleConnectCallback()\n    }\n\n    /**\n     * 替换该设备的连接回调\n     */\n    override fun replaceBleConnectCallback(bleDevice: BleDevice, bleConnectCallback: BleConnectCallback.() -> Unit) {\n        val callback = BleConnectCallback()\n        callback.apply(bleConnectCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.replaceBleConnectCallback(callback)\n    }\n\n    /**\n     * 获取设备的BluetoothGatt对象\n     */\n    override fun getBluetoothGatt(bleDevice: BleDevice): BluetoothGatt? {\n        return bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)?.getBluetoothGatt()\n    }\n\n    /**\n     * notify\n     */\n    override fun notify(bleDevice: BleDevice,\n                        serviceUUID: String,\n                        notifyUUID: String,\n                        timeoutMillis: Long?,\n                        bleDescriptorGetType: BleDescriptorGetType,\n                        bleNotifyCallback: BleNotifyCallback.() -> Unit) {\n        val callback = BleNotifyCallback()\n        callback.apply(bleNotifyCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.enableCharacteristicNotify(\n                serviceUUID,\n                notifyUUID,\n                timeoutMillis,\n                bleDescriptorGetType,\n                callback\n            )\n            return\n        }\n        val exception = UnConnectedException(\"$notifyUUID -> 设置Notify失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callNotifyFail(bleDevice, notifyUUID, exception)\n    }\n\n    /**\n     * stop notify\n     */\n    override fun stopNotify(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        notifyUUID: String,\n        bleDescriptorGetType: BleDescriptorGetType\n    ): Boolean {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            return it.disableCharacteristicNotify(\n                serviceUUID,\n                notifyUUID,\n                bleDescriptorGetType\n            )\n        }\n        BleLogger.e(\"$notifyUUID -> StopNotify失败，设备未连接\")\n        return false\n    }\n\n    /**\n     * indicate\n     */\n    override fun indicate(bleDevice: BleDevice,\n                          serviceUUID: String,\n                          indicateUUID: String,\n                          timeoutMillis: Long?,\n                          bleDescriptorGetType: BleDescriptorGetType,\n                          bleIndicateCallback: BleIndicateCallback.() -> Unit) {\n        val callback = BleIndicateCallback()\n        callback.apply(bleIndicateCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.enableCharacteristicIndicate(\n                serviceUUID,\n                indicateUUID,\n                timeoutMillis,\n                bleDescriptorGetType,\n                callback\n            )\n            return\n        }\n        val exception = UnConnectedException(\"$indicateUUID -> 设置Indicate失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callIndicateFail(bleDevice, indicateUUID, exception)\n    }\n\n    /**\n     * stop indicate\n     */\n    override fun stopIndicate(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        indicateUUID: String,\n        bleDescriptorGetType: BleDescriptorGetType\n    ): Boolean {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            return it.disableCharacteristicIndicate(\n                serviceUUID,\n                indicateUUID,\n                bleDescriptorGetType\n            )\n        }\n        return false\n    }\n\n    /**\n     * 读取信号值\n     */\n    override fun readRssi(bleDevice: BleDevice, bleRssiCallback: BleRssiCallback.() -> Unit) {\n        val callback = BleRssiCallback()\n        callback.apply(bleRssiCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.readRemoteRssi(callback)\n            return\n        }\n        val exception = UnConnectedException(\"${bleDevice.deviceAddress} -> 读取Rssi失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callRssiFail(bleDevice, exception)\n    }\n\n    /**\n     * 设置mtu\n     */\n    override fun setMtu(bleDevice: BleDevice,\n                        mtu: Int,\n                        bleMtuChangedCallback: BleMtuChangedCallback.() -> Unit) {\n        val callback = BleMtuChangedCallback()\n        callback.apply(bleMtuChangedCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.setMtu(mtu, callback)\n            return\n        }\n        val exception = UnConnectedException(\"${bleDevice.deviceAddress} -> 设置mtu失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callSetMtuFail(bleDevice, exception)\n    }\n\n    /**\n     * 设置设备的传输优先级\n     * connectionPriority 必须是 [BluetoothGatt.CONNECTION_PRIORITY_BALANCED]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_HIGH]、\n     * [BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER]的其中一个\n     *\n     */\n    override fun setConnectionPriority(bleDevice: BleDevice, connectionPriority: Int): Boolean {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        return request?.setConnectionPriority(connectionPriority)?: false\n    }\n\n    /**\n     * 读特征值数据\n     */\n    override fun readData(bleDevice: BleDevice,\n                          serviceUUID: String,\n                          readUUID: String,\n                          bleReadCallback: BleReadCallback.() -> Unit) {\n        val callback = BleReadCallback()\n        callback.apply(bleReadCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.readData(serviceUUID, readUUID, callback)\n            return\n        }\n        val exception = UnConnectedException(\"$readUUID -> 读特征值数据失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callReadFail(bleDevice, exception)\n    }\n\n    /**\n     * 写数据\n     * 注意：因为分包后每一个包，可能是包含完整的协议，所以分包由业务层处理，组件只会根据包的长度和mtu值对比后是否拦截\n     */\n    override fun writeData(bleDevice: BleDevice,\n                           serviceUUID: String,\n                           writeUUID: String,\n                           dataArray: SparseArray<ByteArray>,\n                           writeType: Int?,\n                           bleWriteCallback: BleWriteCallback.() -> Unit) {\n        val callback = BleWriteCallback()\n        callback.apply(bleWriteCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.writeData(serviceUUID, writeUUID, dataArray, writeType, callback)\n            return\n        }\n        val exception = UnConnectedException(\"$writeUUID -> 写数据失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callWriteFail(bleDevice, 0, dataArray.size(), exception)\n        callback.callWriteComplete(bleDevice, false)\n    }\n\n    /**\n     * 放入一个写队列，写成功，则从队列中取下一个数据，写失败，则重试[retryWriteCount]次\n     * 与[writeData]的区别在于，[writeData]写成功，则从队列中取下一个数据，写失败，则不再继续写后面的数据\n     *\n     * @param skipErrorPacketData 是否跳过数据长度为0的数据包\n     * @param retryWriteCount 写失败后重试的次数\n     */\n    override fun writeQueueData(\n        bleDevice: BleDevice,\n        serviceUUID: String,\n        writeUUID: String,\n        dataArray: SparseArray<ByteArray>,\n        skipErrorPacketData: Boolean,\n        retryWriteCount: Int,\n        retryDelayTime: Long,\n        writeType: Int?,\n        bleWriteCallback: BleWriteCallback.() -> Unit\n    ) {\n        val callback = BleWriteCallback()\n        callback.apply(bleWriteCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.writeQueueData(\n                serviceUUID,\n                writeUUID,\n                dataArray,\n                skipErrorPacketData,\n                retryWriteCount,\n                retryDelayTime,\n                writeType,\n                callback\n            )\n            return\n        }\n        val exception = UnConnectedException(\"$writeUUID -> 写数据失败，设备未连接\")\n        BleLogger.e(exception.message)\n        callback.callWriteFail(bleDevice, 0, dataArray.size(), exception)\n        callback.callWriteComplete(bleDevice, false)\n    }\n\n    /**\n     * 取消写操作\n     */\n    override fun cancelWriting(bleDevice: BleDevice) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.let {\n            it.cancelWriting()\n            return\n        }\n    }\n\n    /**\n     * 获取所有已连接设备集合\n     */\n    override fun getAllConnectedDevice(): MutableList<BleDevice> {\n        return bleConnectedDeviceManager.getAllConnectedDevice()\n    }\n\n    /**\n     * 添加设备的连接状态发生变化、indicate/notify收到数据、mtu改变的回调\n     */\n    override fun addBleEventCallback(bleDevice: BleDevice, bleEventCallback: BleEventCallback.() -> Unit) {\n        val callback = BleEventCallback()\n        callback.apply(bleEventCallback)\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.addBleEventCallback(callback)\n    }\n\n    /**\n     * 移除该设备的Indicate回调\n     */\n    override fun removeBleIndicateCallback(bleDevice: BleDevice, indicateUUID: String) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeIndicateCallback(indicateUUID)\n    }\n\n    /**\n     * 移除该设备的Notify回调\n     */\n    override fun removeBleNotifyCallback(bleDevice: BleDevice, notifyUUID: String) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeNotifyCallback(notifyUUID)\n    }\n\n    /**\n     * 移除该设备的Read回调\n     */\n    override fun removeBleReadCallback(bleDevice: BleDevice, readUUID: String) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeReadCallback(readUUID)\n    }\n\n    /**\n     * 移除该设备的MtuChanged回调\n     */\n    override fun removeBleMtuChangedCallback(bleDevice: BleDevice) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeMtuChangedCallback()\n    }\n\n    /**\n     * 移除该设备的Rssi回调\n     */\n    override fun removeBleRssiCallback(bleDevice: BleDevice) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeRssiCallback()\n    }\n\n    /**\n     * 移除该设备的Write回调\n     */\n    override fun removeBleWriteCallback(\n        bleDevice: BleDevice,\n        writeUUID: String,\n        bleWriteCallback: BleWriteCallback?\n    ) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeWriteCallback(writeUUID, bleWriteCallback)\n    }\n\n    /**\n     * 移除该设备的Scan回调\n     */\n    override fun removeBleScanCallback() {\n        BleScanRequest.get().removeBleScanCallback()\n    }\n\n    /**\n     * 添加一个新的设备扫描回调\n     */\n    override fun addBleScanCallback(bleScanCallback: BleScanCallback.() -> Unit) {\n        val callback = BleScanCallback()\n        callback.apply(bleScanCallback)\n        BleScanRequest.get().addBleScanCallback(callback)\n    }\n\n    /**\n     * 移除该设备回调，BleConnectCallback除外\n     */\n    override fun removeAllCharacterCallback(bleDevice: BleDevice) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeAllCharacterCallback()\n    }\n\n    /**\n     * 移除该设备Event回调\n     */\n    override fun removeBleEventCallback(bleDevice: BleDevice) {\n        val request = bleConnectedDeviceManager.getBleConnectedDevice(bleDevice)\n        request?.removeBleEventCallback()\n    }\n\n    /**\n     * 断开所有设备的连接\n     */\n    override fun disConnectAll() {\n        bleConnectedDeviceManager.disConnectAll()\n    }\n\n    /**\n     * 注册系统蓝牙广播\n     */\n    override fun registerBluetoothStateReceiver(bluetoothCallback: BluetoothCallback.() -> Unit) {\n        if (bluetoothReceiver == null) {\n            bluetoothReceiver = BluetoothReceiver()\n            val callback = BluetoothCallback()\n            callback.apply(bluetoothCallback)\n            bluetoothReceiver?.setBluetoothCallback(callback)\n            val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                BleManager.get().getContext()?.registerReceiver(\n                    bluetoothReceiver,\n                    filter,\n                    Context.RECEIVER_EXPORTED\n                )\n            } else {\n                BleManager.get().getContext()?.registerReceiver(\n                    bluetoothReceiver,\n                    filter\n                )\n            }\n            BleLogger.d(\"注册系统蓝牙广播\")\n        }\n    }\n\n    /**\n     * 取消注册系统蓝牙广播\n     */\n    override fun unRegisterBluetoothStateReceiver() {\n        //取消注册系统蓝牙广播\n        bluetoothReceiver?.let {\n            BleManager.get().getContext()?.unregisterReceiver(it)\n        }\n        bluetoothReceiver = null\n        BleLogger.d(\"取消注册系统蓝牙广播\")\n    }\n\n    /**\n     * 断开所有连接 释放资源\n     */\n    override fun closeAll() {\n        mainScope.cancel()\n        ioScope.cancel()\n        defaultScope.cancel()\n        unRegisterBluetoothStateReceiver()\n        BleScanRequest.get().close()\n        bleConnectedDeviceManager.closeAll()\n        instance = null\n    }\n\n    /**\n     * 断开某个设备的连接 释放资源\n     */\n    override fun close(bleDevice: BleDevice) {\n        bleConnectedDeviceManager.close(bleDevice)\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/base/BleTaskQueueRequest.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.request.base\n\nimport com.bhm.ble.control.BleTaskQueue\nimport com.bhm.ble.data.BleTaskQueueType\nimport com.bhm.ble.data.Constants.DEFAULT_TASK_QUEUE_TYPE\nimport com.bhm.ble.device.BleConnectedDeviceManager\nimport com.bhm.ble.device.BleDevice\nimport java.util.concurrent.ConcurrentHashMap\n\n\n/**\n * 封装任务队列的Request\n *\n * @author Buhuiming\n * @date 2023年06月13日 16时03分\n */\ninternal open class BleTaskQueueRequest(\n    private val bleDevice: BleDevice,\n    private val tag: String\n) : Request() {\n\n    private var bleTaskQueueHashMap: ConcurrentHashMap<String, BleTaskQueue>? = null\n\n    private val bleTaskQueueType = getBleOptions()?.taskQueueType?: DEFAULT_TASK_QUEUE_TYPE\n\n\n    private var operateBleTaskQueue: BleTaskQueue? = null\n\n    init {\n        when (bleTaskQueueType) {\n            BleTaskQueueType.Operate -> operateBleTaskQueue = BleTaskQueue(bleDevice.deviceAddress + tag)\n            BleTaskQueueType.Independent ->  bleTaskQueueHashMap = ConcurrentHashMap()\n            else -> {}\n        }\n    }\n\n    fun getTaskQueue(uuid: String): BleTaskQueue? {\n        return when (bleTaskQueueType) {\n            BleTaskQueueType.Default ->\n                BleConnectedDeviceManager.get()\n                    .getBleConnectedDevice(bleDevice)\n                    ?.getShareBleTaskQueue()\n            BleTaskQueueType.Operate -> operateBleTaskQueue\n            BleTaskQueueType.Independent -> {\n                if (bleTaskQueueHashMap?.containsKey(uuid) == true) {\n                    bleTaskQueueHashMap?.get(uuid)\n                } else {\n                    val independentBleTaskQueue = BleTaskQueue(bleDevice.deviceAddress + tag)\n                    bleTaskQueueHashMap?.put(uuid, independentBleTaskQueue)\n                    independentBleTaskQueue\n                }\n            }\n        }\n    }\n\n    fun getTaskQueueList(): List<BleTaskQueue?>? {\n        return when (bleTaskQueueType) {\n            BleTaskQueueType.Default ->\n                arrayListOf(\n                    BleConnectedDeviceManager.get()\n                        .getBleConnectedDevice(bleDevice)\n                        ?.getShareBleTaskQueue()\n                )\n            BleTaskQueueType.Operate -> arrayListOf(operateBleTaskQueue)\n            BleTaskQueueType.Independent -> {\n                bleTaskQueueHashMap?.values?.toList()\n            }\n        }\n    }\n\n    open fun close() {\n        when (bleTaskQueueType) {\n            BleTaskQueueType.Operate -> operateBleTaskQueue?.clear()\n            BleTaskQueueType.Independent -> {\n                bleTaskQueueHashMap?.forEach {\n                    it.value.clear()\n                }\n                bleTaskQueueHashMap?.clear()\n                bleTaskQueueHashMap = null\n            }\n            else -> {}\n        }\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/request/base/Request.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.request.base\n\nimport android.bluetooth.BluetoothGatt\nimport android.bluetooth.BluetoothGattCharacteristic\nimport com.bhm.ble.BleManager\nimport com.bhm.ble.control.BleTask\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_INTERVAL\nimport com.bhm.ble.data.Constants.DEFAULT_OPERATE_MILLIS_TIMEOUT\nimport com.bhm.ble.device.BleConnectedDevice\nimport com.bhm.ble.device.BleConnectedDeviceManager\nimport com.bhm.ble.device.BleDevice\nimport java.util.UUID\n\n\n/**\n * 所有Request的基类\n * @author Buhuiming\n * @date 2023年05月26日 13时59分\n */\ninternal abstract class Request {\n\n    /**\n     * 获取BleManager\n     */\n    fun getBleManager() = BleManager.get()\n\n    /**\n     * 获取BleOptions\n     */\n    fun getBleOptions() = getBleManager().getOptions()\n\n    /**\n     * 获取操作时间\n     */\n    fun getOperateTime(): Long {\n        var operateTime = getBleOptions()?.operateMillisTimeOut ?: DEFAULT_OPERATE_MILLIS_TIMEOUT\n        if (operateTime <= 0) {\n            operateTime = DEFAULT_OPERATE_MILLIS_TIMEOUT\n        }\n        return operateTime\n    }\n\n    /**\n     * 获取操作间隔\n     */\n    fun getOperateInterval(): Long {\n        var operateInterval = getBleOptions()?.operateInterval ?: DEFAULT_OPERATE_INTERVAL\n        if (operateInterval <= 0) {\n            operateInterval = DEFAULT_OPERATE_INTERVAL\n        }\n        return operateInterval\n    }\n\n    /**\n     * 生成一个任务\n     */\n    fun getTask(\n        taskId: String,\n        block: suspend BleTask.() -> Unit,\n        interrupt: (task: BleTask, throwable: Throwable?) -> Unit,\n        callback: (task: BleTask, throwable: Throwable?) -> Unit\n    ): BleTask {\n        return getTask(\n            taskId,\n            getOperateTime(),\n            block,\n            interrupt,\n            callback\n        )\n    }\n\n    /**\n     * 生成一个任务\n     */\n    fun getTask(\n        taskId: String,\n        durationTimeMillis: Long,\n        block: suspend BleTask.() -> Unit,\n        interrupt: (task: BleTask, throwable: Throwable?) -> Unit,\n        callback: (task: BleTask, throwable: Throwable?) -> Unit\n    ): BleTask {\n        return BleTask(\n            taskId = taskId,\n            durationTimeMillis = durationTimeMillis,\n            operateInterval = getOperateInterval(),\n            callInMainThread = false,\n            autoDoNextTask = true,\n            block = block,\n            interrupt = interrupt,\n            callback = callback\n        )\n    }\n\n    /**\n     * 获取连接设备\n     */\n    fun getBleConnectedDevice(bleDevice: BleDevice): BleConnectedDevice? {\n        return BleConnectedDeviceManager.get().getBleConnectedDevice(bleDevice)\n    }\n\n    /**\n     * 获取BluetoothGatt\n     */\n    fun getBluetoothGatt(bleDevice: BleDevice): BluetoothGatt? {\n        return getBleConnectedDevice(bleDevice)?.getBluetoothGatt()\n    }\n\n    /**\n     * 获取Characteristic\n     */\n    fun getCharacteristic(bleDevice: BleDevice,\n                          serviceUUID: String,\n                          characteristicUUID: String\n    ): BluetoothGattCharacteristic? {\n        val gattService = getBluetoothGatt(bleDevice)?.getService(UUID.fromString(serviceUUID))\n        return gattService?.getCharacteristic(UUID.fromString(characteristicUUID))\n    }\n}"
  },
  {
    "path": "ble/src/main/java/com/bhm/ble/utils/BleUtil.kt",
    "content": "/*\n * Copyright (c) 2022-2032 buhuiming\n * 不能修改和删除上面的版权声明\n * 此代码属于buhuiming编写，在未经允许的情况下不得传播复制\n */\npackage com.bhm.ble.utils\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.bluetooth.le.ScanResult\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport android.location.LocationManager\nimport android.os.Build\nimport android.util.SparseArray\nimport com.bhm.ble.device.BleDevice\nimport com.bhm.ble.log.BleLogger\nimport kotlin.math.roundToInt\n\n\n/**\n * 工具类\n *\n * @author Buhuiming\n * @date 2023年05月19日 14时04分\n */\nobject BleUtil {\n\n    /**\n     * 系统GPS是否打开\n     * @return true = 打开\n     */\n    fun isGpsOpen(context: Context?): Boolean {\n        val locationManager = context?.getSystemService(Context.LOCATION_SERVICE) as LocationManager?\n        return locationManager?.isProviderEnabled(LocationManager.GPS_PROVIDER) == true\n                || locationManager?.isProviderEnabled(LocationManager.NETWORK_PROVIDER) == true\n    }\n\n    /**\n     * 判断是否拥有[permission]权限\n     * @return true = 拥有该权限\n     */\n    private fun isPermission(context: Context?, permission: String): Boolean {\n        return context?.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED\n    }\n\n    /**\n     * 设备是否支持蓝牙\n     *  @return true = 支持\n     */\n    fun isBleSupport(context: Context?): Boolean {\n        return context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)?: false\n    }\n\n    /**\n     * 判断是否拥有蓝牙权限\n     * @return true = 拥有该权限\n     */\n    fun isPermission(context: Context?): Boolean {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&\n//            isPermission(context?.applicationContext,\n//                Manifest.permission.ACCESS_FINE_LOCATION) &&\n//            isPermission(context?.applicationContext,\n//                Manifest.permission.ACCESS_COARSE_LOCATION) &&\n            isPermission(context?.applicationContext,\n                Manifest.permission.BLUETOOTH_SCAN) &&\n//            isPermission(context?.applicationContext,\n//                Manifest.permission.BLUETOOTH_ADVERTISE) &&\n            isPermission(context?.applicationContext,\n                Manifest.permission.BLUETOOTH_CONNECT)) {\n            return true\n        } else if (\n            isPermission(context?.applicationContext,\n                Manifest.permission.ACCESS_FINE_LOCATION) &&\n            isPermission(context?.applicationContext,\n                Manifest.permission.ACCESS_COARSE_LOCATION)) {\n            return true\n        }\n        return false\n    }\n\n    /**\n     * ScanResult转BleDevice\n     */\n    @SuppressLint(\"MissingPermission\")\n    fun scanResultToBleDevice(scanResult: ScanResult): BleDevice {\n        return BleDevice(\n            deviceInfo = scanResult.device,\n            deviceName = scanResult.device?.name,\n            deviceAddress = scanResult.device?.address,\n            rssi = scanResult.rssi,\n            timestampNanos = scanResult.timestampNanos,\n            scanRecord = scanResult.scanRecord?.bytes,\n            serviceUuids = scanResult.scanRecord?.serviceUuids,\n            tag = null\n        )\n    }\n\n    /**\n     * 字节数组转16进制字符串\n     *\n     * @param bytes 需要转换的byte数组\n     * @param addSpace 是否添加空格\n     * @return 转换后的Hex字符串\n     */\n    fun bytesToHex(bytes: ByteArray?, addSpace: Boolean = true): String {\n        if (bytes == null) {\n            return \"\"\n        }\n        val sb = StringBuilder()\n        for (aByte in bytes) {\n            val hex = Integer.toHexString(aByte.toInt() and 0xFF)\n            if (hex.length < 2) {\n                sb.append(0)\n            }\n            sb.append(hex)\n            if (addSpace) {\n                sb.append(\" \")\n            }\n        }\n        return sb.toString()\n    }\n\n    /**\n     * 分包\n     * @param data 需要分别的数据\n     * @param packageLength 每个数据包最大长度\n     */\n    fun subpackage(data: ByteArray, packageLength: Int): SparseArray<ByteArray> {\n        val listData: SparseArray<ByteArray>\n        if (data.size > packageLength) {\n            val pkgCount = if (data.size % packageLength == 0) {\n                data.size / packageLength\n            } else {\n                (data.size / packageLength + 1).toFloat().roundToInt()\n            }\n            listData = SparseArray<ByteArray>(pkgCount)\n            for (i in 0 until pkgCount) {\n                var dataPkg: ByteArray\n                var length: Int\n                if (pkgCount == 1 || i == pkgCount - 1) {\n                    length = if (data.size % packageLength == 0) {\n                        packageLength\n                    } else {\n                        data.size % packageLength\n                    }\n                    System.arraycopy(\n                        data,\n                        i * packageLength,\n                        ByteArray(length).also { dataPkg = it },\n                        0,\n                        length\n                    )\n                } else {\n                    System.arraycopy(\n                        data,\n                        i * packageLength,\n                        ByteArray(packageLength).also { dataPkg = it },\n                        0,\n                        packageLength\n                    )\n                }\n                BleLogger.d(\"${i + 1} data is: ${bytesToHex(dataPkg)}\")\n                listData.put(i, dataPkg)\n            }\n        } else {\n            listData = SparseArray<ByteArray>(1)\n            listData.put(0, data)\n        }\n        return listData\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    id 'com.android.application' version '8.2.1' apply false\n    id 'com.android.library' version '8.2.1' apply false\n    id 'org.jetbrains.kotlin.android' version '1.8.22' apply false\n}\n\next {\n    coreKtxVersion = '1.13.1'\n    appcompatVersion = '1.7.1'\n    materialVersion = '1.9.0'\n    multidexVersion = '2.0.1'\n    lifecycleExtensionsVersion = '2.2.0'\n    lifecycleVersion = '2.6.1'\n\n    retrofitVersion = '2.9.0'\n    gsonVersion = '2.10.1'\n    interceptorVersion = '4.11.0'\n\n    timberLog = '5.0.1'\n    eventbus = '3.3.1'\n\n    netCore = '1.1.2'\n    BackgroundLibrary = '1.7.6'\n\n    glide = '4.15.0'\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Aug 12 09:31:03 CST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "jitpack.yml",
    "content": "jdk:\n  - openjdk17\nbefore_install:\n  - sdk install java 17.0.7-open\n  - sdk use java 17.0.7-open"
  },
  {
    "path": "maven_upload.gradle",
    "content": "def RELEASE_REPOSITORY_URL = \"http://localhost:8081/nexus/content/repositories/releases/\"\ndef SNAPSHOT_REPOSITORY_URL = \"http://localhost:8081/nexus/content/repositories/snapshots/\"\ndef NEXUS_USERNAME = \"admin\"\ndef NEXUS_PASSWORD = \"admin123\"\n\napply plugin: 'maven-publish'\n\nafterEvaluate {\n    publishing {\n        repositories {\n            maven {\n                allowInsecureProtocol(true)\n                name(\"AndroidReleaseMaven\")\n                url = RELEASE_REPOSITORY_URL\n                credentials {\n                    username = NEXUS_USERNAME\n                    password = NEXUS_PASSWORD\n                }\n            }\n            maven {\n                allowInsecureProtocol(true)\n                name(\"AndroidSnapshotMaven\")\n                url = SNAPSHOT_REPOSITORY_URL\n                credentials {\n                    username = NEXUS_USERNAME\n                    password = NEXUS_PASSWORD\n                }\n            }\n        }\n        publications {\n            Production(MavenPublication) {\n                from components.release\n                artifact androidSourcesJar //打包源码，去除这行打的包将看不到源码\n                groupId = GROUP\n                artifactId = POM_ARTIFACT_ID\n                version = VERSION_NAME\n            }\n            Develop(MavenPublication) {\n                from components.debug\n                artifact androidSourcesJar //打包源码，去除这行打的包将看不到源码\n                groupId = GROUP\n                artifactId = POM_ARTIFACT_ID\n                version = \"${VERSION_NAME}-SNAPSHOT\"\n            }\n        }\n    }\n    artifacts {\n        archives androidSourcesJar\n        archives androidJavadocsJar\n    }\n}\n// 用于打包源代码的任务\ntask androidSourcesJar(type: Jar) {\n    archiveClassifier.set('sources')\n    from android.sourceSets.main.java.srcDirs\n}\ntask androidJavadocs(type: Javadoc) {\n    source = android.sourceSets.main.java.srcDirs\n    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n}\ntask androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n    archiveClassifier.set('javadoc')\n    from androidJavadocs.destinationDir\n}\n//解决 JavaDoc 中文注释生成失败的问题\ntasks.withType(Javadoc) {\n    options.addStringOption('Xdoclint:none', '-quiet')\n    options.addStringOption('encng', 'UTF-8')\n    options.addStringOption('charSet', 'UTF-8')\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        google()\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven { url 'https://jitpack.io' }\n    }\n}\nrootProject.name = \"BleCore\"\ninclude ':app'\ninclude ':ble'\ninclude ':support'\n"
  },
  {
    "path": "support/.gitignore",
    "content": "/build"
  },
  {
    "path": "support/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'org.jetbrains.kotlin.android'\n}\n\nandroid {\n    compileSdk 33\n\n    defaultConfig {\n        minSdk 21\n        targetSdk 33\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    kotlinOptions {\n        jvmTarget = '17'\n    }\n    buildFeatures {\n        viewBinding true\n        dataBinding true\n    }\n    namespace 'com.bhm.support.sdk'\n}\n\ndependencies {\n\n    api \"androidx.core:core-ktx:$rootProject.coreKtxVersion\"\n    api \"androidx.appcompat:appcompat:$rootProject.appcompatVersion\"\n    api \"com.google.android.material:material:$rootProject.materialVersion\"\n    api \"androidx.multidex:multidex:$rootProject.multidexVersion\"\n\n    // Lifecycle components\n    api \"androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleExtensionsVersion\"\n    api \"androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion\"\n    api \"androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.lifecycleVersion\"\n\n    // network & serialization\n    api \"com.google.code.gson:gson:$rootProject.gsonVersion\"\n    api \"com.squareup.retrofit2:converter-gson:$rootProject.retrofitVersion\"\n    api \"com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion\"\n    api \"com.squareup.okhttp3:logging-interceptor:$rootProject.interceptorVersion\"\n\n    //log\n    api \"com.jakewharton.timber:timber:$rootProject.timberLog\"\n\n    //eventbus\n    api \"org.greenrobot:eventbus:$rootProject.eventbus\"\n\n    //Http\n    api \"com.github.buhuiming:NetCore:$rootProject.netCore\"\n\n    //https://github.com/JavaNoober/BackgroundLibrary\n    api \"com.github.JavaNoober.BackgroundLibrary:libraryx:$rootProject.BackgroundLibrary\"\n\n    //图片缓存 glide https://github.com/bumptech/glide\n    api \"com.github.bumptech.glide:glide:$rootProject.glide\"\n\n    //noinspection GradleDependency 一个强大的RecyclerAdapter框架 https://github.com/CymChad/BaseRecyclerViewAdapterHelper\n//    api \"com.github.CymChad:BaseRecyclerViewAdapterHelper:$rootProject.baseRecyclerViewAdapterHelper\"\n//    api \"io.github.cymchad:BaseRecyclerViewAdapterHelper:4.0.0\"\n}"
  },
  {
    "path": "support/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "support/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "support/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.bhm.support.sdk\">\n\n    <uses-permission android:name=\"android.permission.REORDER_TASKS\" />\n</manifest>"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseActivity.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Message\nimport android.view.KeyEvent\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.ViewModelStoreOwner\nimport com.bhm.support.sdk.core.AppTheme\nimport com.bhm.support.sdk.core.WeakHandler\nimport com.bhm.support.sdk.entity.MessageEvent\nimport com.bhm.support.sdk.utils.ActivityUtil\nimport com.noober.background.BackgroundLibrary\nimport org.greenrobot.eventbus.EventBus\nimport org.greenrobot.eventbus.Subscribe\nimport org.greenrobot.eventbus.ThreadMode\n\n\n/**\n * @author Buhuiming\n * @description: Activity基类\n * @date :2022/6/28 14:09\n */\nabstract class BaseActivity<VM : BaseViewModel> : AppCompatActivity(), Handler.Callback {\n\n    lateinit var viewModel: VM\n\n    private var activityLauncher: ActivityResultLauncher<Intent>? = null\n\n    private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null\n\n    private var arCallback: ((resultCode: Int, resultIntent: Intent?) -> Unit)? = null\n\n    private var permissionAgree: (() -> Unit)? = null\n\n    private var permissionRefuse: ((refusePermissions: ArrayList<String>) -> Unit)? = null\n\n    lateinit var mainHandler: WeakHandler\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        BackgroundLibrary.inject(this)\n        super.onCreate(savedInstanceState)\n        AppTheme.fitSystemWindow(this)\n        ActivityUtil.addActivity(this)\n        EventBus.getDefault().register(this)\n        init()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        EventBus.getDefault().unregister(this)\n        ActivityUtil.removeActivity(this)\n        mainHandler.removeCallbacksAndMessages(null)\n    }\n\n    /**\n     *ViewModel绑定\n     */\n    private fun init() {\n        viewModel = createViewModel(this, createViewModel())\n        activityLauncher = registerForActivityResult(StartActivityForResult()) { result ->\n            if (result != null) {\n                arCallback?.let {\n                    it(result.resultCode, result.data)\n                }\n            }\n        }\n        permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()\n        ) {\n            val refusePermission: ArrayList<String> = ArrayList()\n            it.keys.forEach { res ->\n                if (it[res] == false) {\n                    refusePermission.add(res)\n                }\n            }\n\n            if (refusePermission.size > 0) {\n                permissionRefuse?.let {\n                    it(refusePermission)\n                }\n            } else {\n                permissionAgree?.let {\n                    it()\n                }\n            }\n        }\n        mainHandler = WeakHandler(Looper.getMainLooper(), this)\n    }\n\n    /**\n     * 创建ViewModel\n     */\n    abstract fun createViewModel(): VM\n\n    /** 是否屏蔽返回键\n     * @return\n     */\n    protected open fun isRefusedBackPress(): Boolean {\n        return false\n    }\n\n    private fun createViewModel(owner: ViewModelStoreOwner, viewModel: VM): VM {\n        return ViewModelProvider(owner).get(viewModel.javaClass)\n    }\n\n    fun startActivity(intent: Intent, arCallback: (resultCode: Int, resultIntent: Intent?) -> Unit) {\n        this.arCallback = arCallback\n        activityLauncher?.launch(intent)\n    }\n\n    fun requestPermission(permissions: Array<String>, agree: () -> Unit, refuse: (refusePermissions: ArrayList<String>) -> Unit) {\n        this.permissionAgree = agree\n        this.permissionRefuse = refuse\n        var allAgree = true\n        for (permission in permissions){\n            if( ContextCompat.checkSelfPermission(this, permission) !=\n                PackageManager.PERMISSION_GRANTED){\n                allAgree=false\n                break\n            }\n        }\n        if (allAgree) {\n            permissionAgree?.let {\n                it()\n            }\n            return\n        }\n        permissionLauncher?.launch(permissions)\n    }\n\n    override fun handleMessage(msg: Message): Boolean {\n        return false\n    }\n\n    @Subscribe(threadMode = ThreadMode.MAIN)\n    open fun onMessageEvent(event: MessageEvent?) {\n       //EventBus Do something\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        // TODO Auto-generated method stub\n        if (isRefusedBackPress() && keyCode == KeyEvent.KEYCODE_BACK) {  //欢迎页 按物理返回键不能关闭APP\n            return true\n        } else if (keyCode == KeyEvent.KEYCODE_BACK) {\n            finish()\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseApplication.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport android.annotation.SuppressLint\nimport android.app.Application\nimport android.content.Context\nimport androidx.multidex.MultiDex\nimport com.bhm.support.sdk.constants.DEBUGGER\nimport timber.log.Timber\n\n/**\n * @author Buhuiming\n * @description: Application基类\n * @date :2022/6/28 14:14\n */\nopen class BaseApplication : Application(){\n\n    companion object {\n        @SuppressLint(\"StaticFieldLeak\")\n        private lateinit var context: Context\n        fun getContext(): Context = context\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        context = this\n        if (isDebugger()) {\n            Timber.plant(Timber.DebugTree())\n        }\n    }\n\n    override fun attachBaseContext(base: Context?) {\n        super.attachBaseContext(base)\n        MultiDex.install(base)\n    }\n\n    open fun isDebugger() = DEBUGGER\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseFragment.kt",
    "content": "@file:Suppress(\"UNCHECKED_CAST\")\n\npackage com.bhm.support.sdk.common\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Message\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.ViewModelStoreOwner\nimport com.bhm.support.sdk.core.WeakHandler\n\n/**\n * @author Buhuiming\n * @description: Fragment基类\n * @date :2022/6/28 14:09\n */\nabstract class BaseFragment<VM : BaseViewModel> : Fragment(), Handler.Callback {\n\n    lateinit var viewModel: VM\n\n    lateinit var activity: BaseActivity<BaseViewModel>\n\n    lateinit var mainHandler: WeakHandler\n\n    var isFirstLoad = true // 是否第一次加载\n\n    override fun onAttach(context: Context) {\n        super.onAttach(context)\n        activity = context as BaseActivity<BaseViewModel>\n        init()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        if (isFirstLoad) {\n            // 将数据加载逻辑放到onResume()方法中\n            lazyLoad()\n            isFirstLoad = false\n            return\n        }\n\n        if (!setLoadDataOnce()) {\n            //每次可见都加载\n            lazyLoad()\n        }\n    }\n\n    fun setLoadDataOnce(): Boolean = true\n\n    fun lazyLoad() {\n\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        isFirstLoad = true\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        mainHandler.removeCallbacksAndMessages(null)\n    }\n\n    /**\n     * ViewModel绑定\n     */\n    private fun init() {\n        viewModel = createViewModel(this, createViewModel())\n        mainHandler = WeakHandler(Looper.getMainLooper(), this)\n    }\n\n    /**\n     * 创建ViewModel\n     */\n    abstract fun createViewModel(): VM\n\n    private fun createViewModel(owner: ViewModelStoreOwner, viewModel: VM): VM {\n        return ViewModelProvider(owner).get(viewModel.javaClass)\n    }\n\n    override fun handleMessage(msg: Message): Boolean {\n        return false\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseVBActivity.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.viewbinding.ViewBinding\nimport com.bhm.support.sdk.utils.ViewUtil\n\n/**\n * @author Buhuiming\n * @description: ViewBinding基类\n * @date :2022/6/28 14:38\n */\nabstract class BaseVBActivity<VM : BaseViewModel, B : ViewBinding> : BaseActivity<VM>() {\n\n    lateinit var viewBinding: B\n\n    lateinit var rootView: View\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        viewBinding = ViewUtil.inflateWithGeneric(this, layoutInflater)\n        rootView = viewBinding.root\n        setContentView(rootView)\n        initData()\n        initEvent()\n    }\n\n    protected open fun initData() {}\n\n    protected open fun initEvent() {}\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseVBFragment.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.viewbinding.ViewBinding\nimport com.bhm.support.sdk.utils.ViewUtil\n\n/**\n * @author Buhuiming\n * @description: ViewBinding基类\n * @date :2022/6/28 14:58\n */\nabstract class BaseVBFragment <VM : BaseViewModel, B : ViewBinding> : BaseFragment<VM>(){\n\n    lateinit var viewBinding: B\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        viewBinding = ViewUtil.inflateWithGeneric(this, layoutInflater)\n        initView()\n        initEvent()\n        return viewBinding.root\n    }\n\n    protected open fun initView() {}\n\n    protected open fun initEvent() {}\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/BaseViewModel.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\n\n/**\n * @author Buhuiming\n * @description: ViewModel基类\n * @date :2022/6/28 14:21\n */\nopen class BaseViewModel(context: Application) : AndroidViewModel(context){\n\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/common/DefaultViewPagerAdapter.kt",
    "content": "package com.bhm.support.sdk.common\n\nimport androidx.fragment.app.Fragment\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\n\n/**\n * viewpager2的adapter\n */\nclass DefaultViewPagerAdapter : FragmentStateAdapter {\n    private var fragmentsList: List<Fragment>? = null\n\n    constructor(fragmentActivity: FragmentActivity, fragmentsList: List<Fragment>?) : super(\n        fragmentActivity\n    ) {\n        this.fragmentsList = fragmentsList\n    }\n\n    constructor(fragment: Fragment, fragmentsList: List<Fragment>?) : super(fragment) {\n        this.fragmentsList = fragmentsList\n    }\n\n    constructor(fragmentManager: FragmentManager, lifecycle: Lifecycle) : super(\n        fragmentManager,\n        lifecycle\n    ) {\n    }\n\n    override fun createFragment(position: Int): Fragment {\n        return fragmentsList!![position]\n    }\n\n    override fun getItemCount(): Int {\n        return fragmentsList!!.size\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/constants/AppConstants.kt",
    "content": "package com.bhm.support.sdk.constants\n\n\n/**\n * @author Buhuiming\n * @description: App常用静态参数\n * @date :2022/6/28 14:26\n */\nconst val DEBUGGER = true"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/constants/PermissionConstants.kt",
    "content": "package com.bhm.support.sdk.constants\n\nimport android.Manifest\n\n/**\n * @author Buhuiming\n * @description:\n * @date :2022/6/28 17:06\n */\nval STORAGE_PERMISSION = arrayOf(\n    Manifest.permission.READ_EXTERNAL_STORAGE,\n    Manifest.permission.WRITE_EXTERNAL_STORAGE\n)"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/AppTheme.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Build\nimport android.view.View\nimport android.view.WindowManager\nimport androidx.core.content.ContextCompat\n\n/**\n * 沉浸式状态栏样式\n */\n@Suppress(\"DEPRECATION\", \"unused\")\nobject AppTheme {\n    /**\n     * 将内容提升至状态栏\n     */\n    fun fitSystemWindow(activity: Activity) {\n        activity.window.decorView.systemUiVisibility =\n            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n        setStatusBarColor(activity, android.R.color.transparent)\n    }\n\n    /**\n     * 将内容提升至状态栏\n     */\n    fun fitSystemLightWindow(activity: Activity) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            activity.window.decorView.systemUiVisibility =\n                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR\n            setStatusBarColor(activity, android.R.color.transparent)\n        }\n    }\n\n    /**\n     * 设置导航栏颜色\n     */\n    fun setNavigationBarColor(activity: Activity, colorResId: Int) {\n        val color = ContextCompat.getColor(activity, colorResId)\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {\n            activity.window.navigationBarColor = color\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            activity.window.navigationBarDividerColor = color\n        }\n    }\n\n    /**\n     * 设置状态栏颜色\n     */\n    fun setStatusBarColor(activity: Activity, colorResId: Int) {\n        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {\n            val window = activity.window\n            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)\n            window.statusBarColor = ContextCompat.getColor(activity, colorResId)\n        }\n    }\n\n    /**\n     * 设置状态栏Light主题\n     */\n    fun setLightStatusBar(activity: Activity) {\n        val window = activity.window\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR\n        }\n    }\n\n    /**\n     * 设置状态栏Dark主题\n     */\n    fun setDarkStatusBar(activity: Activity) {\n        val window = activity.window\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE\n        }\n    }\n\n    /**\n     * Android官方标准的系统UI全屏样式\n     * 重写Activity的onWindowFocusChanged方法\n     * 在获取到焦点时调用\n     * if (hasFocus) initWindowStyle();\n     */\n    fun fullScreenStyle(activity: Activity) {\n        activity.window\n            .decorView.systemUiVisibility =\n            (View.SYSTEM_UI_FLAG_LAYOUT_STABLE //防止系统栏隐藏时内容区域大小发生变化\n                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION //隐藏导航栏\n                    or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN //全屏\n                    or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION //隐藏底部的 三个 虚拟按键导航栏\n                    or View.SYSTEM_UI_FLAG_FULLSCREEN\n                    or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)\n        activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n    }\n\n    /**\n     * 获取状态栏高度\n     */\n    fun getStatusBarHeight(context: Context): Int {\n        var statusBarHeight = 0\n        val resourceId = context.resources.getIdentifier(\"status_bar_height\", \"dimen\", \"android\")\n        if (resourceId > 0) {\n            statusBarHeight = context.resources.getDimensionPixelSize(resourceId)\n        }\n        return statusBarHeight\n    }\n\n    /**\n     * 获取导航栏高度\n     */\n    fun getNavigationBarHeight(context: Context): Int {\n        var navigationBarHeight = 0\n        val rid = context.resources.getIdentifier(\"config_showNavigationBar\", \"bool\", \"android\")\n        if (rid != 0) {\n            val resourceId =\n                context.resources.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\")\n            navigationBarHeight = context.resources.getDimensionPixelSize(resourceId)\n        }\n        return navigationBarHeight\n    }\n\n    /**\n     * 是否有底部导航栏\n     */\n    fun isHaveNavigationBar(context: Context): Boolean {\n        val rs = context.resources\n        val id = rs.getIdentifier(\"config_showNavigationBar\", \"bool\", \"android\")\n        return rs.getBoolean(id)\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/GlideCircleTransform.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport android.content.Context\nimport android.graphics.*\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport com.bumptech.glide.load.resource.bitmap.TransformationUtils\nimport java.security.MessageDigest\n\n/**glide圆形图\n */\nclass GlideCircleTransform : BitmapTransformation() {\n    override fun transform(\n        pool: BitmapPool,\n        toTransform: Bitmap,\n        outWidth: Int,\n        outHeight: Int\n    ): Bitmap {\n        /* 解决 Glide的centerCrop会和圆角冲突*/\n        val bitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight)\n        return circleCrop(pool, bitmap)!!\n    }\n\n    private fun circleCrop(pool: BitmapPool, source: Bitmap?): Bitmap? {\n        if (source == null) return null\n        val size = Math.min(source.width, source.height)\n        val x = (source.width - size) / 2\n        val y = (source.height - size) / 2\n\n        // TODO this could be acquired from the pool too\n        val squared = Bitmap.createBitmap(source, x, y, size, size)\n        val result = pool[size, size, Bitmap.Config.ARGB_8888]\n        val canvas = Canvas(result)\n        val paint = Paint()\n        paint.shader =\n            BitmapShader(squared, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)\n        paint.isAntiAlias = true\n        val r = size / 2f\n        canvas.drawCircle(r, r, r, paint)\n        return result\n    }\n\n    override fun updateDiskCacheKey(messageDigest: MessageDigest) {}\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/GlideRoundTransform.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport android.content.res.Resources\nimport android.graphics.*\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport com.bumptech.glide.load.resource.bitmap.TransformationUtils\nimport java.security.MessageDigest\n\n/** glide圆角图\n * Created by bhm on 2018/3/6.\n */\nclass GlideRoundTransform : BitmapTransformation {\n    private var centerCrop = true\n\n    @JvmOverloads\n    constructor(dp: Float = 10f) {\n        radius = Resources.getSystem().displayMetrics.density * dp\n    }\n\n    constructor(dp: Float, centerCrop: Boolean) {\n        radius = Resources.getSystem().displayMetrics.density * dp\n        this.centerCrop = centerCrop\n    }\n\n    override fun transform(\n        pool: BitmapPool,\n        toTransform: Bitmap,\n        outWidth: Int,\n        outHeight: Int\n    ): Bitmap {\n        /* 解决 Glide的centerCrop会和圆角冲突*/\n        if (centerCrop) {\n            val bitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight)\n            return roundCrop(pool, bitmap)!!\n        }\n        val bitmap = TransformationUtils.fitCenter(pool, toTransform, outWidth, outHeight)\n        return roundCrop(pool, bitmap)!!\n    }\n\n    private fun roundCrop(pool: BitmapPool, source: Bitmap?): Bitmap? {\n        if (source == null) return null\n        val result = pool[source.width, source.height, Bitmap.Config.ARGB_8888]\n        val canvas = Canvas(result)\n        val paint = Paint()\n        paint.shader =\n            BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)\n        paint.isAntiAlias = true\n        val rectF = RectF(\n            0f, 0f, source.width.toFloat(), source.height\n                .toFloat()\n        )\n        canvas.drawRoundRect(rectF, radius, radius, paint)\n        return result\n    }\n\n    val id: String\n        get() = javaClass.name + Math.round(radius)\n\n    override fun updateDiskCacheKey(messageDigest: MessageDigest) {}\n\n    companion object {\n        private var radius = 0f\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/GridSpacingItemDecoration.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView.ItemDecoration\nimport androidx.recyclerview.widget.RecyclerView\n\n/**\n * 间隔\n */\nclass GridSpacingItemDecoration(\n    private val spanCount: Int,\n    private val spacing: Int,\n    private val includeEdge: Boolean\n) : ItemDecoration() {\n    override fun getItemOffsets(\n        outRect: Rect,\n        view: View,\n        parent: RecyclerView,\n        state: RecyclerView.State\n    ) {\n        val position = parent.getChildAdapterPosition(view) // item position\n        val column = position % spanCount // item column\n        if (includeEdge) {\n            outRect.left =\n                spacing - column * spacing / spanCount // spacing - column * ((1f / spanCount) * spacing)\n            outRect.right =\n                (column + 1) * spacing / spanCount // (column + 1) * ((1f / spanCount) * spacing)\n            if (position < spanCount) { // top edge\n                outRect.top = spacing\n            }\n            outRect.bottom = spacing // item bottom\n        } else {\n            outRect.left = column * spacing / spanCount // column * ((1f / spanCount) * spacing)\n            outRect.right =\n                spacing - (column + 1) * spacing / spanCount // spacing - (column + 1) * ((1f /    spanCount) * spacing)\n            if (position >= spanCount) {\n                outRect.top = spacing // item top\n            }\n        }\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/MyStaggeredGridLayoutManager.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\n\n/**\n * 禁止滑动\n */\nclass MyStaggeredGridLayoutManager(spanCount: Int, orientation: Int) :\n    StaggeredGridLayoutManager(spanCount, orientation) {\n    private var isScrollEnabled = true\n\n    fun setScrollEnabled(flag: Boolean) {\n        isScrollEnabled = flag\n    }\n\n    override fun canScrollVertically(): Boolean {\n        return isScrollEnabled && super.canScrollVertically()\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/core/WeakHandler.kt",
    "content": "package com.bhm.support.sdk.core\n\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Message\nimport java.lang.ref.WeakReference\n\n/**\n * @author Buhuiming\n * @description: 管理handler，防止内存泄露\n * @date :2022/6/28 16:02\n */\nopen class WeakHandler(looper: Looper?, cb: Callback?) : Handler(looper!!) {\n\n    private var activityWeakReference: WeakReference<Callback?>\n\n    init {\n        activityWeakReference = WeakReference<Callback?>(cb)\n    }\n\n    fun setCallback(cb: Callback?) {\n        activityWeakReference = WeakReference<Callback?>(cb)\n    }\n\n    override fun handleMessage(msg: Message) {\n        val cb = activityWeakReference.get()\n        cb?.handleMessage(msg)\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/entity/MessageEvent.kt",
    "content": "package com.bhm.support.sdk.entity\n\nimport java.io.Serializable\n\n/**\n * @author Buhuiming\n */\nclass MessageEvent : Serializable {\n    var msgId = 0\n    var msg: String? = null\n    var data: Any? = null\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/ActivityUtil.kt",
    "content": "package com.bhm.support.sdk.utils\n\nimport android.app.Activity\nimport android.app.ActivityManager\nimport android.content.Context\nimport java.util.*\n\n/**\n * @author Buhuiming\n * @description: 管理activity\n * @date :2022/6/28 14:28\n */\nobject ActivityUtil {\n\n    private val activities: MutableList<Activity> = ArrayList()\n\n    fun addActivity(activity: Activity) {\n        activities.add(activity)\n    }\n\n    fun removeActivity(activity: Activity) {\n        activities.remove(activity)\n    }\n\n\n    fun getActivities(): List<Activity> {\n        return activities\n    }\n\n    /**\n     * 关闭所有的Activity\n     *\n     * @param exceptClassName 如果不想关闭某一个activity, 将activity的className传入\n     */\n    fun finishAll(vararg exceptClassName: String?) {\n        val names = mutableListOf(*exceptClassName)\n        for (activity in activities) {\n            if (!activity.isFinishing && !names.contains(activity.javaClass.name)) {\n                activity.finish()\n            }\n        }\n    }\n\n    /**\n     * 关闭某一个Activity\n     * @param exceptClassName 将activity的className传入\n     */\n    fun finish(vararg exceptClassName: String?) {\n        val names = mutableListOf(*exceptClassName)\n        for (activity in activities) {\n            if (!activity.isFinishing && names.contains(activity.javaClass.name)) {\n                activity.finish()\n            }\n        }\n    }\n\n    fun moveTaskToBack(task: Activity) {\n        task.moveTaskToBack(true)\n    }\n\n    fun moveTaskToFront(task: Activity) {\n        getManager(task).moveTaskToFront(task.taskId, ActivityManager.MOVE_TASK_WITH_HOME)\n    }\n\n    private fun getManager(task: Activity): ActivityManager {\n        return task.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager\n    }\n\n    private fun size(): Int {\n        return activities.size - 1\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/DateUtil.kt",
    "content": "package com.bhm.support.sdk.utils\n\nimport java.text.SimpleDateFormat\nimport java.util.*\n\n/**\n * @author Buhuiming\n * @date :2022/10/9 13:49\n */\nobject DateUtil {\n\n    @JvmStatic\n    fun getMmDdEEEE() : String {\n        val date = Date()\n        val datetime = SimpleDateFormat(\"MM月dd日 EEEE\", Locale.getDefault())\n        return datetime.format(date)\n    }\n\n    @JvmStatic\n    fun getOneDate(limit: Int): IntArray {\n        return getOneDate(limit, false)\n    }\n\n    /** 获取某个时间段\n     * @param limit\n     * @return\n     */\n    @JvmStatic\n    fun getOneDate(limit: Int, isShiFen: Boolean): IntArray {\n        val startDates = IntArray(6)\n        val calendar = Calendar.getInstance()\n        calendar.add(Calendar.DAY_OF_MONTH, limit)\n        startDates[0] = calendar[Calendar.YEAR]\n        startDates[1] = calendar[Calendar.MONTH] + 1\n        startDates[2] = calendar[Calendar.DAY_OF_MONTH]\n        if (isShiFen) {\n            startDates[3] = calendar[Calendar.HOUR_OF_DAY]\n            startDates[4] = calendar[Calendar.MINUTE]\n            startDates[5] = calendar[Calendar.SECOND]\n        } else {\n            startDates[3] = 0\n            startDates[4] = 0\n            startDates[5] = 0\n        }\n        return startDates\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/DisplayUtil.kt",
    "content": "package com.bhm.support.sdk.utils\n\nimport android.content.Context\n\n/**\n * @author Buhuiming\n * @date :2022/10/11 14:14\n */\nobject DisplayUtil {\n\n    /**dp转px\n     * @param context\n     * @param dp\n     * @return\n     */\n    fun dp2px(context: Context, dp: Float): Int {\n        val scale = context.resources.displayMetrics.density\n        return (dp * scale + 0.5f).toInt()\n    }\n\n    /**px转dp\n     * @param context\n     * @param pxValue\n     * @return\n     */\n    fun px2dp(context: Context, pxValue: Float): Int {\n        val scale = context.resources.displayMetrics.density\n        return (pxValue / scale + 0.5f).toInt()\n    }\n\n    /**\n     * sp转px\n     *\n     * @param spValue sp值\n     * @return px值\n     */\n    fun sp2px(context: Context, spValue: Float): Int {\n        val fontScale = context.resources.displayMetrics.scaledDensity\n        return (spValue * fontScale + 0.5f).toInt()\n    }\n\n    /**\n     * px转sp\n     *\n     * @param pxValue px值\n     * @return sp值\n     */\n    fun px2sp(context: Context, pxValue: Float): Int {\n        val fontScale = context.resources.displayMetrics.scaledDensity\n        return (pxValue / fontScale + 0.5f).toInt()\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/NotificationUtil.kt",
    "content": "package com.bhm.support.sdk.utils\n\nimport android.annotation.TargetApi\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.os.Build\nimport android.text.TextUtils\nimport androidx.core.app.NotificationCompat\n\n\n/**\n * 从Android 8.1（API级别27）开始，如果同一时间发布了多个通知的话, 只有第一个通知会发出声音\n */\nclass NotificationUtil private constructor(\n    context: Context,\n) : ContextWrapper(context) {\n\n    private var largeIcon: Bitmap? = null\n\n    private var smallIcon: Int = 0\n\n    companion object {\n        const val DEFAULT_CHANNEL_ID = \"DEFAULT_CHANNEL_ID\" //默认的channel\n\n        private const val DEFAULT_CHANNEL = \"DEFAULT_CHANNEL\"\n\n        private var notificationUtil: NotificationUtil? = null\n\n        @Synchronized\n        fun getInstance(context: Context): NotificationUtil? {\n            if (notificationUtil == null) {\n                notificationUtil = NotificationUtil(context)\n            }\n            return notificationUtil\n        }\n    }\n\n    private var manager: NotificationManager? = null\n        get() {\n            if (field == null) {\n                field = getSystemService(NOTIFICATION_SERVICE) as NotificationManager\n            }\n            return field\n        }\n\n    fun init(smallIconId: Int, largeIconId: Int, channels: Map<String, String>?) {\n        setIcon(smallIconId, largeIconId)\n        createChannels(channels)\n    }\n\n    /*设置图标*/\n    private fun setIcon(smallIconId: Int, largeIconId: Int){\n        largeIcon = BitmapFactory.decodeResource(resources, largeIconId)\n        smallIcon = smallIconId\n    }\n\n    //设置消息渠道\n    private fun createChannels(channels: Map<String, String>?) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            createNotificationChannel(DEFAULT_CHANNEL_ID, DEFAULT_CHANNEL)\n            if (channels == null || channels.isEmpty()) {\n                return\n            }\n            channels.forEach {\n                createNotificationChannel(it.key, it.value)\n            }\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.O)\n    private fun createNotificationChannel(\n        channelId: String,\n        channelName: String\n    ) {\n        val channel =\n            NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)\n        channel.setShowBadge(true)\n        channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        manager?.createNotificationChannel(channel)\n    }\n\n    /**\n     * 获取默认通道的 NotificationCompat.Builder\n     */\n    private fun getNotificationBuilderByChannel(channel: String): NotificationCompat.Builder {\n        val builder: NotificationCompat.Builder\n        val channelId = if (TextUtils.isEmpty(channel)) DEFAULT_CHANNEL_ID else channel\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            builder = NotificationCompat.Builder(applicationContext, channelId)\n        } else {\n            builder = NotificationCompat.Builder(this, channelId).setDefaults(Notification.DEFAULT_SOUND or Notification.DEFAULT_VIBRATE)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //8.0以下 && 7.0及以上 设置优先级\n                builder.priority = NotificationManager.IMPORTANCE_HIGH\n            } else {\n                builder.priority = NotificationCompat.PRIORITY_HIGH\n            }\n        }\n        return builder\n    }\n\n    /**\n     * 创建普通的文字通知1\n     * note: 默认通知只显示一行(系统自动截取)\n     * 可以通过NotificationCompat.BigTextStyle()显示多行文本\n     */\n    private fun buildNotificationText(\n        title: String,\n        body: String,\n        pendingIntent: PendingIntent,\n        channelId: String\n    ): NotificationCompat.Builder {\n        return getNotificationBuilderByChannel(channelId)\n            .setAutoCancel(true)\n            .setSmallIcon(smallIcon)\n            .setLargeIcon(largeIcon)\n            .setContentTitle(title)\n            .setContentText(body)\n            .setContentIntent(pendingIntent)\n            //.setTimeoutAfter(3000)//时间过后自动取消该通知\n            //.setNumber(1127)//超过999 系统就直接显示999\n            .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) //长按应用图标,通知显示的图标类型, 默认显示大图标\n            .setStyle(NotificationCompat.BigTextStyle().setBigContentTitle(title).bigText(body))\n    }\n\n    /**\n     * 创建普通的文字通知2 添加Action\n     * @param actions 在通知消息中添加按钮 最多添加3个\n     */\n    private fun buildNotificationTextAction(\n        title: String,\n        body: String,\n        pendingIntent: PendingIntent,\n        channelId: String,\n        vararg actions: NotificationCompat.Action\n    ): NotificationCompat.Builder {\n        val builder = getNotificationBuilderByChannel(channelId)\n            .setAutoCancel(true)\n            .setSmallIcon(smallIcon)\n            .setLargeIcon(largeIcon)\n            .setContentTitle(title)\n            .setContentText(body)\n            .setContentIntent(pendingIntent)\n            .setStyle(\n                NotificationCompat.BigTextStyle()\n                    .setBigContentTitle(title)\n                    .bigText(body)\n            )\n        if (actions.isNotEmpty()) {\n            for (action in actions) {\n                builder.addAction(action)\n            }\n        }\n        return builder\n    }\n\n    /**\n     * 创建带图片的通知\n     * 消息折叠时显示小图, 展开后显示大图\n     */\n    private fun buildNotificationImage(\n        title: String,\n        body: String,\n        imgBitmap: Bitmap,\n        pendingIntent: PendingIntent,\n        channelId: String\n    ): NotificationCompat.Builder {\n        return getNotificationBuilderByChannel(channelId)\n            .setAutoCancel(true)\n            .setSmallIcon(smallIcon)\n            .setLargeIcon(largeIcon)\n            .setContentTitle(title)\n            .setContentText(body)\n            .setContentIntent(pendingIntent)\n            .setStyle(\n                NotificationCompat.BigPictureStyle()\n                    .setBigContentTitle(title)\n                    .bigLargeIcon(imgBitmap)\n                    .bigPicture(imgBitmap)\n            )\n    }\n\n    /**\n     * 创建带图片的通知2 添加Action\n     * 消息折叠时显示小图, 展开后显示大图\n     */\n    private fun buildNotificationImageAction(\n        title: String,\n        body: String,\n        imgBitmap: Bitmap,\n        pendingIntent: PendingIntent,\n        channelId: String,\n        vararg actions: NotificationCompat.Action\n    ): NotificationCompat.Builder {\n        val builder = getNotificationBuilderByChannel(channelId)\n            .setAutoCancel(true)\n            .setSmallIcon(smallIcon)\n            .setLargeIcon(largeIcon)\n            .setContentTitle(title)\n            .setContentText(body)\n            .setContentIntent(pendingIntent)\n            .setStyle(\n                NotificationCompat.BigPictureStyle()\n                    .setBigContentTitle(title)\n                    .bigLargeIcon(imgBitmap)\n                    .bigPicture(imgBitmap)\n            )\n        if (actions.isNotEmpty()) {\n            for (action in actions) {\n                builder.addAction(action)\n            }\n        }\n        return builder\n    }\n\n    fun buildNotificationText(\n        title: String,\n        body: String,\n        pendingIntent: PendingIntent,\n        channelId: String,\n        vararg actions: NotificationCompat.Action\n    ): NotificationCompat.Builder {\n        return if (actions.isNotEmpty()) buildNotificationTextAction(\n            title,\n            body,\n            pendingIntent,\n            channelId = channelId,\n            *actions\n        ) else buildNotificationText(title, body, pendingIntent, channelId)\n    }\n\n    fun buildNotificationImage(\n        title: String,\n        body: String,\n        imgBitmap: Bitmap,\n        pendingIntent: PendingIntent,\n        channelId: String,\n        vararg actions: NotificationCompat.Action\n    ): NotificationCompat.Builder {\n        return if (actions.isNotEmpty()) buildNotificationImageAction(\n            title,\n            body,\n            imgBitmap,\n            pendingIntent,\n            channelId,\n            *actions\n        ) else buildNotificationImage(title, body, imgBitmap, pendingIntent, channelId)\n    }\n\n    fun notify(id: Int, notification: NotificationCompat.Builder) {\n        manager!!.notify(id, notification.build())\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/SPUtil.kt",
    "content": "package com.bhm.support.sdk.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\n\n/**\n * @author Buhuiming\n * @description: SharedPreferences\n * @date :2022/6/30 14:15\n */\nclass SPUtil(context: Context) {\n\n    companion object {\n        @SuppressLint(\"StaticFieldLeak\")\n        private lateinit var instance: SPUtil\n\n        fun getInstance(context: Context): SPUtil {\n            if (!Companion::instance.isInitialized) {\n                instance = SPUtil(context.applicationContext)\n            }\n            return instance\n        }\n    }\n\n    private val sp = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)\n    private val editor = sp.edit()\n\n    fun getString(key: String): String? = sp.getString(key, null)\n\n    fun getInt(key: String): Int = sp.getInt(key, 0)\n\n    fun getBoolean(key: String): Boolean = sp.getBoolean(key, false)\n\n    fun getFloat(key: String): Float = sp.getFloat(key, 0.toFloat())\n\n    fun putString(key: String, value: String?) {\n        editor.putString(key, value).apply()\n    }\n\n    fun putInt(key: String, value: Int) {\n        editor.putInt(key, value).apply()\n    }\n\n    fun putBoolean(key: String, value: Boolean) {\n        editor.putBoolean(key, value).apply()\n    }\n\n    fun put(key: String, value: Float) {\n        editor.putFloat(key, value).apply()\n    }\n\n    /**\n     * 清空用户数据\n     */\n    fun clear() {\n        editor.clear().apply()\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/utils/ViewUtil.kt",
    "content": "@file:Suppress(\"UNCHECKED_CAST\")\n\npackage com.bhm.support.sdk.utils\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.ImageView\nimport androidx.activity.ComponentActivity\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.IntRange\nimport androidx.databinding.ViewDataBinding\nimport androidx.fragment.app.Fragment\nimport androidx.viewbinding.ViewBinding\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.load.engine.DiskCacheStrategy\nimport com.bumptech.glide.request.RequestOptions\nimport com.bhm.support.sdk.R\nimport com.bhm.support.sdk.core.GlideCircleTransform\nimport com.bhm.support.sdk.core.GlideRoundTransform\nimport java.lang.reflect.InvocationTargetException\nimport java.lang.reflect.ParameterizedType\n\n/**\n * @author Buhuiming\n * @description:\n * @date :2022/6/28 14:44\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject ViewUtil {\n\n    private const val INTERNAL_TIME: Long = 600\n\n    @JvmStatic\n    fun <VB : ViewBinding> inflateWithGeneric(genericOwner: Any, layoutInflater: LayoutInflater): VB =\n        withGenericBindingClass<VB>(genericOwner) { clazz ->\n            clazz.getMethod(\"inflate\", LayoutInflater::class.java).invoke(null, layoutInflater) as VB\n        }.withLifecycleOwner(genericOwner)\n\n    private fun <VB : ViewBinding> VB.withLifecycleOwner(genericOwner: Any) = apply {\n        if (this is ViewDataBinding && genericOwner is ComponentActivity) {\n            lifecycleOwner = genericOwner\n        } else if (this is ViewDataBinding && genericOwner is Fragment) {\n            lifecycleOwner = genericOwner.viewLifecycleOwner\n        }\n    }\n\n    private fun <VB : ViewBinding> withGenericBindingClass(genericOwner: Any, block: (Class<VB>) -> VB): VB {\n        var genericSuperclass = genericOwner.javaClass.genericSuperclass\n        var superclass = genericOwner.javaClass.superclass\n        while (superclass != null) {\n            if (genericSuperclass is ParameterizedType) {\n                genericSuperclass.actualTypeArguments.forEach {\n                    try {\n                        return block.invoke(it as Class<VB>)\n                    } catch (e: NoSuchMethodException) {\n                    } catch (e: ClassCastException) {\n                    } catch (e: InvocationTargetException) {\n                        var tagException: Throwable? = e\n                        while (tagException is InvocationTargetException) {\n                            tagException = e.cause\n                        }\n                        throw tagException ?: IllegalArgumentException(\"ViewBinding generic was found, but creation failed.\")\n                    }\n                }\n            }\n            genericSuperclass = superclass.genericSuperclass\n            superclass = superclass.superclass\n        }\n        throw IllegalArgumentException(\"There is no generic of ViewBinding.\")\n    }\n\n    /**\n     * Whether this click event is invalid.\n     *\n     * @param target target view\n     * @return true, invalid click event.\n     * @see .isInvalidClick\n     */\n    @JvmStatic\n    fun isInvalidClick(target: View): Boolean {\n        return isInvalidClick(target, INTERNAL_TIME)\n    }\n\n    /**\n     * Whether this click event is invalid.\n     *\n     * @param target       target view\n     * @param internalTime the internal time. The unit is millisecond.\n     * @return true, invalid click event.\n     */\n    @JvmStatic\n    fun isInvalidClick(target: View, @IntRange(from = 0) internalTime: Long): Boolean {\n        val curTimeStamp = System.currentTimeMillis()\n        val lastClickTimeStamp: Long\n        val o = target.getTag(R.id.last_click_time)\n        if (o == null) {\n            target.setTag(R.id.last_click_time, curTimeStamp)\n            return false\n        }\n        lastClickTimeStamp = o as Long\n        val isInvalid = curTimeStamp - lastClickTimeStamp < internalTime\n        if (!isInvalid) target.setTag(R.id.last_click_time, curTimeStamp)\n        return isInvalid\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView,\n        url: String?,\n        @DrawableRes placeholderId: Int,\n        radius: Float\n    ) {\n        //占位图和失败图是一样的时候\n        loadRoundImg(imageView, url, placeholderId, placeholderId, radius, true)\n    }\n\n    //Glide设置了圆角，并且是结合CenterCrop，所以选择跳过缓存，否则不一样的圆角图片会取错\n    //当然提供了保留缓存，在一些特定的场合使用\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView,\n        url: String?,\n        @DrawableRes placeholderId: Int,\n        radius: Float,\n        skipMemoryCache: Boolean\n    ) {\n        //占位图和失败图是一样的时候\n        loadRoundImg(imageView, url, placeholderId, placeholderId, radius, skipMemoryCache)\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView, url: String?, @DrawableRes placeholderId: Int,\n        @DrawableRes errorId: Int, radius: Float, skipMemoryCache: Boolean\n    ) {\n        loadRoundImg(imageView, url, placeholderId, errorId, radius, true, skipMemoryCache)\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView, imageUrl: String?, @DrawableRes placeholderId: Int,\n        @DrawableRes errorId: Int, radius: Float, centerCrop: Boolean, skipMemoryCache: Boolean\n    ) {\n        var url = imageUrl\n        if (url == null) {\n            url = \"\"\n        }\n        Glide.with(imageView.context)\n            .load(url)\n            .skipMemoryCache(skipMemoryCache)\n            .diskCacheStrategy(if (skipMemoryCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC)\n            .apply(\n                RequestOptions()\n                    .placeholder(placeholderId)\n                    .error(errorId)\n                    .transform(GlideRoundTransform(radius, centerCrop))\n            )\n            .thumbnail(\n                loadTransform(\n                    imageView.context,\n                    placeholderId,\n                    radius,\n                    centerCrop,\n                    skipMemoryCache\n                )\n            )\n            .thumbnail(\n                loadTransform(\n                    imageView.context,\n                    errorId,\n                    radius,\n                    centerCrop,\n                    skipMemoryCache\n                )\n            )\n            .into(imageView)\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView,\n        uri: Uri?,\n        @DrawableRes placeholderId: Int,\n        radius: Float,\n        skipMemoryCache: Boolean\n    ) {\n        //占位图和失败图是一样的时候\n        loadRoundImg(imageView, uri, placeholderId, placeholderId, radius, skipMemoryCache)\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView,\n        uri: Uri?,\n        @DrawableRes placeholderId: Int,\n        radius: Float\n    ) {\n        //占位图和失败图是一样的时候\n        loadRoundImg(imageView, uri, placeholderId, placeholderId, radius, true)\n    }\n\n    @JvmStatic\n    fun loadRoundImg(\n        imageView: ImageView, uri: Uri?, @DrawableRes placeholderId: Int,\n        @DrawableRes errorId: Int, radius: Float, skipMemoryCache: Boolean\n    ) {\n        Glide.with(imageView.context)\n            .load(uri ?: errorId)\n            .skipMemoryCache(skipMemoryCache)\n            .diskCacheStrategy(if (skipMemoryCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC)\n            .apply(\n                RequestOptions()\n                    .placeholder(placeholderId)\n                    .error(errorId)\n                    .transform(GlideRoundTransform(radius))\n            )\n            .thumbnail(loadTransform(imageView.context, placeholderId, radius, skipMemoryCache))\n            .thumbnail(loadTransform(imageView.context, errorId, radius, skipMemoryCache))\n            .into(imageView)\n    }\n\n    //圆形\n    @JvmStatic\n    fun loadCircleImg(\n        imageView: ImageView,\n        url: String?,\n        @DrawableRes placeholderId: Int,\n        skipMemoryCache: Boolean\n    ) {\n        //占位图和失败图是一样的时候\n        loadCircleImg(imageView, url, placeholderId, placeholderId, skipMemoryCache)\n    }\n\n    //圆形\n    @JvmStatic\n    fun loadCircleImg(imageView: ImageView, url: String?, @DrawableRes placeholderId: Int) {\n        //占位图和失败图是一样的时候\n        loadCircleImg(imageView, url, placeholderId, placeholderId, true)\n    }\n\n    //圆形\n    @JvmStatic\n    fun loadCircleImg(\n        imageView: ImageView, imageUrl: String?, @DrawableRes placeholderId: Int,\n        @DrawableRes errorId: Int, skipMemoryCache: Boolean\n    ) {\n        var url = imageUrl\n        if (url == null) {\n            url = \"\"\n        }\n        Glide.with(imageView.context)\n            .load(url)\n            .skipMemoryCache(skipMemoryCache)\n            .diskCacheStrategy(if (skipMemoryCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC)\n            .apply(\n                RequestOptions()\n                    .placeholder(placeholderId)\n                    .error(errorId)\n                    .transform(GlideCircleTransform())\n            )\n            .thumbnail(loadTransform(imageView.context, placeholderId, skipMemoryCache))\n            .thumbnail(loadTransform(imageView.context, errorId, skipMemoryCache))\n            .into(imageView)\n    }\n\n    private fun loadTransform(\n        context: Context, @DrawableRes placeholderId: Int,\n        radius: Float, skipMemoryCache: Boolean\n    ): RequestBuilder<Drawable?> {\n        return loadTransform(context, placeholderId, radius, true, skipMemoryCache)\n    }\n\n    private fun loadTransform(\n        context: Context, @DrawableRes placeholderId: Int,\n        radius: Float, centerCrop: Boolean, skipMemoryCache: Boolean\n    ): RequestBuilder<Drawable?> {\n        return Glide.with(context)\n            .load(placeholderId)\n            .skipMemoryCache(skipMemoryCache)\n            .diskCacheStrategy(if (skipMemoryCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC)\n            .apply(RequestOptions().transform(GlideRoundTransform(radius, centerCrop)))\n    }\n\n    private fun loadTransform(\n        context: Context,\n        @DrawableRes placeholderId: Int,\n        skipMemoryCache: Boolean\n    ): RequestBuilder<Drawable?> {\n        return Glide.with(context)\n            .load(placeholderId)\n            .skipMemoryCache(skipMemoryCache)\n            .diskCacheStrategy(if (skipMemoryCache) DiskCacheStrategy.NONE else DiskCacheStrategy.AUTOMATIC)\n            .apply(RequestOptions().transform(GlideCircleTransform()))\n    }\n}"
  },
  {
    "path": "support/src/main/java/com/bhm/support/sdk/widget/ChoseView.kt",
    "content": "package com.bhm.support.sdk.widget\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.text.TextUtils\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.Gravity\nimport android.view.View.OnClickListener\nimport android.view.ViewGroup\nimport android.widget.*\nimport androidx.appcompat.widget.AppCompatRadioButton\nimport androidx.core.content.ContextCompat\nimport androidx.recyclerview.widget.OrientationHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport com.bhm.support.sdk.R\nimport com.bhm.support.sdk.core.GridSpacingItemDecoration\nimport com.bhm.support.sdk.core.MyStaggeredGridLayoutManager\nimport com.bhm.support.sdk.utils.ViewUtil.isInvalidClick\nimport com.bhm.support.sdk.widget.ChoseView.CheckItemRecyclerAdapter.MyViewHolder\nimport com.noober.background.drawable.DrawableCreator\nimport java.io.Serializable\n\n/**\n * 选择控件\n */\n@Suppress(\"unused\")\nclass ChoseView : LinearLayout {\n    private var viewType = 0\n    private var isCircularView = false\n    private var isOnlyShow = false\n    private var itemViewHeight = 0f\n    private var itemViewWidth = 0f\n    private var itemViewPaddingStart = 0f\n    private var itemViewPaddingTop = 0f\n    private var itemViewPaddingEnd = 0f\n    private var itemViewPaddingBottom = 0f\n    private var itemViewMargin = 0f\n    private var itemViewCorners = 0f\n    private var itemViewStrokeWidth = 0f\n    private var cvGravity = 0\n    private var choseStyle = 0\n    private var unCheckBgColor = 0\n    private var checkBgColor = 0\n    private var unCheckTextColor = 0\n    private var checkTextColor = 0\n    private var unCheckStrokeColor = 0\n    private var checkStrokeColor = 0\n    private var itemViewTextSize = 0f\n    private var spanCount = 0\n    private var adapter: CheckItemRecyclerAdapter? = null\n    private var listData: List<IdNameEntity>? = null\n    private var selectCallBack1: SelectCallBack1? = null\n    private var selectCallBack: SelectCallBack? = null\n\n    constructor(context: Context) : super(context) {\n        init(context, null)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        init(context, attrs)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    ) {\n        init(context, attrs)\n    }\n\n    private fun init(context: Context, attrs: AttributeSet?) {\n        if (null == attrs) {\n            return\n        }\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChoseView)\n        //item的高度，为0则自适应\n        itemViewHeight = typedArray.getDimension(R.styleable.ChoseView_itemViewHeight, 0f)\n        //item的宽度，为0则自适应，当viewType=recyclerView即只有viewType=2时，这个值不需要设置\n        itemViewWidth = typedArray.getDimension(R.styleable.ChoseView_itemViewWidth, 0f)\n        itemViewPaddingStart =\n            typedArray.getDimension(R.styleable.ChoseView_itemViewPaddingStart, 0f)\n        itemViewPaddingTop = typedArray.getDimension(R.styleable.ChoseView_itemViewPaddingTop, 0f)\n        itemViewPaddingEnd = typedArray.getDimension(R.styleable.ChoseView_itemViewPaddingEnd, 0f)\n        itemViewPaddingBottom =\n            typedArray.getDimension(R.styleable.ChoseView_itemViewPaddingBottom, 0f)\n        //每个itemView之间的间隔\n        itemViewMargin = typedArray.getDimension(R.styleable.ChoseView_itemViewMargin, 0f)\n        //圆角\n        itemViewCorners = typedArray.getDimension(R.styleable.ChoseView_itemViewCorners, 0f)\n        //描边\n        itemViewStrokeWidth = typedArray.getDimension(R.styleable.ChoseView_itemViewStrokeWidth, 0f)\n        //fixed=0固定显示， scrollView=1单行横向滑动显示，recyclerView=2网格gridView方式显示\n        viewType = typedArray.getInt(R.styleable.ChoseView_viewType, 0)\n        //是否只显示，不能点击\n        isOnlyShow = typedArray.getBoolean(R.styleable.ChoseView_isOnlyShow, false)\n        //item是否圆形，圆形的时候，圆角无效，直径为高\n        isCircularView = typedArray.getBoolean(R.styleable.ChoseView_isCircularView, false)\n        //0左边，1右边，2居中，当viewType=recyclerView即只有viewType=2时，这个值不需要设置\n        cvGravity = typedArray.getInt(R.styleable.ChoseView_gravity, 0)\n        //single=0单选，选中后不能点击取消；singleUnNeeded=1 单选，选中后可以点击取消；multiple=2多选，选中后可以点击取消；\n        choseStyle = typedArray.getInt(R.styleable.ChoseView_choseStyle, 0)\n        //网格布局一行显示的个数，只有viewType=recyclerView即只有viewType=2才有效\n        spanCount = typedArray.getInt(R.styleable.ChoseView_spanCount, 4)\n        unCheckBgColor = typedArray.getColor(\n            R.styleable.ChoseView_unCheckBgColor,\n            ContextCompat.getColor(context, R.color.white)\n        )\n        checkBgColor = typedArray.getColor(\n            R.styleable.ChoseView_checkBgColor,\n            ContextCompat.getColor(context, R.color.color_main)\n        )\n        unCheckTextColor = typedArray.getColor(\n            R.styleable.ChoseView_unCheckTextColor,\n            ContextCompat.getColor(context, R.color.color_main)\n        )\n        checkTextColor = typedArray.getColor(\n            R.styleable.ChoseView_checkTextColor,\n            ContextCompat.getColor(context, R.color.color_2f)\n        )\n        unCheckStrokeColor = typedArray.getColor(R.styleable.ChoseView_unCheckStrokeColor, 0)\n        checkStrokeColor = typedArray.getColor(R.styleable.ChoseView_checkStrokeColor, 0)\n        if (unCheckStrokeColor == 0) {\n            unCheckStrokeColor = unCheckBgColor\n        }\n        if (checkStrokeColor == 0) {\n            checkStrokeColor = checkBgColor\n        }\n        itemViewTextSize =\n            typedArray.getDimensionPixelSize(R.styleable.ChoseView_itemViewTextSize, 42).toFloat()\n        typedArray.recycle()\n    }\n\n    fun setItemViewSize(itemViewWidth: Float, itemViewHeight: Float) {\n        this.itemViewWidth = itemViewWidth\n        this.itemViewHeight = itemViewHeight\n    }\n\n    fun setItemViewTextSize(itemViewTextSize: Float) {\n        this.itemViewTextSize = itemViewTextSize\n    }\n\n    fun setItemViewPadding(\n        itemViewPaddingStart: Float,\n        itemViewPaddingTop: Int,\n        itemViewPaddingEnd: Int,\n        itemViewPaddingBottom: Int\n    ) {\n        this.itemViewPaddingStart = itemViewPaddingStart\n        this.itemViewPaddingTop = itemViewPaddingTop.toFloat()\n        this.itemViewPaddingEnd = itemViewPaddingEnd.toFloat()\n        this.itemViewPaddingBottom = itemViewPaddingBottom.toFloat()\n    }\n\n    fun setItemViewMargin(itemViewMargin: Float) {\n        this.itemViewMargin = itemViewMargin\n    }\n\n    fun setItemViewCorners(itemViewCorners: Float) {\n        this.itemViewCorners = itemViewCorners\n    }\n\n    fun setItemViewStrokeWidth(itemViewStrokeWidth: Float) {\n        this.itemViewStrokeWidth = itemViewStrokeWidth\n    }\n\n    fun setBgColor(checkBgColor: Int, unCheckBgColor: Int) {\n        this.checkBgColor = checkBgColor\n        this.unCheckBgColor = unCheckBgColor\n    }\n\n    fun setTextColor(checkTextColor: Int, unCheckTextColor: Int) {\n        this.checkTextColor = checkTextColor\n        this.unCheckTextColor = unCheckTextColor\n    }\n\n    fun setCheckStrokeColor(checkStrokeColor: Int, unCheckStrokeColor: Int) {\n        this.checkStrokeColor = checkStrokeColor\n        this.unCheckStrokeColor = unCheckStrokeColor\n        if (unCheckStrokeColor == 0) {\n            this.unCheckStrokeColor = unCheckBgColor\n        }\n        if (checkStrokeColor == 0) {\n            this.checkStrokeColor = checkBgColor\n        }\n    }\n\n    fun setViewType(viewType: Int) {\n        this.viewType = viewType\n    }\n\n    override fun setGravity(cvGravity: Int) {\n        this.cvGravity = cvGravity\n    }\n\n    fun setChoseStyle(choseStyle: Int) {\n        this.choseStyle = choseStyle\n    }\n\n    fun setSpanCount(spanCount: Int) {\n        this.spanCount = spanCount\n    }\n\n    fun setIsCircularView(isCircularView: Boolean) {\n        this.isCircularView = isCircularView\n    }\n\n    fun setIsOnlyShow(isOnlyShow: Boolean) {\n        this.isOnlyShow = isOnlyShow\n    }\n\n    fun setData(entities: List<IdNameEntity>?) {\n        listData = entities\n        if (viewType == 0) {\n            //数量少，横向不超出一屏幕使用这个\n            if (choseStyle == 0 || choseStyle == 1) {\n                val radioGroup = RadioGroup(context)\n                radioGroup.orientation = HORIZONTAL\n                radioGroup.removeAllViews()\n                removeAllViews()\n                this.addView(radioGroup)\n                val params = LayoutParams(\n                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT\n                )\n                when (cvGravity) {\n                    0 -> {\n                        params.gravity = Gravity.START or Gravity.CENTER_VERTICAL\n                    }\n                    1 -> {\n                        params.gravity = Gravity.END or Gravity.CENTER_VERTICAL\n                    }\n                    2 -> {\n                        params.gravity = Gravity.CENTER\n                    }\n                }\n                radioGroup.layoutParams = params\n                val lp = RadioGroup.LayoutParams(itemViewWidth.toInt(), itemViewHeight.toInt())\n                lp.gravity = Gravity.CENTER\n                lp.setMargins(0, 0, itemViewMargin.toInt(), 0)\n                for (entity in listData!!) {\n                    var name = entity.name\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.title\n                    }\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.value\n                    }\n                    radioGroup.addView(getItemView0(name, entity.isCheck, entity.id), lp)\n                }\n                radioGroup.setOnCheckedChangeListener { group: RadioGroup, checkedId: Int ->\n                    if (choseStyle == 0) {\n                        //选中，再点击不能取消\n                        for (i in 0 until group.childCount) {\n                            val radioButton = group.getChildAt(i) as RadioButton\n                            radioButton.setTextColor(if (radioButton.id == checkedId) checkTextColor else unCheckTextColor)\n                            val drawable =\n                                DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                                    .setPadding(\n                                        itemViewPaddingStart,\n                                        itemViewPaddingTop,\n                                        itemViewPaddingEnd,\n                                        itemViewPaddingBottom\n                                    )\n                                    .setSolidColor(if (radioButton.id == checkedId) checkBgColor else unCheckBgColor)\n                                    .setStrokeColor(if (radioButton.id == checkedId) checkStrokeColor else unCheckStrokeColor)\n                                    .setStrokeWidth(itemViewStrokeWidth)\n                                    .build()\n                            radioButton.background = drawable\n                        }\n                    } else {\n                        for (i in 0 until group.childCount) {\n                            val radioButton = group.getChildAt(i) as SingleRadioButton\n                            radioButton.setTextColor(if (radioButton.id == checkedId) checkTextColor else unCheckTextColor)\n                            val drawable =\n                                DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                                    .setPadding(\n                                        itemViewPaddingStart,\n                                        itemViewPaddingTop,\n                                        itemViewPaddingEnd,\n                                        itemViewPaddingBottom\n                                    )\n                                    .setSolidColor(if (radioButton.id == checkedId) checkBgColor else unCheckBgColor)\n                                    .setStrokeColor(if (radioButton.id == checkedId) checkStrokeColor else unCheckStrokeColor)\n                                    .setStrokeWidth(itemViewStrokeWidth)\n                                    .build()\n                            radioButton.background = drawable\n                        }\n                    }\n                    if (selectCallBack != null) {\n                        selectCallBack!!.selectBack(singleCheckItemPosition)\n                    }\n                }\n            } else if (choseStyle == 2) {\n                val contentView = LinearLayout(context)\n                contentView.orientation = HORIZONTAL\n                contentView.removeAllViews()\n                removeAllViews()\n                this.addView(contentView)\n                val params = LayoutParams(\n                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT\n                )\n                when (cvGravity) {\n                    0 -> {\n                        params.gravity = Gravity.START or Gravity.CENTER_VERTICAL\n                    }\n                    1 -> {\n                        params.gravity = Gravity.END or Gravity.CENTER_VERTICAL\n                    }\n                    2 -> {\n                        params.gravity = Gravity.CENTER\n                    }\n                }\n                contentView.layoutParams = params\n                val lp = LayoutParams(itemViewWidth.toInt(), itemViewHeight.toInt())\n                lp.gravity = Gravity.CENTER\n                lp.setMargins(0, 0, itemViewMargin.toInt(), 0)\n                for (entity in listData!!) {\n                    var name = entity.name\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.title\n                    }\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.value\n                    }\n                    contentView.addView(getItemView2(name, entity.isCheck, entity.id), lp)\n                }\n            }\n        } else if (viewType == 1) {\n            if (choseStyle == 0 || choseStyle == 1) {\n                val scrollView = HorizontalScrollView(context)\n                scrollView.layoutParams =\n                    LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)\n                scrollView.isHorizontalScrollBarEnabled = false\n                val radioGroup = RadioGroup(context)\n                radioGroup.orientation = HORIZONTAL\n                radioGroup.removeAllViews()\n                scrollView.removeAllViews()\n                removeAllViews()\n                scrollView.addView(radioGroup)\n                this.addView(scrollView)\n                val params = FrameLayout.LayoutParams(\n                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT\n                )\n                when (cvGravity) {\n                    0 -> {\n                        params.gravity = Gravity.START or Gravity.CENTER_VERTICAL\n                    }\n                    1 -> {\n                        params.gravity = Gravity.END or Gravity.CENTER_VERTICAL\n                    }\n                    2 -> {\n                        params.gravity = Gravity.CENTER\n                    }\n                }\n                radioGroup.layoutParams = params\n                val lp = RadioGroup.LayoutParams(itemViewWidth.toInt(), itemViewHeight.toInt())\n                lp.gravity = Gravity.CENTER\n                lp.setMargins(0, 0, itemViewMargin.toInt(), 0)\n                for (entity in listData!!) {\n                    var name = entity.name\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.title\n                    }\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.value\n                    }\n                    radioGroup.addView(getItemView0(name, entity.isCheck, entity.id), lp)\n                }\n                radioGroup.setOnCheckedChangeListener { group: RadioGroup, checkedId: Int ->\n                    if (choseStyle == 0) {\n                        //选中，再点击不能取消\n                        for (i in 0 until group.childCount) {\n                            val radioButton = group.getChildAt(i) as RadioButton\n                            radioButton.setTextColor(if (radioButton.id == checkedId) checkTextColor else unCheckTextColor)\n                            val drawable =\n                                DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                                    .setPadding(\n                                        itemViewPaddingStart,\n                                        itemViewPaddingTop,\n                                        itemViewPaddingEnd,\n                                        itemViewPaddingBottom\n                                    )\n                                    .setSolidColor(if (radioButton.id == checkedId) checkBgColor else unCheckBgColor)\n                                    .setStrokeColor(if (radioButton.id == checkedId) checkStrokeColor else unCheckStrokeColor)\n                                    .setStrokeWidth(itemViewStrokeWidth)\n                                    .build()\n                            radioButton.background = drawable\n                        }\n                    } else {\n                        for (i in 0 until group.childCount) {\n                            val radioButton = group.getChildAt(i) as SingleRadioButton\n                            radioButton.setTextColor(if (radioButton.id == checkedId) checkTextColor else unCheckTextColor)\n                            val drawable =\n                                DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                                    .setPadding(\n                                        itemViewPaddingStart,\n                                        itemViewPaddingTop,\n                                        itemViewPaddingEnd,\n                                        itemViewPaddingBottom\n                                    )\n                                    .setSolidColor(if (radioButton.id == checkedId) checkBgColor else unCheckBgColor)\n                                    .setStrokeColor(if (radioButton.id == checkedId) checkStrokeColor else unCheckStrokeColor)\n                                    .setStrokeWidth(itemViewStrokeWidth)\n                                    .build()\n                            radioButton.background = drawable\n                        }\n                    }\n                    if (selectCallBack != null) {\n                        selectCallBack!!.selectBack(singleCheckItemPosition)\n                    }\n                }\n            } else if (choseStyle == 2) {\n                val scrollView = HorizontalScrollView(context)\n                scrollView.layoutParams =\n                    LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)\n                scrollView.isHorizontalScrollBarEnabled = false\n                val contentView = LinearLayout(context)\n                contentView.orientation = HORIZONTAL\n                scrollView.removeAllViews()\n                contentView.removeAllViews()\n                removeAllViews()\n                scrollView.addView(contentView)\n                this.addView(scrollView)\n                val params = FrameLayout.LayoutParams(\n                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT\n                )\n                when (cvGravity) {\n                    0 -> {\n                        params.gravity = Gravity.START or Gravity.CENTER_VERTICAL\n                    }\n                    1 -> {\n                        params.gravity = Gravity.END or Gravity.CENTER_VERTICAL\n                    }\n                    2 -> {\n                        params.gravity = Gravity.CENTER\n                    }\n                }\n                contentView.layoutParams = params\n                val lp = LayoutParams(itemViewWidth.toInt(), itemViewHeight.toInt())\n                lp.gravity = Gravity.CENTER\n                lp.setMargins(0, 0, itemViewMargin.toInt(), 0)\n                for (entity in listData!!) {\n                    var name = entity.name\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.title\n                    }\n                    if (TextUtils.isEmpty(name)) {\n                        name = entity.value\n                    }\n                    contentView.addView(getItemView2(name, entity.isCheck, entity.id), lp)\n                }\n            }\n        } else if (viewType == 2) {\n            removeAllViews()\n            val recyclerView = RecyclerView(context!!)\n            recyclerView.layoutParams =\n                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)\n            this.addView(recyclerView)\n            val manager = MyStaggeredGridLayoutManager(spanCount, OrientationHelper.VERTICAL)\n            manager.setScrollEnabled(false)\n            recyclerView.layoutManager = manager\n            recyclerView.setHasFixedSize(false)\n            recyclerView.addItemDecoration(\n                GridSpacingItemDecoration(\n                    spanCount,\n                    itemViewMargin.toInt(),\n                    false\n                )\n            )\n            adapter = CheckItemRecyclerAdapter(listData, object : SelectCallBack {\n                @SuppressLint(\"NotifyDataSetChanged\")\n                override fun selectBack(position: Int) {\n                    if (choseStyle == 0) {\n                        //单选，选中的不能取消\n                        if (listData!![position].isCheck) {\n                            return\n                        }\n                        for (itemEntity in listData!!) {\n                            itemEntity.isCheck = false\n                        }\n                        listData!![position].isCheck = true\n                        if (selectCallBack != null) {\n                            selectCallBack!!.selectBack(singleCheckItemPosition)\n                        }\n                    } else if (choseStyle == 1) {\n                        //单选，可以取消\n                        for (itemEntity in listData!!) {\n                            itemEntity.isCheck = false\n                        }\n                        if (!listData!![position].isCheck) {\n                            listData!![position].isCheck = true\n                        }\n                        if (selectCallBack != null) {\n                            selectCallBack!!.selectBack(singleCheckItemPosition)\n                        }\n                    } else {\n                        //多选\n                        listData!![position].isCheck = !listData!![position].isCheck\n                        if (selectCallBack1 != null) {\n                            selectCallBack1!!.selectBack(multipleCheckItemPosition)\n                        }\n                    }\n                    adapter!!.notifyDataSetChanged()\n                }\n            })\n            recyclerView.adapter = adapter\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun refreshRecyclerView() {\n        if (adapter != null) {\n            adapter!!.notifyDataSetChanged()\n        }\n    }\n\n    //单选\n    val singleCheckItemPosition: Int\n        get() {\n            if (choseStyle == 0 || choseStyle == 1) {\n                //单选\n                when (viewType) {\n                    0 -> {\n                        val radioGroup = getChildAt(0) as RadioGroup\n                        return radioGroup.checkedRadioButtonId\n                    }\n                    1 -> {\n                        val scrollView = getChildAt(0) as HorizontalScrollView\n                        val radioGroup = scrollView.getChildAt(0) as RadioGroup\n                        return radioGroup.checkedRadioButtonId\n                    }\n                    2 -> {\n                        for (entity in listData!!) {\n                            if (entity.isCheck) {\n                                return entity.id\n                            }\n                        }\n                    }\n                }\n            }\n            return -1\n        }\n\n    //多选\n    val multipleCheckItemPosition: ArrayList<Int?>?\n        get() {\n            if (choseStyle == 2) {\n                //多选\n                val res = ArrayList<Int?>()\n                when (viewType) {\n                    0 -> {\n                        val contentView = getChildAt(0) as LinearLayout\n                        for (i in 0 until contentView.childCount) {\n                            val checkBox = contentView.getChildAt(i) as CheckBox\n                            if (checkBox.isChecked) {\n                                res.add(checkBox.id)\n                            }\n                        }\n                    }\n                    1 -> {\n                        val scrollView = getChildAt(0) as HorizontalScrollView\n                        val contentView = scrollView.getChildAt(0) as LinearLayout\n                        for (i in 0 until contentView.childCount) {\n                            val checkBox = contentView.getChildAt(i) as CheckBox\n                            if (checkBox.isChecked) {\n                                res.add(checkBox.id)\n                            }\n                        }\n                    }\n                    2 -> {\n                        for (entity in listData!!) {\n                            if (entity.isCheck) {\n                                res.add(entity.id)\n                            }\n                        }\n                    }\n                }\n                return res\n            }\n            return null\n        }\n\n    fun setSelectCallBackSingle(selectCallBack: SelectCallBack?) {\n        this.selectCallBack = selectCallBack\n    }\n\n    fun setSelectCallBackMultiple(selectCallBack: SelectCallBack1?) {\n        selectCallBack1 = selectCallBack\n    }\n\n    private fun getItemView0(text: String?, isCheck: Boolean, id: Int): RadioButton {\n        val rb: RadioButton = if (choseStyle == 0) {\n            //选中，再点击不能取消\n            RadioButton(context)\n        } else {\n            SingleRadioButton(context)\n        }\n        rb.text = text\n        rb.setTextSize(TypedValue.COMPLEX_UNIT_PX, itemViewTextSize)\n        rb.gravity = Gravity.CENTER\n        rb.setButtonDrawable(android.R.color.transparent)\n        rb.setTextColor(if (isCheck) checkTextColor else unCheckTextColor)\n        rb.setBackgroundColor(if (isCheck) checkBgColor else unCheckBgColor)\n        rb.id = id\n        if (isCircularView) {\n            itemViewCorners = itemViewHeight\n        }\n        val drawable = DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n            .setPadding(\n                itemViewPaddingStart,\n                itemViewPaddingTop,\n                itemViewPaddingEnd,\n                itemViewPaddingBottom\n            )\n            .setSolidColor(if (isCheck) checkBgColor else unCheckBgColor)\n            .setStrokeColor(if (isCheck) checkStrokeColor else unCheckStrokeColor)\n            .setStrokeWidth(itemViewStrokeWidth)\n            .build()\n        rb.background = drawable\n        rb.isChecked = isCheck\n        rb.isEnabled = !isOnlyShow\n        return rb\n    }\n\n    private fun getItemView2(text: String?, isCheck: Boolean, id: Int): CheckBox {\n        val checkBox = CheckBox(context)\n        checkBox.text = text\n        checkBox.setTextSize(TypedValue.COMPLEX_UNIT_PX, itemViewTextSize)\n        checkBox.gravity = Gravity.CENTER\n        checkBox.setButtonDrawable(android.R.color.transparent)\n        checkBox.setTextColor(if (isCheck) checkTextColor else unCheckTextColor)\n        checkBox.setBackgroundColor(if (isCheck) checkBgColor else unCheckBgColor)\n        checkBox.id = id\n        if (isCircularView) {\n            itemViewCorners = itemViewHeight\n        }\n        val drawable = DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n            .setPadding(\n                itemViewPaddingStart,\n                itemViewPaddingTop,\n                itemViewPaddingEnd,\n                itemViewPaddingBottom\n            )\n            .setSolidColor(if (isCheck) checkBgColor else unCheckBgColor)\n            .setStrokeColor(if (isCheck) checkStrokeColor else unCheckStrokeColor)\n            .setStrokeWidth(itemViewStrokeWidth)\n            .build()\n        checkBox.background = drawable\n        checkBox.isChecked = isCheck\n        checkBox.isEnabled = !isOnlyShow\n        checkBox.setOnCheckedChangeListener { buttonView: CompoundButton, isChecked: Boolean ->\n            buttonView.setTextColor(if (isChecked) checkTextColor else unCheckTextColor)\n            val drawable1 = DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                .setPadding(\n                    itemViewPaddingStart,\n                    itemViewPaddingTop,\n                    itemViewPaddingEnd,\n                    itemViewPaddingBottom\n                )\n                .setSolidColor(if (isChecked) checkBgColor else unCheckBgColor)\n                .setStrokeColor(if (isCheck) checkStrokeColor else unCheckStrokeColor)\n                .setStrokeWidth(itemViewStrokeWidth)\n                .build()\n            buttonView.background = drawable1\n            if (selectCallBack1 != null) {\n                selectCallBack1!!.selectBack(multipleCheckItemPosition)\n            }\n        }\n        return checkBox\n    }\n\n    internal inner class CheckItemRecyclerAdapter(\n        private val list: List<IdNameEntity>?,\n        private val callBack: SelectCallBack\n    ) : RecyclerView.Adapter<MyViewHolder>() {\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {\n            val linearLayout = LinearLayout(context)\n            val textView = TextView(context)\n            val params: LayoutParams\n            if (isCircularView) {\n                itemViewCorners = itemViewHeight\n                params = LayoutParams(itemViewHeight.toInt(), itemViewHeight.toInt())\n            } else if (itemViewWidth == 0f){\n                params = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemViewHeight.toInt())\n            } else {\n                params = LayoutParams(itemViewWidth.toInt(), itemViewHeight.toInt())\n            }\n            linearLayout.addView(textView, params)\n            textView.gravity = Gravity.CENTER\n            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, itemViewTextSize)\n            textView.layoutParams = params\n            textView.gravity = Gravity.CENTER\n            return MyViewHolder(linearLayout, textView)\n        }\n\n        override fun onBindViewHolder(\n            holder: MyViewHolder,\n            @SuppressLint(\"RecyclerView\") position: Int\n        ) {\n            if (isCircularView) {\n                itemViewCorners = itemViewHeight - 1 //显得更圆一点\n            }\n            val drawable = DrawableCreator.Builder().setCornersRadius(itemViewCorners)\n                .setPadding(\n                    itemViewPaddingStart,\n                    itemViewPaddingTop,\n                    itemViewPaddingEnd,\n                    itemViewPaddingBottom\n                )\n                .setSolidColor(if (list!![position].isCheck) checkBgColor else unCheckBgColor)\n                .setStrokeColor(if (list[position].isCheck) checkStrokeColor else unCheckStrokeColor)\n                .setStrokeWidth(itemViewStrokeWidth)\n                .build()\n            holder.textView.setTextColor(if (list[position].isCheck) checkTextColor else unCheckTextColor)\n            holder.textView.setBackgroundColor(if (list[position].isCheck) checkBgColor else unCheckBgColor)\n            holder.textView.background = drawable\n            holder.textView.isEnabled = !isOnlyShow\n            var name = list[position].name\n            if (TextUtils.isEmpty(name)) {\n                name = list[position].title\n            }\n            if (TextUtils.isEmpty(name)) {\n                name = list[position].value\n            }\n            holder.textView.text = name\n            holder.textView.setOnClickListener(OnClickListener { v ->\n                if (isInvalidClick(v, 300)) {\n                    return@OnClickListener\n                }\n                callBack.selectBack(position)\n            })\n        }\n\n        override fun getItemCount(): Int {\n            return list!!.size\n        }\n\n        internal inner class MyViewHolder(linearLayout: LinearLayout?, var textView: TextView) :\n            RecyclerView.ViewHolder(\n                linearLayout!!\n            )\n    }\n\n    /**\n     * RadioButton可以取消选中\n     */\n    internal class SingleRadioButton : AppCompatRadioButton {\n        constructor(context: Context?) : super(context)\n        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)\n        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(\n            context,\n            attrs,\n            defStyleAttr\n        )\n\n        override fun toggle() {\n            isChecked = !isChecked\n            if (!isChecked) {\n                (parent as RadioGroup).clearCheck()\n            }\n        }\n    }\n\n    interface SelectCallBack {\n        fun selectBack(position: Int)\n    }\n\n    interface SelectCallBack1 {\n        fun selectBack(positions: List<Int?>?)\n    }\n\n    class IdNameEntity : Serializable {\n        constructor()\n        constructor(id: Int, name: String?) {\n            this.id = id\n            this.name = name\n        }\n\n        constructor(id: Int, name: String?, check: Boolean) {\n            this.id = id\n            this.name = name\n            isCheck = check\n        }\n\n        constructor(id: Int, name: String?, value: String?) {\n            this.id = id\n            this.name = name\n            this.value = value\n        }\n\n        var id = 0\n        var name: String? = null\n        var isCheck = false\n        var value: String? = null\n        var title: String? = null\n    }\n}"
  },
  {
    "path": "support/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <attr format=\"integer\" name=\"gravity\">\n        <flag name=\"start\" value=\"0\"/>\n        <flag name=\"end\" value=\"1\"/>\n        <flag name=\"center\" value=\"2\"/>\n    </attr>\n\n    <attr format=\"boolean\" name=\"isOnlyShow\"/>\n\n    <declare-styleable name=\"ChoseView\">\n        <attr format=\"integer\" name=\"viewType\">\n            <flag name=\"fixed\" value=\"0\"/>\n            <flag name=\"scrollView\" value=\"1\"/>\n            <flag name=\"recyclerView\" value=\"2\"/>\n        </attr>\n        <attr name=\"isOnlyShow\"/>\n        <attr name=\"gravity\"/>\n        <attr format=\"integer\" name=\"choseStyle\">\n            <flag name=\"single\" value=\"0\"/>\n            <flag name=\"singleUnNeeded\" value=\"1\"/>\n            <flag name=\"multiple\" value=\"2\"/>\n        </attr>\n        <attr name=\"spanCount\"/>\n        <attr format=\"dimension\" name=\"itemViewHeight\"/>\n        <attr format=\"dimension\" name=\"itemViewWidth\"/>\n        <attr format=\"dimension\" name=\"itemViewStrokeWidth\"/>\n        <attr format=\"dimension\" name=\"itemViewPaddingStart\"/>\n        <attr format=\"dimension\" name=\"itemViewPaddingTop\"/>\n        <attr format=\"dimension\" name=\"itemViewPaddingEnd\"/>\n        <attr format=\"dimension\" name=\"itemViewPaddingBottom\"/>\n        <attr format=\"dimension\" name=\"itemViewCorners\"/>\n        <attr format=\"dimension\" name=\"itemViewMargin\"/>\n        <attr format=\"color\" name=\"unCheckBgColor\"/>\n        <attr format=\"color\" name=\"checkBgColor\"/>\n        <attr format=\"color\" name=\"unCheckTextColor\"/>\n        <attr format=\"color\" name=\"checkTextColor\"/>\n        <attr format=\"color\" name=\"unCheckStrokeColor\"/>\n        <attr format=\"color\" name=\"checkStrokeColor\"/>\n        <attr format=\"dimension\" name=\"itemViewTextSize\"/>\n        <attr format=\"boolean\" name=\"isCircularView\"/>\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "support/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"white\">#FFFFFF</color><!--主色-->\n    <color name=\"color_main\">#3D3DED</color><!--主色-->\n    <color name=\"color_2f\">#212427</color><!--重要文字、标题-->\n</resources>\n"
  },
  {
    "path": "support/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n    <item name=\"last_click_time\" type=\"id\"/>\n</resources>"
  }
]