├── .gitignore ├── FAQ.md ├── LICENSE ├── README.md ├── VERSION.md ├── _config.yml ├── baseble ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── vise │ │ └── baseble │ │ ├── ViseBle.java │ │ ├── callback │ │ ├── IBleCallback.java │ │ ├── IConnectCallback.java │ │ ├── IRssiCallback.java │ │ └── scan │ │ │ ├── IScanCallback.java │ │ │ ├── IScanFilter.java │ │ │ ├── ListFilterScanCallback.java │ │ │ ├── RegularFilterScanCallback.java │ │ │ ├── ScanCallback.java │ │ │ ├── SingleFilterScanCallback.java │ │ │ └── UuidFilterScanCallback.java │ │ ├── common │ │ ├── BleConfig.java │ │ ├── BleConstant.java │ │ ├── BleExceptionCode.java │ │ ├── BluetoothServiceType.java │ │ ├── ConnectState.java │ │ └── PropertyType.java │ │ ├── core │ │ ├── BluetoothGattChannel.java │ │ ├── DeviceMirror.java │ │ ├── DeviceMirrorPool.java │ │ └── LruHashMap.java │ │ ├── exception │ │ ├── BleException.java │ │ ├── ConnectException.java │ │ ├── GattException.java │ │ ├── InitiatedException.java │ │ ├── OtherException.java │ │ ├── TimeoutException.java │ │ └── handler │ │ │ ├── BleExceptionHandler.java │ │ │ └── DefaultBleExceptionHandler.java │ │ ├── model │ │ ├── BluetoothLeDevice.java │ │ ├── BluetoothLeDeviceStore.java │ │ ├── adrecord │ │ │ ├── AdRecord.java │ │ │ └── AdRecordStore.java │ │ └── resolver │ │ │ ├── BluetoothClassResolver.java │ │ │ ├── CompanyIdentifierResolver.java │ │ │ └── GattAttributeResolver.java │ │ └── utils │ │ ├── AdRecordUtil.java │ │ ├── BleUtil.java │ │ └── HexUtil.java │ └── res │ └── values │ └── strings.xml ├── bintray.gradle ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── jar.gradle ├── newapp ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── vise │ │ └── bledemo │ │ ├── activity │ │ ├── DeviceControlActivity.java │ │ ├── DeviceDetailActivity.java │ │ ├── DeviceScanActivity.java │ │ └── MainActivity.java │ │ ├── adapter │ │ ├── DeviceAdapter.java │ │ ├── DeviceMainAdapter.java │ │ ├── MergeAdapter.java │ │ └── SackOfViewsAdapter.java │ │ ├── common │ │ ├── BluetoothDeviceManager.java │ │ └── ToastUtil.java │ │ └── event │ │ ├── CallbackDataEvent.java │ │ ├── ConnectEvent.java │ │ ├── NotifyDataEvent.java │ │ └── ScanEvent.java │ └── res │ ├── drawable │ └── toast_shape.xml │ ├── layout │ ├── actionbar_progress_indeterminate.xml │ ├── activity_device_control.xml │ ├── activity_device_detail.xml │ ├── activity_device_scan.xml │ ├── activity_main.xml │ ├── common_layout_toast.xml │ ├── item_gatt_services.xml │ ├── item_main_layout.xml │ ├── item_scan_layout.xml │ ├── list_item_view_adrecord.xml │ ├── list_item_view_device_info.xml │ ├── list_item_view_header.xml │ ├── list_item_view_rssi_info.xml │ └── list_item_view_textview.xml │ ├── menu │ ├── about.xml │ ├── connect.xml │ ├── details.xml │ └── scan.xml │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── screenshot ├── BLE_V2.0.6.apk ├── screenshot.gif └── wxpay.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | gradlew 7 | gradlew.bat 8 | /.idea 9 | gradle 10 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | ## FAQ 2 | 3 | ### 扫描不到设备? 4 | 首先确认该设备是不是低功耗设备,很多人会拿这个去扫描手机,这是扫描不到的,因为目前手机虽然是双模的,但是不会一直向外广播自己,一般都是作为中心设备。如果需要扫描手机需要用传统蓝牙的扫描方式 startDiscovery(),更多的使用详情可以参考我的博文[Android BLE学习笔记](http://blog.csdn.net/xiaoyaoyou1212/article/details/51854454)。 5 | 6 | ### 连接不上设备? 7 | 首先确认设备本身是否有问题,可以用其他蓝牙工具试下是否可以连接。如果确认设备没问题,那么用其他手机试试是否可以,多试试不同系统的,如果其他手机可以,那么可以确认是兼容性问题,因为不同的手机与设备组合后可能模块不能兼容,这个目前该框架没有做相关的兼容处理,如果你有好的处理方式,欢迎加群交流! 8 | 9 | ### 设备连接很慢? 10 | 这个是正常的,除非做了相关的模块兼容处理,一般第一次连接设备都会有点慢,只要不释放资源,后面连接都很快,还有部分 Android 7.0 的手机比其他的 Android 系统连接设备要慢。 11 | 12 | ### 连接设备时报 133 错误? 13 | 这个一般是没有调用 close 导致的问题,该框架已经做了相关处理,如果还出现该问题,那么可能是兼容性问题,建议换个手机再试试。 14 | 15 | ### 连接设备时报 257 错误? 16 | 这个是因为同一个设备在同一时刻有多次连接操作,导致蓝牙阻塞,建议使用同步机制控制连接操作。 17 | 18 | ### 发送数据时报 13 错误? 19 | 这个一般是该 UUID 对应的特征值不具备写入能力或者该通道发送的数据设备不能接收,建议换个有可写能力 UUID 生成的特征值试试。 20 | 21 | ### 连接成功后发送不了数据? 22 | 确保你选择的服务和特征值是支持可写的,可以用群里提供的demo APK试试写入数据。 23 | 24 | ### 连接成功后接收不到设备发送过来的数据? 25 | 确保你选择的服务和特征值是支持通知的,还有需要确定该UUID是可通知方式还是指示器方式,这两种方式是有区别的,指示器方式底层封装了应答机制,比可通知方式更可靠,在使用框架时确保传入的那个 isIndication 是否是对的。测试时可以先用群里提供的 demo APK 先验证是否可以收到设备的数据。 26 | 27 | ### 接收数据的可通知方式与指示器方式有什么区别,该怎么选择? 28 | 可通知方式:设备将要发送的数据直接发送,不管接收方有没有收到; 29 | 指示器方式:设备每发送一次数据都会等待接收方的应答,如果没有应答会重复发送,如果有应答才会进行下一次数据的发送。 30 | 这两种方式App端只需要根据需求选择就行,不需要关系是否需要应答,这个应答机制协议层已经做了封装。至于什么时候选择什么方式,一般情况是:如果是需要保证数据到达的准确性那么就选择指示器方式,而如果是只需要保证数据快速发送不太关心数据是否准确到达那么就选择可通知方式。 31 | 32 | ### 收发数据超过 20 字节怎么处理? 33 | 34 | 如果收发数据超过 20 字节,在发送时需要进行分包处理,接收时则需要进行组包处理。由于该库是基础的通信库,与数据处理等不进行挂钩,而组包一般与协议相关,故没有在该库中进行处理,而需要上层在调用数据发送和接收数据时统一进行处理。由于最近有人在使用库时问到分包的问题,故在此统一进行说明下,使用时可参考如下方式进行分包组包处理。 35 | 36 | 分包处理如下: 37 | ``` 38 | //外部调用发送数据方法 39 | public void write(final BluetoothLeDevice bluetoothLeDevice, byte[] data) { 40 | if (dataInfoQueue != null) { 41 | dataInfoQueue.clear(); 42 | dataInfoQueue = splitPacketFor20Byte(data); 43 | new Handler(Looper.getMainLooper()).post(new Runnable() { 44 | @Override 45 | public void run() { 46 | send(bluetoothLeDevice); 47 | } 48 | }); 49 | } 50 | } 51 | 52 | //发送队列,提供一种简单的处理方式,实际项目场景需要根据需求优化 53 | private Queue dataInfoQueue = new LinkedList<>(); 54 | private void send(final BluetoothLeDevice bluetoothLeDevice) { 55 | if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) { 56 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 57 | if (dataInfoQueue.peek() != null && deviceMirror != null) { 58 | deviceMirror.writeData(dataInfoQueue.poll()); 59 | } 60 | if (dataInfoQueue.peek() != null) { 61 | new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { 62 | @Override 63 | public void run() { 64 | send(bluetoothLeDevice); 65 | } 66 | }, 100); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * 数据分包 73 | * 74 | * @param data 75 | * @return 76 | */ 77 | private Queue splitPacketFor20Byte(byte[] data) { 78 | Queue dataInfoQueue = new LinkedList<>(); 79 | if (data != null) { 80 | int index = 0; 81 | do { 82 | byte[] surplusData = new byte[data.length - index]; 83 | byte[] currentData; 84 | System.arraycopy(data, index, surplusData, 0, data.length - index); 85 | if (surplusData.length <= 20) { 86 | currentData = new byte[surplusData.length]; 87 | System.arraycopy(surplusData, 0, currentData, 0, surplusData.length); 88 | index += surplusData.length; 89 | } else { 90 | currentData = new byte[20]; 91 | System.arraycopy(data, index, currentData, 0, 20); 92 | index += 20; 93 | } 94 | dataInfoQueue.offer(currentData); 95 | } while (index < data.length); 96 | } 97 | return dataInfoQueue; 98 | } 99 | ``` 100 | 101 | 组包处理如下所示: 102 | ``` 103 | private byte[] buffer = new byte[1024]; 104 | private int bufferIndex = 0; 105 | 106 | //数据组包处理,收到数据后就调用此方法 107 | public void parse(byte[] bytes) { 108 | if (bytes == null) { 109 | return; 110 | } 111 | BleLog.i("receive packet:" + HexUtil.encodeHexStr(bytes)); 112 | if (0 != bufferIndex) {//如果当前buffer有数据,就直接拷贝 113 | System.arraycopy(bytes, 0, buffer, bufferIndex, bytes.length); 114 | } else {//如果没有数据,判断当前的数据头部是不是协议头,这里默认协议头是0xFF 115 | if (bytes[0] == 0xFF && bufferIndex == 0) { 116 | //计算数据长度,根据协议中长度字段以及协议头、校验码长度 117 | bufferLength = ConvertUtil.bytesToIntHigh(new byte[]{bytes[1], bytes[2]}, 0) + 3; 118 | buffer = new byte[bufferLength]; 119 | System.arraycopy(bytes, 0, buffer, 0, bytes.length); 120 | } 121 | } 122 | //数据包拷进来后要移位 123 | bufferIndex += bytes.length; 124 | final byte[] data = new byte[bufferIndex]; 125 | System.arraycopy(buffer, 0, data, 0, data.length); 126 | if (isRightPacket(data)) {//判断数据是否符合协议要求 127 | BleLog.i("receive data:" + HexUtil.encodeHexStr(data)); 128 | bufferIndex = 0;//位置清零 129 | receiveData(data); 130 | } 131 | } 132 | 133 | //数据处理 134 | private void receiveData(byte[] data) { 135 | //处理组包后的数据 136 | } 137 | ``` 138 | -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | ## 版本说明 2 | - V2.0.6(2018-04-25) 3 | - 增加字节数组与整型数转换方法; 4 | - 增加间隔扫描的配置; 5 | - 增加获取服务、特征值、属性的方法。 6 | 7 | - V2.0.5(2017-12-21) 8 | - 修复encodeHexStr参数为空出现的空指针异常; 9 | - 修复注册多个可通知通道后数据返回重复回调问题; 10 | - 修复RegularFilterScanCallback设备名称可能为空的问题; 11 | - 优化设置通知的默认描述属性UUID。 12 | 13 | - V2.0.4(2017-11-21) 14 | - 修复扫描卡顿问题; 15 | - 优化断开连接处理。 16 | 17 | - V2.0.3(2017-11-14) 18 | - 增加解绑通道的 API。 19 | 20 | - V2.0.2(2017-11-10) 21 | - 修复操作数据回调异常; 22 | - 增加获取设备连接失败正在重试次数的 API; 23 | - 增加获取设备操作数据失败正在重试次数的 API。 24 | 25 | - V2.0.1(2017-11-09) 26 | - 修复频繁重复连接同一款设备导致的回调异常。 27 | 28 | - V2.0.0(2017-10-30) 29 | - 全新改版,调整了核心类的架构,增加了多设备连接管理,使用方式与第一版类似。 30 | 31 | - V1.0.10(2017-07-05) 32 | - 修复Android 5.0新连接方式ScanSettings未设置导致的异常。 33 | 34 | - V1.0.8(2017-05-19) 35 | - 修复连接指定设备中连接成功后继续返回连接失败的问题。 36 | 37 | - V1.0.7(2017-04-25) 38 | - 增加根据特定条件过滤扫描列表的回调,可以根据信号强度过滤,也可以根据设备名称正则过滤。 39 | 40 | - V1.0.6(2017-04-24) 41 | - 修改日志打印,使用ViseLog库作为日志打印基础库,日志管理更方便; 42 | - 修改数据操作回调相关,将IBleCallback进行拆分,此处使用泛型不太合理,故去掉泛型操作,防止出现类转换异常。 43 | 44 | - V1.0.5(2017-03-18) 45 | - 修复Android 5.0新扫描方式可能出现的空指针异常。 46 | 47 | - V1.0.3(2016-10-29) 48 | - 增加针对Android 5.0以上系统的新扫描方式。 49 | 50 | - V1.0.2(2016-09-01) 51 | - 优化初始化方式,将Context定义为Application级别。 52 | 53 | - V1.0.1(2016-08-29) 54 | - 优化通知回调功能。 55 | 56 | - V1.0.0(2016-08-05) 57 | - 项目初始提交。 58 | 59 | 版本号说明:版本号第一位为大版本更新时使用,第二位为小功能更新时使用,第三位则是用来bug修复管理。 -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /baseble/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /baseble/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 18 9 | targetSdkVersion 23 10 | versionCode 26 11 | versionName "2.0.6" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile 'com.vise.xiaoyaoyou:viselog:1.1.2' 23 | } 24 | 25 | apply from: '../bintray.gradle' -------------------------------------------------------------------------------- /baseble/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/xyy/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /baseble/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/ViseBle.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothManager; 5 | import android.content.Context; 6 | import android.os.Handler; 7 | import android.os.Looper; 8 | import android.text.TextUtils; 9 | 10 | import com.vise.baseble.callback.IConnectCallback; 11 | import com.vise.baseble.callback.scan.IScanCallback; 12 | import com.vise.baseble.callback.scan.ScanCallback; 13 | import com.vise.baseble.callback.scan.SingleFilterScanCallback; 14 | import com.vise.baseble.common.BleConfig; 15 | import com.vise.baseble.common.ConnectState; 16 | import com.vise.baseble.core.DeviceMirror; 17 | import com.vise.baseble.core.DeviceMirrorPool; 18 | import com.vise.baseble.exception.TimeoutException; 19 | import com.vise.baseble.model.BluetoothLeDevice; 20 | import com.vise.baseble.model.BluetoothLeDeviceStore; 21 | import com.vise.log.ViseLog; 22 | 23 | /** 24 | * @Description: BLE设备操作入口 25 | * @author: DAWI 26 | * @date: 17/8/1 22:24. 27 | */ 28 | public class ViseBle { 29 | private Context context;//上下文 30 | private BluetoothManager bluetoothManager;//蓝牙管理 31 | private BluetoothAdapter bluetoothAdapter;//蓝牙适配器 32 | private DeviceMirrorPool deviceMirrorPool;//设备连接池 33 | private DeviceMirror lastDeviceMirror;//上次操作设备镜像 34 | 35 | private static ViseBle instance;//入口操作管理 36 | private static BleConfig bleConfig = BleConfig.getInstance(); 37 | 38 | /** 39 | * 单例方式获取蓝牙通信入口 40 | * 41 | * @return 返回ViseBluetooth 42 | */ 43 | public static ViseBle getInstance() { 44 | if (instance == null) { 45 | synchronized (ViseBle.class) { 46 | if (instance == null) { 47 | instance = new ViseBle(); 48 | } 49 | } 50 | } 51 | return instance; 52 | } 53 | 54 | private ViseBle() { 55 | } 56 | 57 | /** 58 | * 获取配置对象,可进行相关配置的修改 59 | * 60 | * @return 61 | */ 62 | public static BleConfig config() { 63 | return bleConfig; 64 | } 65 | 66 | /** 67 | * 初始化 68 | * 69 | * @param context 上下文 70 | */ 71 | public void init(Context context) { 72 | if (this.context == null && context != null) { 73 | this.context = context.getApplicationContext(); 74 | bluetoothManager = (BluetoothManager) this.context.getSystemService(Context.BLUETOOTH_SERVICE); 75 | bluetoothAdapter = bluetoothManager.getAdapter(); 76 | deviceMirrorPool = new DeviceMirrorPool(); 77 | } 78 | } 79 | 80 | /** 81 | * 开始扫描 82 | * 83 | * @param scanCallback 自定义回调 84 | */ 85 | public void startScan(ScanCallback scanCallback) { 86 | if (scanCallback == null) { 87 | throw new IllegalArgumentException("this ScanCallback is Null!"); 88 | } 89 | scanCallback.setScan(true).scan(); 90 | } 91 | 92 | /** 93 | * 停止扫描 94 | * 95 | * @param scanCallback 自定义回调 96 | */ 97 | public void stopScan(ScanCallback scanCallback) { 98 | if (scanCallback == null) { 99 | throw new IllegalArgumentException("this ScanCallback is Null!"); 100 | } 101 | scanCallback.setScan(false).removeHandlerMsg().scan(); 102 | } 103 | 104 | /** 105 | * 连接设备 106 | * 107 | * @param bluetoothLeDevice 108 | * @param connectCallback 109 | */ 110 | public void connect(BluetoothLeDevice bluetoothLeDevice, IConnectCallback connectCallback) { 111 | if (bluetoothLeDevice == null || connectCallback == null) { 112 | ViseLog.e("This bluetoothLeDevice or connectCallback is null."); 113 | return; 114 | } 115 | if (deviceMirrorPool != null && !deviceMirrorPool.isContainDevice(bluetoothLeDevice)) { 116 | DeviceMirror deviceMirror = new DeviceMirror(bluetoothLeDevice); 117 | if (lastDeviceMirror != null && !TextUtils.isEmpty(lastDeviceMirror.getUniqueSymbol()) 118 | && lastDeviceMirror.getUniqueSymbol().equals(deviceMirror.getUniqueSymbol())) { 119 | deviceMirror = lastDeviceMirror;//防止重复创建设备镜像 120 | } 121 | deviceMirror.connect(connectCallback); 122 | lastDeviceMirror = deviceMirror; 123 | } else { 124 | ViseLog.i("This device is connected."); 125 | } 126 | } 127 | 128 | /** 129 | * 连接指定mac地址的设备 130 | * 131 | * @param mac 设备mac地址 132 | * @param connectCallback 连接回调 133 | */ 134 | public void connectByMac(String mac, final IConnectCallback connectCallback) { 135 | if (mac == null || connectCallback == null) { 136 | ViseLog.e("This mac or connectCallback is null."); 137 | return; 138 | } 139 | startScan(new SingleFilterScanCallback(new IScanCallback() { 140 | @Override 141 | public void onDeviceFound(BluetoothLeDevice bluetoothLeDevice) { 142 | 143 | } 144 | 145 | @Override 146 | public void onScanFinish(final BluetoothLeDeviceStore bluetoothLeDeviceStore) { 147 | if (bluetoothLeDeviceStore.getDeviceList().size() > 0) { 148 | new Handler(Looper.getMainLooper()).post(new Runnable() { 149 | @Override 150 | public void run() { 151 | connect(bluetoothLeDeviceStore.getDeviceList().get(0), connectCallback); 152 | } 153 | }); 154 | } else { 155 | connectCallback.onConnectFailure(new TimeoutException()); 156 | } 157 | } 158 | 159 | @Override 160 | public void onScanTimeout() { 161 | connectCallback.onConnectFailure(new TimeoutException()); 162 | } 163 | 164 | }).setDeviceMac(mac)); 165 | } 166 | 167 | /** 168 | * 连接指定设备名称的设备 169 | * 170 | * @param name 设备名称 171 | * @param connectCallback 连接回调 172 | */ 173 | public void connectByName(String name, final IConnectCallback connectCallback) { 174 | if (name == null || connectCallback == null) { 175 | ViseLog.e("This name or connectCallback is null."); 176 | return; 177 | } 178 | startScan(new SingleFilterScanCallback(new IScanCallback() { 179 | @Override 180 | public void onDeviceFound(BluetoothLeDevice bluetoothLeDevice) { 181 | 182 | } 183 | 184 | @Override 185 | public void onScanFinish(final BluetoothLeDeviceStore bluetoothLeDeviceStore) { 186 | if (bluetoothLeDeviceStore.getDeviceList().size() > 0) { 187 | new Handler(Looper.getMainLooper()).post(new Runnable() { 188 | @Override 189 | public void run() { 190 | connect(bluetoothLeDeviceStore.getDeviceList().get(0), connectCallback); 191 | } 192 | }); 193 | } else { 194 | connectCallback.onConnectFailure(new TimeoutException()); 195 | } 196 | } 197 | 198 | @Override 199 | public void onScanTimeout() { 200 | connectCallback.onConnectFailure(new TimeoutException()); 201 | } 202 | 203 | }).setDeviceName(name)); 204 | } 205 | 206 | /** 207 | * 获取连接池中的设备镜像,如果没有连接则返回空 208 | * 209 | * @param bluetoothLeDevice 210 | * @return 211 | */ 212 | public DeviceMirror getDeviceMirror(BluetoothLeDevice bluetoothLeDevice) { 213 | if (deviceMirrorPool != null) { 214 | return deviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 215 | } 216 | return null; 217 | } 218 | 219 | /** 220 | * 获取该设备连接状态 221 | * 222 | * @param bluetoothLeDevice 223 | * @return 224 | */ 225 | public ConnectState getConnectState(BluetoothLeDevice bluetoothLeDevice) { 226 | if (deviceMirrorPool != null) { 227 | return deviceMirrorPool.getConnectState(bluetoothLeDevice); 228 | } 229 | return ConnectState.CONNECT_DISCONNECT; 230 | } 231 | 232 | /** 233 | * 判断该设备是否已连接 234 | * 235 | * @param bluetoothLeDevice 236 | * @return 237 | */ 238 | public boolean isConnect(BluetoothLeDevice bluetoothLeDevice) { 239 | if (deviceMirrorPool != null) { 240 | return deviceMirrorPool.isContainDevice(bluetoothLeDevice); 241 | } 242 | return false; 243 | } 244 | 245 | /** 246 | * 断开某一个设备 247 | * 248 | * @param bluetoothLeDevice 249 | */ 250 | public void disconnect(BluetoothLeDevice bluetoothLeDevice) { 251 | if (deviceMirrorPool != null) { 252 | deviceMirrorPool.disconnect(bluetoothLeDevice); 253 | } 254 | } 255 | 256 | /** 257 | * 断开所有设备 258 | */ 259 | public void disconnect() { 260 | if (deviceMirrorPool != null) { 261 | deviceMirrorPool.disconnect(); 262 | } 263 | } 264 | 265 | /** 266 | * 清除资源,在退出应用时调用 267 | */ 268 | public void clear() { 269 | if (deviceMirrorPool != null) { 270 | deviceMirrorPool.clear(); 271 | } 272 | } 273 | 274 | /** 275 | * 获取Context 276 | * 277 | * @return 返回Context 278 | */ 279 | public Context getContext() { 280 | return context; 281 | } 282 | 283 | /** 284 | * 获取蓝牙管理 285 | * 286 | * @return 返回蓝牙管理 287 | */ 288 | public BluetoothManager getBluetoothManager() { 289 | return bluetoothManager; 290 | } 291 | 292 | /** 293 | * 获取蓝牙适配器 294 | * 295 | * @return 返回蓝牙适配器 296 | */ 297 | public BluetoothAdapter getBluetoothAdapter() { 298 | return bluetoothAdapter; 299 | } 300 | 301 | /** 302 | * 获取设备镜像池 303 | * 304 | * @return 305 | */ 306 | public DeviceMirrorPool getDeviceMirrorPool() { 307 | return deviceMirrorPool; 308 | } 309 | 310 | /** 311 | * 获取当前连接失败重试次数 312 | * 313 | * @return 314 | */ 315 | public int getConnectRetryCount() { 316 | if (lastDeviceMirror == null) { 317 | return 0; 318 | } 319 | return lastDeviceMirror.getConnectRetryCount(); 320 | } 321 | 322 | /** 323 | * 获取当前读取数据失败重试次数 324 | * 325 | * @return 326 | */ 327 | public int getReadDataRetryCount() { 328 | if (lastDeviceMirror == null) { 329 | return 0; 330 | } 331 | return lastDeviceMirror.getReadDataRetryCount(); 332 | } 333 | 334 | /** 335 | * 获取当前使能数据失败重试次数 336 | * 337 | * @return 338 | */ 339 | public int getReceiveDataRetryCount() { 340 | if (lastDeviceMirror == null) { 341 | return 0; 342 | } 343 | return lastDeviceMirror.getReceiveDataRetryCount(); 344 | } 345 | 346 | /** 347 | * 获取当前写入数据失败重试次数 348 | * 349 | * @return 350 | */ 351 | public int getWriteDataRetryCount() { 352 | if (lastDeviceMirror == null) { 353 | return 0; 354 | } 355 | return lastDeviceMirror.getWriteDataRetryCount(); 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/IBleCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback; 2 | 3 | import com.vise.baseble.core.BluetoothGattChannel; 4 | import com.vise.baseble.exception.BleException; 5 | import com.vise.baseble.model.BluetoothLeDevice; 6 | 7 | /** 8 | * @Description: 操作数据回调 9 | * @author: DAWI 10 | * @date: 2017/10/17 19:42 11 | */ 12 | public interface IBleCallback { 13 | void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice); 14 | 15 | void onFailure(BleException exception); 16 | } 17 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/IConnectCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback; 2 | 3 | import com.vise.baseble.core.DeviceMirror; 4 | import com.vise.baseble.exception.BleException; 5 | 6 | /** 7 | * @Description: 连接设备回调 8 | * @author: DAWI 9 | * @date: 17/8/1 23:00. 10 | */ 11 | public interface IConnectCallback { 12 | //连接成功 13 | void onConnectSuccess(DeviceMirror deviceMirror); 14 | 15 | //连接失败 16 | void onConnectFailure(BleException exception); 17 | 18 | //连接断开 19 | void onDisconnect(boolean isActive); 20 | } 21 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/IRssiCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback; 2 | 3 | import com.vise.baseble.exception.BleException; 4 | 5 | /** 6 | * @Description: 获取信号值回调 7 | * @author: DAWI 8 | * @date: 2017/10/19 15:19 9 | */ 10 | public interface IRssiCallback { 11 | void onSuccess(int rssi); 12 | 13 | void onFailure(BleException exception); 14 | } 15 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/IScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import com.vise.baseble.model.BluetoothLeDevice; 4 | import com.vise.baseble.model.BluetoothLeDeviceStore; 5 | 6 | /** 7 | * @Description: 扫描回调 8 | * @author: DAWI 9 | * @date: 17/9/10 18:14. 10 | */ 11 | public interface IScanCallback { 12 | //发现设备 13 | void onDeviceFound(BluetoothLeDevice bluetoothLeDevice); 14 | 15 | //扫描完成 16 | void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore); 17 | 18 | //扫描超时 19 | void onScanTimeout(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/IScanFilter.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import com.vise.baseble.model.BluetoothLeDevice; 4 | 5 | /** 6 | * @Description: 扫描过滤接口,根据需要实现过滤规则 7 | * @author: DAWI 8 | * @date: 17/9/10 18:19. 9 | */ 10 | public interface IScanFilter { 11 | BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice); 12 | } 13 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/ListFilterScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import com.vise.baseble.model.BluetoothLeDevice; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @Description: 指定设备集合进行过滤,一般用设备名称和Mac地址集合 9 | * @author: DAWI 10 | * @date: 17/9/12 22:50. 11 | */ 12 | public class ListFilterScanCallback extends ScanCallback { 13 | private List deviceNameList;//指定设备名称集合 14 | private List deviceMacList;//指定设备Mac地址集合 15 | 16 | public ListFilterScanCallback(IScanCallback scanCallback) { 17 | super(scanCallback); 18 | } 19 | 20 | public ListFilterScanCallback setDeviceNameList(List deviceNameList) { 21 | this.deviceNameList = deviceNameList; 22 | return this; 23 | } 24 | 25 | public ListFilterScanCallback setDeviceMacList(List deviceMacList) { 26 | this.deviceMacList = deviceMacList; 27 | return this; 28 | } 29 | 30 | @Override 31 | public BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice) { 32 | BluetoothLeDevice tempDevice = null; 33 | if (deviceNameList != null && deviceNameList.size() > 0) { 34 | for (String deviceName : deviceNameList) { 35 | if (bluetoothLeDevice != null && bluetoothLeDevice.getName() != null && deviceName != null 36 | && deviceName.equalsIgnoreCase(bluetoothLeDevice.getName().trim())) { 37 | tempDevice = bluetoothLeDevice; 38 | } 39 | } 40 | } else if (deviceMacList != null && deviceMacList.size() > 0) { 41 | for (String deviceMac : deviceMacList) { 42 | if (bluetoothLeDevice != null && bluetoothLeDevice.getAddress() != null && deviceMac != null 43 | && deviceMac.equalsIgnoreCase(bluetoothLeDevice.getAddress().trim())) { 44 | tempDevice = bluetoothLeDevice; 45 | } 46 | } 47 | } 48 | return tempDevice; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/RegularFilterScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.vise.baseble.model.BluetoothLeDevice; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * @Description: 根据正则过滤扫描设备,这里设置的是根据一定信号范围内指定正则设备名称的过滤 12 | * @author: DAWI 13 | * @date: 17/9/12 22:19. 14 | */ 15 | public class RegularFilterScanCallback extends ScanCallback { 16 | private Pattern pattern; 17 | private Matcher matcher; 18 | private String regularDeviceName;//正则表达式表示的设备名称 19 | private int deviceRssi;//设备的信号 20 | 21 | public RegularFilterScanCallback(IScanCallback scanCallback) { 22 | super(scanCallback); 23 | pattern = Pattern.compile("^[\\x00-\\xff]*$"); 24 | } 25 | 26 | public RegularFilterScanCallback setRegularDeviceName(String regularDeviceName) { 27 | this.regularDeviceName = regularDeviceName; 28 | if (!TextUtils.isEmpty(this.regularDeviceName)) { 29 | pattern = Pattern.compile(this.regularDeviceName); 30 | } 31 | return this; 32 | } 33 | 34 | public RegularFilterScanCallback setDeviceRssi(int deviceRssi) { 35 | this.deviceRssi = deviceRssi; 36 | return this; 37 | } 38 | 39 | @Override 40 | public BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice) { 41 | BluetoothLeDevice tempDevice = null; 42 | String tempName = bluetoothLeDevice.getName(); 43 | int tempRssi = bluetoothLeDevice.getRssi(); 44 | if (!TextUtils.isEmpty(tempName)) { 45 | matcher = pattern.matcher(tempName); 46 | if (this.deviceRssi < 0) { 47 | if (matcher.matches() && tempRssi >= this.deviceRssi) { 48 | tempDevice = bluetoothLeDevice; 49 | } 50 | } else { 51 | if (matcher.matches()) { 52 | tempDevice = bluetoothLeDevice; 53 | } 54 | } 55 | } 56 | return tempDevice; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/ScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import com.vise.baseble.ViseBle; 8 | import com.vise.baseble.common.BleConfig; 9 | import com.vise.baseble.model.BluetoothLeDevice; 10 | import com.vise.baseble.model.BluetoothLeDeviceStore; 11 | 12 | /** 13 | * @Description: 扫描设备回调 14 | * @author: DAWI 15 | * @date: 17/8/1 22:58. 16 | */ 17 | public class ScanCallback implements BluetoothAdapter.LeScanCallback, IScanFilter { 18 | protected Handler handler = new Handler(Looper.myLooper()); 19 | protected boolean isScan = true;//是否开始扫描 20 | protected boolean isScanning = false;//是否正在扫描 21 | protected BluetoothLeDeviceStore bluetoothLeDeviceStore;//用来存储扫描到的设备 22 | protected IScanCallback scanCallback;//扫描结果回调 23 | 24 | public ScanCallback(IScanCallback scanCallback) { 25 | this.scanCallback = scanCallback; 26 | if (scanCallback == null) { 27 | throw new NullPointerException("this scanCallback is null!"); 28 | } 29 | bluetoothLeDeviceStore = new BluetoothLeDeviceStore(); 30 | } 31 | 32 | public ScanCallback setScan(boolean scan) { 33 | isScan = scan; 34 | return this; 35 | } 36 | 37 | public boolean isScanning() { 38 | return isScanning; 39 | } 40 | 41 | public void scan() { 42 | if (isScan) { 43 | if (isScanning) { 44 | return; 45 | } 46 | bluetoothLeDeviceStore.clear(); 47 | if (BleConfig.getInstance().getScanTimeout() > 0) { 48 | handler.postDelayed(new Runnable() { 49 | @Override 50 | public void run() { 51 | isScanning = false; 52 | 53 | if (ViseBle.getInstance().getBluetoothAdapter() != null) { 54 | ViseBle.getInstance().getBluetoothAdapter().stopLeScan(ScanCallback.this); 55 | } 56 | 57 | if (bluetoothLeDeviceStore.getDeviceMap() != null 58 | && bluetoothLeDeviceStore.getDeviceMap().size() > 0) { 59 | scanCallback.onScanFinish(bluetoothLeDeviceStore); 60 | } else { 61 | scanCallback.onScanTimeout(); 62 | } 63 | } 64 | }, BleConfig.getInstance().getScanTimeout()); 65 | }else if (BleConfig.getInstance().getScanRepeatInterval() > 0){ 66 | //如果超时时间设置为一直扫描(即 <= 0),则判断是否设置重复扫描间隔 67 | handler.postDelayed(new Runnable() { 68 | @Override 69 | public void run() { 70 | isScanning = false; 71 | 72 | if (ViseBle.getInstance().getBluetoothAdapter() != null) { 73 | ViseBle.getInstance().getBluetoothAdapter().stopLeScan(ScanCallback.this); 74 | } 75 | 76 | if (bluetoothLeDeviceStore.getDeviceMap() != null 77 | && bluetoothLeDeviceStore.getDeviceMap().size() > 0) { 78 | scanCallback.onScanFinish(bluetoothLeDeviceStore); 79 | } else { 80 | scanCallback.onScanTimeout(); 81 | } 82 | isScanning = true; 83 | if (ViseBle.getInstance().getBluetoothAdapter() != null) { 84 | ViseBle.getInstance().getBluetoothAdapter().startLeScan(ScanCallback.this); 85 | } 86 | handler.postDelayed(this,BleConfig.getInstance().getScanRepeatInterval()); 87 | } 88 | }, BleConfig.getInstance().getScanRepeatInterval()); 89 | } 90 | isScanning = true; 91 | if (ViseBle.getInstance().getBluetoothAdapter() != null) { 92 | ViseBle.getInstance().getBluetoothAdapter().startLeScan(ScanCallback.this); 93 | } 94 | } else { 95 | isScanning = false; 96 | if (ViseBle.getInstance().getBluetoothAdapter() != null) { 97 | ViseBle.getInstance().getBluetoothAdapter().stopLeScan(ScanCallback.this); 98 | } 99 | } 100 | } 101 | 102 | public ScanCallback removeHandlerMsg() { 103 | handler.removeCallbacksAndMessages(null); 104 | bluetoothLeDeviceStore.clear(); 105 | return this; 106 | } 107 | 108 | @Override 109 | public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) { 110 | BluetoothLeDevice bluetoothLeDevice = new BluetoothLeDevice(bluetoothDevice, rssi, scanRecord, System.currentTimeMillis()); 111 | BluetoothLeDevice filterDevice = onFilter(bluetoothLeDevice); 112 | if (filterDevice != null) { 113 | bluetoothLeDeviceStore.addDevice(filterDevice); 114 | scanCallback.onDeviceFound(filterDevice); 115 | } 116 | } 117 | 118 | @Override 119 | public BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice) { 120 | return bluetoothLeDevice; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/SingleFilterScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import com.vise.baseble.ViseBle; 4 | import com.vise.baseble.model.BluetoothLeDevice; 5 | 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | 8 | /** 9 | * @Description: 设置扫描指定的单个设备,一般是设备名称和Mac地址 10 | * @author: DAWI 11 | * @date: 17/9/12 22:16. 12 | */ 13 | public class SingleFilterScanCallback extends ScanCallback { 14 | private AtomicBoolean hasFound = new AtomicBoolean(false); 15 | private String deviceName;//指定设备名称 16 | private String deviceMac;//指定设备Mac地址 17 | 18 | public SingleFilterScanCallback(IScanCallback scanCallback) { 19 | super(scanCallback); 20 | } 21 | 22 | public ScanCallback setDeviceName(String deviceName) { 23 | this.deviceName = deviceName; 24 | return this; 25 | } 26 | 27 | public ScanCallback setDeviceMac(String deviceMac) { 28 | this.deviceMac = deviceMac; 29 | return this; 30 | } 31 | 32 | @Override 33 | public BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice) { 34 | BluetoothLeDevice tempDevice = null; 35 | if (!hasFound.get()) { 36 | if (bluetoothLeDevice != null && bluetoothLeDevice.getAddress() != null && deviceMac != null 37 | && deviceMac.equalsIgnoreCase(bluetoothLeDevice.getAddress().trim())) { 38 | hasFound.set(true); 39 | isScanning = false; 40 | removeHandlerMsg(); 41 | ViseBle.getInstance().stopScan(SingleFilterScanCallback.this); 42 | tempDevice = bluetoothLeDevice; 43 | bluetoothLeDeviceStore.addDevice(bluetoothLeDevice); 44 | scanCallback.onScanFinish(bluetoothLeDeviceStore); 45 | } else if (bluetoothLeDevice != null && bluetoothLeDevice.getName() != null && deviceName != null 46 | && deviceName.equalsIgnoreCase(bluetoothLeDevice.getName().trim())) { 47 | hasFound.set(true); 48 | isScanning = false; 49 | removeHandlerMsg(); 50 | ViseBle.getInstance().stopScan(SingleFilterScanCallback.this); 51 | tempDevice = bluetoothLeDevice; 52 | bluetoothLeDeviceStore.addDevice(bluetoothLeDevice); 53 | scanCallback.onScanFinish(bluetoothLeDeviceStore); 54 | } 55 | } 56 | return tempDevice; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/callback/scan/UuidFilterScanCallback.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.callback.scan; 2 | 3 | import android.os.ParcelUuid; 4 | 5 | import com.vise.baseble.model.BluetoothLeDevice; 6 | 7 | import java.util.UUID; 8 | 9 | /** 10 | * @Description: 根据指定uuid过滤设备 11 | * @author: DAWI 12 | * @date: 17/9/12 23:20. 13 | */ 14 | public class UuidFilterScanCallback extends ScanCallback { 15 | private UUID uuid;//设备uuid 16 | 17 | public UuidFilterScanCallback(IScanCallback scanCallback) { 18 | super(scanCallback); 19 | } 20 | 21 | public UuidFilterScanCallback setUuid(String uuid) { 22 | this.uuid = UUID.fromString(uuid); 23 | return this; 24 | } 25 | 26 | public UuidFilterScanCallback setUuid(UUID uuid) { 27 | this.uuid = uuid; 28 | return this; 29 | } 30 | 31 | @Override 32 | public BluetoothLeDevice onFilter(BluetoothLeDevice bluetoothLeDevice) { 33 | BluetoothLeDevice tempDevice = null; 34 | if (bluetoothLeDevice != null && bluetoothLeDevice.getDevice() != null 35 | && bluetoothLeDevice.getDevice().getUuids() != null 36 | && bluetoothLeDevice.getDevice().getUuids().length > 0) { 37 | for (ParcelUuid parcelUuid : bluetoothLeDevice.getDevice().getUuids()) { 38 | if (uuid != null && uuid == parcelUuid.getUuid()) { 39 | tempDevice = bluetoothLeDevice; 40 | } 41 | } 42 | } 43 | return tempDevice; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/BleConfig.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | import static com.vise.baseble.common.BleConstant.DEFAULT_CONN_TIME; 4 | import static com.vise.baseble.common.BleConstant.DEFAULT_MAX_CONNECT_COUNT; 5 | import static com.vise.baseble.common.BleConstant.DEFAULT_OPERATE_TIME; 6 | import static com.vise.baseble.common.BleConstant.DEFAULT_RETRY_COUNT; 7 | import static com.vise.baseble.common.BleConstant.DEFAULT_RETRY_INTERVAL; 8 | import static com.vise.baseble.common.BleConstant.DEFAULT_SCAN_REPEAT_INTERVAL; 9 | import static com.vise.baseble.common.BleConstant.DEFAULT_SCAN_TIME; 10 | 11 | /** 12 | * @Description: 蓝牙通信相关配置 13 | * @author: DAWI 14 | * @date: 2017/10/16 11:46 15 | */ 16 | public class BleConfig { 17 | private static BleConfig instance; 18 | 19 | private int scanTimeout = DEFAULT_SCAN_TIME;//扫描超时时间(毫秒) 20 | private int connectTimeout = DEFAULT_CONN_TIME;//连接超时时间(毫秒) 21 | private int operateTimeout = DEFAULT_OPERATE_TIME;//数据操作超时时间(毫秒) 22 | private int connectRetryCount = DEFAULT_RETRY_COUNT;//连接重试次数 23 | private int connectRetryInterval = DEFAULT_RETRY_INTERVAL;//连接重试间隔(毫秒) 24 | private int operateRetryCount = DEFAULT_RETRY_COUNT;//数据操作重试次数 25 | private int operateRetryInterval = DEFAULT_RETRY_INTERVAL;//数据操作重试间隔时间(毫秒) 26 | private int maxConnectCount = DEFAULT_MAX_CONNECT_COUNT;//最大连接数量 27 | 28 | //yankee 29 | private int scanRepeatInterval = DEFAULT_SCAN_REPEAT_INTERVAL;//每隔X时间重复扫描 (毫秒) 30 | 31 | private BleConfig() { 32 | } 33 | 34 | public static BleConfig getInstance() { 35 | if (instance == null) { 36 | synchronized (BleConfig.class) { 37 | if (instance == null) { 38 | instance = new BleConfig(); 39 | } 40 | } 41 | } 42 | return instance; 43 | } 44 | 45 | /** 46 | * 获取发送数据超时时间 47 | * 48 | * @return 返回发送数据超时时间 49 | */ 50 | public int getOperateTimeout() { 51 | return operateTimeout; 52 | } 53 | 54 | /** 55 | * 设置发送数据超时时间 56 | * 57 | * @param operateTimeout 发送数据超时时间 58 | * @return 返回ViseBle 59 | */ 60 | public BleConfig setOperateTimeout(int operateTimeout) { 61 | this.operateTimeout = operateTimeout; 62 | return this; 63 | } 64 | 65 | /** 66 | * 获取连接超时时间 67 | * 68 | * @return 返回连接超时时间 69 | */ 70 | public int getConnectTimeout() { 71 | return connectTimeout; 72 | } 73 | 74 | /** 75 | * 设置连接超时时间 76 | * 77 | * @param connectTimeout 连接超时时间 78 | * @return 返回ViseBle 79 | */ 80 | public BleConfig setConnectTimeout(int connectTimeout) { 81 | this.connectTimeout = connectTimeout; 82 | return this; 83 | } 84 | 85 | /** 86 | * 获取扫描超时时间 87 | * 88 | * @return 返回扫描超时时间 89 | */ 90 | public int getScanTimeout() { 91 | return scanTimeout; 92 | } 93 | 94 | /** 95 | * 设置扫描超时时间 96 | * 97 | * @param scanTimeout 扫描超时时间 98 | * @return 返回ViseBle 99 | */ 100 | public BleConfig setScanTimeout(int scanTimeout) { 101 | this.scanTimeout = scanTimeout; 102 | return this; 103 | } 104 | 105 | /** 106 | * 获取连接重试次数 107 | * 108 | * @return 109 | */ 110 | public int getConnectRetryCount() { 111 | return connectRetryCount; 112 | } 113 | 114 | /** 115 | * 设置连接重试次数 116 | * 117 | * @param connectRetryCount 118 | * @return 119 | */ 120 | public BleConfig setConnectRetryCount(int connectRetryCount) { 121 | this.connectRetryCount = connectRetryCount; 122 | return this; 123 | } 124 | 125 | /** 126 | * 获取连接重试间隔时间 127 | * 128 | * @return 129 | */ 130 | public int getConnectRetryInterval() { 131 | return connectRetryInterval; 132 | } 133 | 134 | /** 135 | * 设置连接重试间隔时间 136 | * 137 | * @param connectRetryInterval 138 | * @return 139 | */ 140 | public BleConfig setConnectRetryInterval(int connectRetryInterval) { 141 | this.connectRetryInterval = connectRetryInterval; 142 | return this; 143 | } 144 | 145 | /** 146 | * 获取最大连接数量 147 | * 148 | * @return 149 | */ 150 | public int getMaxConnectCount() { 151 | return maxConnectCount; 152 | } 153 | 154 | /** 155 | * 设置最大连接数量 156 | * 157 | * @param maxConnectCount 158 | * @return 159 | */ 160 | public BleConfig setMaxConnectCount(int maxConnectCount) { 161 | this.maxConnectCount = maxConnectCount; 162 | return this; 163 | } 164 | 165 | /** 166 | * 获取操作数据重试次数 167 | * 168 | * @return 169 | */ 170 | public int getOperateRetryCount() { 171 | return operateRetryCount; 172 | } 173 | 174 | /** 175 | * 设置操作数据重试次数 176 | * 177 | * @param operateRetryCount 178 | * @return 179 | */ 180 | public BleConfig setOperateRetryCount(int operateRetryCount) { 181 | this.operateRetryCount = operateRetryCount; 182 | return this; 183 | } 184 | 185 | /** 186 | * 获取操作数据重试间隔时间 187 | * 188 | * @return 189 | */ 190 | public int getOperateRetryInterval() { 191 | return operateRetryInterval; 192 | } 193 | 194 | /** 195 | * 设置操作数据重试间隔时间 196 | * 197 | * @param operateRetryInterval 198 | * @return 199 | */ 200 | public BleConfig setOperateRetryInterval(int operateRetryInterval) { 201 | this.operateRetryInterval = operateRetryInterval; 202 | return this; 203 | } 204 | 205 | /** 206 | * 获取扫描间隔时间 207 | * @return 208 | */ 209 | public int getScanRepeatInterval() { 210 | return scanRepeatInterval; 211 | } 212 | 213 | /** 214 | * 设置每隔多少时间重复扫描一次 215 | * 设置扫描间隔时间 (毫秒) 216 | * @param scanRepeatInterval 217 | * @return 218 | */ 219 | public BleConfig setScanRepeatInterval(int scanRepeatInterval) { 220 | this.scanRepeatInterval = scanRepeatInterval; 221 | return this; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/BleConstant.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | /** 4 | * @Description: BLE常量 5 | * @author: DAWI 6 | * @date: 16/8/20 20:31. 7 | */ 8 | public class BleConstant { 9 | public static final String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; 10 | 11 | public static final int TIME_FOREVER = -1; 12 | 13 | public static final int DEFAULT_SCAN_TIME = 20000; 14 | public static final int DEFAULT_CONN_TIME = 10000; 15 | public static final int DEFAULT_OPERATE_TIME = 5000; 16 | 17 | public static final int DEFAULT_RETRY_INTERVAL = 1000; 18 | public static final int DEFAULT_RETRY_COUNT = 3; 19 | 20 | public static final int DEFAULT_MAX_CONNECT_COUNT = 5; 21 | 22 | public static final int MSG_CONNECT_TIMEOUT = 0x01; 23 | public static final int MSG_WRITE_DATA_TIMEOUT = 0x02; 24 | public static final int MSG_READ_DATA_TIMEOUT = 0x03; 25 | public static final int MSG_RECEIVE_DATA_TIMEOUT = 0x04; 26 | public static final int MSG_CONNECT_RETRY = 0x05; 27 | public static final int MSG_WRITE_DATA_RETRY = 0x06; 28 | public static final int MSG_READ_DATA_RETRY = 0x07; 29 | public static final int MSG_RECEIVE_DATA_RETRY = 0x08; 30 | 31 | //yankee 32 | public static final int DEFAULT_SCAN_REPEAT_INTERVAL = -1; 33 | } 34 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/BleExceptionCode.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | /** 4 | * @Description: BLE异常Code 5 | * @author: DAWI 6 | * @date: 16/8/14 10:43. 7 | */ 8 | public enum BleExceptionCode { 9 | TIMEOUT, //超时 10 | CONNECT_ERR, //连接异常 11 | GATT_ERR, //GATT异常 12 | INITIATED_ERR, //初始化异常 13 | OTHER_ERR //其他异常 14 | } 15 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/BluetoothServiceType.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | import android.bluetooth.BluetoothClass; 4 | 5 | /** 6 | * @Description: BLE服务类型 7 | * @author: DAWI 8 | * @date: 16/8/7 22:07. 9 | */ 10 | public enum BluetoothServiceType { 11 | AUDIO(BluetoothClass.Service.AUDIO), //音频服务 12 | CAPTURE(BluetoothClass.Service.CAPTURE), //捕捉服务 13 | INFORMATION(BluetoothClass.Service.INFORMATION), //信息服务 14 | LIMITED_DISCOVERABILITY(BluetoothClass.Service.LIMITED_DISCOVERABILITY), //有限发现服务 15 | NETWORKING(BluetoothClass.Service.NETWORKING), //网络服务 16 | OBJECT_TRANSFER(BluetoothClass.Service.OBJECT_TRANSFER), //对象传输服务 17 | POSITIONING(BluetoothClass.Service.POSITIONING), //定位服务 18 | RENDER(BluetoothClass.Service.RENDER), //给予服务 19 | TELEPHONY(BluetoothClass.Service.TELEPHONY); //电话服务 20 | 21 | private int code; 22 | 23 | BluetoothServiceType(int code) { 24 | this.code = code; 25 | } 26 | 27 | public int getCode() { 28 | return this.code; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/ConnectState.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | /** 4 | * @Description: 状态描述 5 | * @author: DAWI 6 | * @date: 16/8/20 17:06. 7 | */ 8 | public enum ConnectState { 9 | CONNECT_INIT(-1), //连接初始化 10 | CONNECT_PROCESS(0x00), //连接中 11 | CONNECT_SUCCESS(0x01), //连接成功 12 | CONNECT_FAILURE(0x02), //连接失败 13 | CONNECT_TIMEOUT(0x03), //连接超时 14 | CONNECT_DISCONNECT(0x04); //连接断开 15 | 16 | private int code; 17 | 18 | ConnectState(int code) { 19 | this.code = code; 20 | } 21 | 22 | public int getCode() { 23 | return code; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/common/PropertyType.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.common; 2 | 3 | /** 4 | * @Description: 属性类型 5 | * @author: DAWI 6 | * @date: 2017/10/17 20:27 7 | */ 8 | public enum PropertyType { 9 | PROPERTY_READ(0x01), 10 | PROPERTY_WRITE(0x02), 11 | PROPERTY_NOTIFY(0x04), 12 | PROPERTY_INDICATE(0x08); 13 | 14 | private int propertyValue; 15 | 16 | PropertyType(int propertyValue) { 17 | this.propertyValue = propertyValue; 18 | } 19 | 20 | public int getPropertyValue() { 21 | return propertyValue; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/core/BluetoothGattChannel.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.core; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | import android.bluetooth.BluetoothGattCharacteristic; 5 | import android.bluetooth.BluetoothGattDescriptor; 6 | import android.bluetooth.BluetoothGattService; 7 | 8 | import com.vise.baseble.common.PropertyType; 9 | 10 | import java.util.UUID; 11 | 12 | /** 13 | * @Description: BluetoothGatt 相关信息 14 | * @author: DAWI 15 | * @date: 2017/10/17 16:25 16 | */ 17 | public class BluetoothGattChannel { 18 | 19 | private BluetoothGatt bluetoothGatt; 20 | private BluetoothGattService service; 21 | private BluetoothGattCharacteristic characteristic; 22 | private BluetoothGattDescriptor descriptor; 23 | private String gattInfoKey; 24 | private PropertyType propertyType; 25 | private UUID serviceUUID; 26 | private UUID characteristicUUID; 27 | private UUID descriptorUUID; 28 | 29 | private BluetoothGattChannel(BluetoothGatt bluetoothGatt, PropertyType propertyType, UUID serviceUUID, UUID characteristicUUID, UUID descriptorUUID) { 30 | this.bluetoothGatt = bluetoothGatt; 31 | this.propertyType = propertyType; 32 | this.serviceUUID = serviceUUID; 33 | this.characteristicUUID = characteristicUUID; 34 | this.descriptorUUID = descriptorUUID; 35 | StringBuilder stringBuilder = new StringBuilder(); 36 | if (propertyType != null) { 37 | stringBuilder.append(propertyType.getPropertyValue()); 38 | } 39 | if (serviceUUID != null && bluetoothGatt != null) { 40 | service = bluetoothGatt.getService(serviceUUID); 41 | stringBuilder.append(serviceUUID.toString()); 42 | } 43 | if (service != null && characteristicUUID != null) { 44 | characteristic = service.getCharacteristic(characteristicUUID); 45 | stringBuilder.append(characteristicUUID.toString()); 46 | } 47 | if (characteristic != null && descriptorUUID != null) { 48 | descriptor = characteristic.getDescriptor(descriptorUUID); 49 | stringBuilder.append(descriptorUUID.toString()); 50 | } 51 | gattInfoKey = stringBuilder.toString(); 52 | } 53 | 54 | public BluetoothGatt getBluetoothGatt() { 55 | return bluetoothGatt; 56 | } 57 | 58 | public BluetoothGattCharacteristic getCharacteristic() { 59 | return characteristic; 60 | } 61 | 62 | public BluetoothGattDescriptor getDescriptor() { 63 | return descriptor; 64 | } 65 | 66 | public BluetoothGattService getService() { 67 | return service; 68 | } 69 | 70 | public BluetoothGattChannel setDescriptor(BluetoothGattDescriptor descriptor) { 71 | this.descriptor = descriptor; 72 | return this; 73 | } 74 | 75 | public String getGattInfoKey() { 76 | return gattInfoKey; 77 | } 78 | 79 | public UUID getCharacteristicUUID() { 80 | return characteristicUUID; 81 | } 82 | 83 | public UUID getDescriptorUUID() { 84 | return descriptorUUID; 85 | } 86 | 87 | public PropertyType getPropertyType() { 88 | return propertyType; 89 | } 90 | 91 | public UUID getServiceUUID() { 92 | return serviceUUID; 93 | } 94 | 95 | public static class Builder { 96 | private BluetoothGatt bluetoothGatt; 97 | private PropertyType propertyType; 98 | private UUID serviceUUID; 99 | private UUID characteristicUUID; 100 | private UUID descriptorUUID; 101 | 102 | public Builder() { 103 | } 104 | 105 | public Builder setBluetoothGatt(BluetoothGatt bluetoothGatt) { 106 | this.bluetoothGatt = bluetoothGatt; 107 | return this; 108 | } 109 | 110 | public Builder setCharacteristicUUID(UUID characteristicUUID) { 111 | this.characteristicUUID = characteristicUUID; 112 | return this; 113 | } 114 | 115 | public Builder setDescriptorUUID(UUID descriptorUUID) { 116 | this.descriptorUUID = descriptorUUID; 117 | return this; 118 | } 119 | 120 | public Builder setPropertyType(PropertyType propertyType) { 121 | this.propertyType = propertyType; 122 | return this; 123 | } 124 | 125 | public Builder setServiceUUID(UUID serviceUUID) { 126 | this.serviceUUID = serviceUUID; 127 | return this; 128 | } 129 | 130 | public BluetoothGattChannel builder() { 131 | return new BluetoothGattChannel(bluetoothGatt, propertyType, serviceUUID, characteristicUUID, descriptorUUID); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/core/DeviceMirrorPool.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.core; 2 | 3 | import com.vise.baseble.common.BleConfig; 4 | import com.vise.baseble.common.ConnectState; 5 | import com.vise.baseble.model.BluetoothLeDevice; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * @Description: 设备镜像池,用来管理多个设备连接后的操作 15 | * @author: DAWI 16 | * @date: 17/8/1 23:18. 17 | */ 18 | public class DeviceMirrorPool { 19 | private final LruHashMap DEVICE_MIRROR_MAP; 20 | 21 | public DeviceMirrorPool() { 22 | DEVICE_MIRROR_MAP = new LruHashMap<>(BleConfig.getInstance().getMaxConnectCount()); 23 | } 24 | 25 | public DeviceMirrorPool(int deviceMirrorSize) { 26 | DEVICE_MIRROR_MAP = new LruHashMap<>(deviceMirrorSize); 27 | } 28 | 29 | /** 30 | * 添加设备镜像 31 | * 32 | * @param bluetoothLeDevice 33 | */ 34 | public synchronized void addDeviceMirror(BluetoothLeDevice bluetoothLeDevice) { 35 | if (bluetoothLeDevice == null) { 36 | return; 37 | } 38 | String key = bluetoothLeDevice.getAddress() + bluetoothLeDevice.getName(); 39 | if (!DEVICE_MIRROR_MAP.containsKey(key)) { 40 | DEVICE_MIRROR_MAP.put(key, new DeviceMirror(bluetoothLeDevice)); 41 | } 42 | } 43 | 44 | /** 45 | * 添加设备镜像 46 | * 47 | * @param deviceMirror 48 | */ 49 | public synchronized void addDeviceMirror(DeviceMirror deviceMirror) { 50 | if (deviceMirror == null) { 51 | return; 52 | } 53 | if (!DEVICE_MIRROR_MAP.containsKey(deviceMirror.getUniqueSymbol())) { 54 | DEVICE_MIRROR_MAP.put(deviceMirror.getUniqueSymbol(), deviceMirror); 55 | } 56 | } 57 | 58 | /** 59 | * 删除设备镜像 60 | * 61 | * @param bluetoothLeDevice 62 | */ 63 | public synchronized void removeDeviceMirror(BluetoothLeDevice bluetoothLeDevice) { 64 | if (bluetoothLeDevice == null) { 65 | return; 66 | } 67 | String key = bluetoothLeDevice.getAddress() + bluetoothLeDevice.getName(); 68 | if (DEVICE_MIRROR_MAP.containsKey(key)) { 69 | DEVICE_MIRROR_MAP.remove(key); 70 | } 71 | } 72 | 73 | /** 74 | * 删除设备镜像 75 | * 76 | * @param deviceMirror 77 | */ 78 | public synchronized void removeDeviceMirror(DeviceMirror deviceMirror) { 79 | if (deviceMirror == null) { 80 | return; 81 | } 82 | if (DEVICE_MIRROR_MAP.containsKey(deviceMirror.getUniqueSymbol())) { 83 | deviceMirror.clear(); 84 | DEVICE_MIRROR_MAP.remove(deviceMirror.getUniqueSymbol()); 85 | } 86 | } 87 | 88 | /** 89 | * 判断是否包含设备镜像 90 | * 91 | * @param deviceMirror 92 | * @return 93 | */ 94 | public synchronized boolean isContainDevice(DeviceMirror deviceMirror) { 95 | if (deviceMirror == null || !DEVICE_MIRROR_MAP.containsKey(deviceMirror.getUniqueSymbol())) { 96 | return false; 97 | } 98 | return true; 99 | } 100 | 101 | /** 102 | * 判断是否包含设备镜像 103 | * 104 | * @param bluetoothLeDevice 105 | * @return 106 | */ 107 | public synchronized boolean isContainDevice(BluetoothLeDevice bluetoothLeDevice) { 108 | if (bluetoothLeDevice == null || !DEVICE_MIRROR_MAP.containsKey(bluetoothLeDevice.getAddress() + 109 | bluetoothLeDevice.getName())) { 110 | return false; 111 | } 112 | return true; 113 | } 114 | 115 | /** 116 | * 获取连接池中该设备镜像的连接状态,如果没有连接则返回CONNECT_DISCONNECT。 117 | * 118 | * @param bluetoothLeDevice 119 | * @return 120 | */ 121 | public synchronized ConnectState getConnectState(BluetoothLeDevice bluetoothLeDevice) { 122 | DeviceMirror deviceMirror = getDeviceMirror(bluetoothLeDevice); 123 | if (deviceMirror != null) { 124 | return deviceMirror.getConnectState(); 125 | } 126 | return ConnectState.CONNECT_DISCONNECT; 127 | } 128 | 129 | /** 130 | * 获取连接池中的设备镜像,如果没有连接则返回空 131 | * 132 | * @param bluetoothLeDevice 133 | * @return 134 | */ 135 | public synchronized DeviceMirror getDeviceMirror(BluetoothLeDevice bluetoothLeDevice) { 136 | if (bluetoothLeDevice != null) { 137 | String key = bluetoothLeDevice.getAddress() + bluetoothLeDevice.getName(); 138 | if (DEVICE_MIRROR_MAP.containsKey(key)) { 139 | return DEVICE_MIRROR_MAP.get(key); 140 | } 141 | } 142 | return null; 143 | } 144 | 145 | /** 146 | * 断开连接池中某一个设备 147 | * 148 | * @param bluetoothLeDevice 149 | */ 150 | public synchronized void disconnect(BluetoothLeDevice bluetoothLeDevice) { 151 | if (isContainDevice(bluetoothLeDevice)) { 152 | getDeviceMirror(bluetoothLeDevice).disconnect(); 153 | } 154 | } 155 | 156 | /** 157 | * 断开连接池中所有设备 158 | */ 159 | public synchronized void disconnect() { 160 | for (Map.Entry stringDeviceMirrorEntry : DEVICE_MIRROR_MAP.entrySet()) { 161 | stringDeviceMirrorEntry.getValue().disconnect(); 162 | } 163 | DEVICE_MIRROR_MAP.clear(); 164 | } 165 | 166 | /** 167 | * 清除连接池 168 | */ 169 | public synchronized void clear() { 170 | for (Map.Entry stringDeviceMirrorEntry : DEVICE_MIRROR_MAP.entrySet()) { 171 | stringDeviceMirrorEntry.getValue().clear(); 172 | } 173 | DEVICE_MIRROR_MAP.clear(); 174 | } 175 | 176 | /** 177 | * 获取连接池设备镜像Map集合 178 | * 179 | * @return 180 | */ 181 | public Map getDeviceMirrorMap() { 182 | return DEVICE_MIRROR_MAP; 183 | } 184 | 185 | /** 186 | * 获取连接池设备镜像List集合 187 | * 188 | * @return 189 | */ 190 | public synchronized List getDeviceMirrorList() { 191 | final List deviceMirrors = new ArrayList<>(DEVICE_MIRROR_MAP.values()); 192 | Collections.sort(deviceMirrors, new Comparator() { 193 | @Override 194 | public int compare(final DeviceMirror lhs, final DeviceMirror rhs) { 195 | return lhs.getUniqueSymbol().compareToIgnoreCase(rhs.getUniqueSymbol()); 196 | } 197 | }); 198 | return deviceMirrors; 199 | } 200 | 201 | /** 202 | * 获取连接池设备详细信息List集合 203 | * 204 | * @return 205 | */ 206 | public synchronized List getDeviceList() { 207 | final List deviceList = new ArrayList<>(); 208 | for (DeviceMirror deviceMirror : getDeviceMirrorList()) { 209 | if (deviceMirror != null) { 210 | deviceList.add(deviceMirror.getBluetoothLeDevice()); 211 | } 212 | } 213 | return deviceList; 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/core/LruHashMap.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.core; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | /** 6 | * @Description: Lru算法实现的HashMap 7 | * @author: DAWI 8 | * @date: 2017/9/28 19:57 9 | */ 10 | public class LruHashMap extends LinkedHashMap { 11 | private final int MAX_SAVE_SIZE; 12 | 13 | public LruHashMap(int saveSize) { 14 | super((int) Math.ceil(saveSize / 0.75) + 1, 0.75f, true); 15 | MAX_SAVE_SIZE = saveSize; 16 | } 17 | 18 | @Override 19 | protected boolean removeEldestEntry(Entry eldest) { 20 | if (size() > MAX_SAVE_SIZE && eldest.getValue() instanceof DeviceMirror) { 21 | ((DeviceMirror) eldest.getValue()).disconnect(); 22 | } 23 | return size() > MAX_SAVE_SIZE; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | StringBuilder sb = new StringBuilder(); 29 | for (Entry entry : entrySet()) { 30 | sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); 31 | } 32 | return sb.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/BleException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import com.vise.baseble.common.BleExceptionCode; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @Description: BLE异常基类 9 | * @author: DAWI 10 | * @date: 16/8/14 10:28. 11 | */ 12 | public class BleException implements Serializable { 13 | private BleExceptionCode code; 14 | private String description; 15 | 16 | public BleException(BleExceptionCode code, String description) { 17 | this.code = code; 18 | this.description = description; 19 | } 20 | 21 | public BleExceptionCode getCode() { 22 | return code; 23 | } 24 | 25 | public BleException setCode(BleExceptionCode code) { 26 | this.code = code; 27 | return this; 28 | } 29 | 30 | public String getDescription() { 31 | return description; 32 | } 33 | 34 | public BleException setDescription(String description) { 35 | this.description = description; 36 | return this; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "BleException{" + 42 | "code=" + code + 43 | ", description='" + description + '\'' + 44 | '}'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/ConnectException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import android.bluetooth.BluetoothGatt; 4 | 5 | import com.vise.baseble.common.BleExceptionCode; 6 | 7 | /** 8 | * @Description: 连接异常 9 | * @author: DAWI 10 | * @date: 16/8/14 10:29. 11 | */ 12 | public class ConnectException extends BleException { 13 | private BluetoothGatt bluetoothGatt; 14 | private int gattStatus; 15 | 16 | public ConnectException(BluetoothGatt bluetoothGatt, int gattStatus) { 17 | super(BleExceptionCode.CONNECT_ERR, "Connect Exception Occurred! "); 18 | this.bluetoothGatt = bluetoothGatt; 19 | this.gattStatus = gattStatus; 20 | } 21 | 22 | public int getGattStatus() { 23 | return gattStatus; 24 | } 25 | 26 | public ConnectException setGattStatus(int gattStatus) { 27 | this.gattStatus = gattStatus; 28 | return this; 29 | } 30 | 31 | public BluetoothGatt getBluetoothGatt() { 32 | return bluetoothGatt; 33 | } 34 | 35 | public ConnectException setBluetoothGatt(BluetoothGatt bluetoothGatt) { 36 | this.bluetoothGatt = bluetoothGatt; 37 | return this; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "ConnectException{" + 43 | "gattStatus=" + gattStatus + 44 | ", bluetoothGatt=" + bluetoothGatt + 45 | "} " + super.toString(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/GattException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import com.vise.baseble.common.BleExceptionCode; 4 | 5 | /** 6 | * @Description: Gatt异常 7 | * @author: DAWI 8 | * @date: 16/8/14 10:30. 9 | */ 10 | public class GattException extends BleException { 11 | private int gattStatus; 12 | 13 | public GattException(int gattStatus) { 14 | super(BleExceptionCode.GATT_ERR, "Gatt Exception Occurred! "); 15 | this.gattStatus = gattStatus; 16 | } 17 | 18 | public int getGattStatus() { 19 | return gattStatus; 20 | } 21 | 22 | public GattException setGattStatus(int gattStatus) { 23 | this.gattStatus = gattStatus; 24 | return this; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "GattException{" + 30 | "gattStatus=" + gattStatus + 31 | '}' + super.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/InitiatedException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import com.vise.baseble.common.BleExceptionCode; 4 | 5 | /** 6 | * @Description: 初始化异常 7 | * @author: DAWI 8 | * @date: 16/8/14 10:30. 9 | */ 10 | public class InitiatedException extends BleException { 11 | public InitiatedException() { 12 | super(BleExceptionCode.INITIATED_ERR, "Initiated Exception Occurred! "); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/OtherException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import com.vise.baseble.common.BleExceptionCode; 4 | 5 | /** 6 | * @Description: 其他异常 7 | * @author: DAWI 8 | * @date: 16/8/14 10:32. 9 | */ 10 | public class OtherException extends BleException { 11 | public OtherException(String description) { 12 | super(BleExceptionCode.OTHER_ERR, description); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/TimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception; 2 | 3 | import com.vise.baseble.common.BleExceptionCode; 4 | 5 | /** 6 | * @Description: 超时异常 7 | * @author: DAWI 8 | * @date: 16/8/14 10:29. 9 | */ 10 | public class TimeoutException extends BleException { 11 | public TimeoutException() { 12 | super(BleExceptionCode.TIMEOUT, "Timeout Exception Occurred! "); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/handler/BleExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception.handler; 2 | 3 | import com.vise.baseble.exception.BleException; 4 | import com.vise.baseble.exception.ConnectException; 5 | import com.vise.baseble.exception.GattException; 6 | import com.vise.baseble.exception.InitiatedException; 7 | import com.vise.baseble.exception.OtherException; 8 | import com.vise.baseble.exception.TimeoutException; 9 | 10 | /** 11 | * @Description: 异常处理 12 | * @author: DAWI 13 | * @date: 16/8/14 10:35. 14 | */ 15 | public abstract class BleExceptionHandler { 16 | public BleExceptionHandler handleException(BleException exception) { 17 | if (exception != null) { 18 | if (exception instanceof ConnectException) { 19 | onConnectException((ConnectException) exception); 20 | } else if (exception instanceof GattException) { 21 | onGattException((GattException) exception); 22 | } else if (exception instanceof TimeoutException) { 23 | onTimeoutException((TimeoutException) exception); 24 | } else if (exception instanceof InitiatedException) { 25 | onInitiatedException((InitiatedException) exception); 26 | } else { 27 | onOtherException((OtherException) exception); 28 | } 29 | } 30 | return this; 31 | } 32 | 33 | /** 34 | * connect failed 35 | */ 36 | protected abstract void onConnectException(ConnectException e); 37 | 38 | /** 39 | * gatt error status 40 | */ 41 | protected abstract void onGattException(GattException e); 42 | 43 | /** 44 | * operation timeout 45 | */ 46 | protected abstract void onTimeoutException(TimeoutException e); 47 | 48 | /** 49 | * operation inititiated error 50 | */ 51 | protected abstract void onInitiatedException(InitiatedException e); 52 | 53 | /** 54 | * other exceptions 55 | */ 56 | protected abstract void onOtherException(OtherException e); 57 | } 58 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/exception/handler/DefaultBleExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.exception.handler; 2 | 3 | import com.vise.baseble.exception.ConnectException; 4 | import com.vise.baseble.exception.GattException; 5 | import com.vise.baseble.exception.InitiatedException; 6 | import com.vise.baseble.exception.OtherException; 7 | import com.vise.baseble.exception.TimeoutException; 8 | import com.vise.log.ViseLog; 9 | 10 | /** 11 | * @Description: 异常默认处理 12 | * @author: DAWI 13 | * @date: 16/8/14 10:35. 14 | */ 15 | public class DefaultBleExceptionHandler extends BleExceptionHandler { 16 | @Override 17 | protected void onConnectException(ConnectException e) { 18 | ViseLog.e(e.getDescription()); 19 | } 20 | 21 | @Override 22 | protected void onGattException(GattException e) { 23 | ViseLog.e(e.getDescription()); 24 | } 25 | 26 | @Override 27 | protected void onTimeoutException(TimeoutException e) { 28 | ViseLog.e(e.getDescription()); 29 | } 30 | 31 | @Override 32 | protected void onInitiatedException(InitiatedException e) { 33 | ViseLog.e(e.getDescription()); 34 | } 35 | 36 | @Override 37 | protected void onOtherException(OtherException e) { 38 | ViseLog.e(e.getDescription()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/model/BluetoothLeDeviceStore.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Description: 设备信息集合 12 | * @author: DAWI 13 | * @date: 16/8/21 16:48. 14 | */ 15 | public class BluetoothLeDeviceStore { 16 | 17 | private final Map mDeviceMap; 18 | 19 | public BluetoothLeDeviceStore() { 20 | mDeviceMap = new HashMap<>(); 21 | } 22 | 23 | public void addDevice(BluetoothLeDevice device) { 24 | if (device == null) { 25 | return; 26 | } 27 | if (mDeviceMap.containsKey(device.getAddress())) { 28 | mDeviceMap.get(device.getAddress()).updateRssiReading(device.getTimestamp(), device.getRssi()); 29 | } else { 30 | mDeviceMap.put(device.getAddress(), device); 31 | } 32 | } 33 | 34 | public void removeDevice(BluetoothLeDevice device) { 35 | if (device == null) { 36 | return; 37 | } 38 | if (mDeviceMap.containsKey(device.getAddress())) { 39 | mDeviceMap.remove(device.getAddress()); 40 | } 41 | } 42 | 43 | public void clear() { 44 | mDeviceMap.clear(); 45 | } 46 | 47 | public Map getDeviceMap() { 48 | return mDeviceMap; 49 | } 50 | 51 | public List getDeviceList() { 52 | final List methodResult = new ArrayList<>(mDeviceMap.values()); 53 | 54 | Collections.sort(methodResult, new Comparator() { 55 | 56 | @Override 57 | public int compare(final BluetoothLeDevice arg0, final BluetoothLeDevice arg1) { 58 | return arg0.getAddress().compareToIgnoreCase(arg1.getAddress()); 59 | } 60 | }); 61 | 62 | return methodResult; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "BluetoothLeDeviceStore{" + 68 | "DeviceList=" + getDeviceList() + 69 | '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/model/adrecord/AdRecord.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.model.adrecord; 2 | 3 | import android.os.Bundle; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | import java.util.Arrays; 8 | 9 | /** 10 | * @Description: 广播包解析model 11 | * 参考:https://www.bluetooth.com/zh-cn/specifications/assigned-numbers/generic-access-profile 12 | * @author: DAWI 13 | * @date: 16/8/7 21:53. 14 | */ 15 | public class AdRecord implements Parcelable { 16 | 17 | public static final int BLE_GAP_AD_TYPE_FLAGS = 0x01;//< Flags for discoverAbility. 18 | public static final int BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE = 0x02;//< Partial list of 16 bit service UUIDs. 19 | public static final int BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE = 0x03;//< Complete list of 16 bit service UUIDs. 20 | public static final int BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE = 0x04;//< Partial list of 32 bit service UUIDs. 21 | public static final int BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE = 0x05;//< Complete list of 32 bit service UUIDs. 22 | public static final int BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE = 0x06;//< Partial list of 128 bit service UUIDs. 23 | public static final int BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE = 0x07;//< Complete list of 128 bit service UUIDs. 24 | public static final int BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME = 0x08;//< Short local device name. 25 | public static final int BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME = 0x09;//< Complete local device name. 26 | public static final int BLE_GAP_AD_TYPE_TX_POWER_LEVEL = 0x0A;//< Transmit power level. 27 | public static final int BLE_GAP_AD_TYPE_CLASS_OF_DEVICE = 0x0D;//< Class of device. 28 | public static final int BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C = 0x0E;//< Simple Pairing Hash C. 29 | public static final int BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R = 0x0F;//< Simple Pairing Randomizer R. 30 | public static final int BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE = 0x10;//< Security Manager TK Value. 31 | public static final int BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS = 0x11;//< Security Manager Out Of Band Flags. 32 | public static final int BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE = 0x12;//< Slave Connection Interval Range. 33 | public static final int BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT = 0x14;//< List of 16-bit Service Solicitation UUIDs. 34 | public static final int BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT = 0x15;//< List of 128-bit Service Solicitation UUIDs. 35 | public static final int BLE_GAP_AD_TYPE_SERVICE_DATA = 0x16;//< Service Data - 16-bit UUID. 36 | public static final int BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS = 0x17;//< Public Target Address. 37 | public static final int BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS = 0x18;//< Random Target Address. 38 | public static final int BLE_GAP_AD_TYPE_APPEARANCE = 0x19;//< Appearance. 39 | public static final int BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL = 0x1A;//< Advertising Interval. 40 | public static final int BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS = 0x1B;//< LE Bluetooth Device Address. 41 | public static final int BLE_GAP_AD_TYPE_LE_ROLE = 0x1C;//< LE Role. 42 | public static final int BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 = 0x1D;//< Simple Pairing Hash C-256. 43 | public static final int BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 = 0x1E;//< Simple Pairing Randomizer R-256. 44 | public static final int BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID = 0x20;//< Service Data - 32-bit UUID. 45 | public static final int BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID = 0x21;//< Service Data - 128-bit UUID. 46 | public static final int BLE_GAP_AD_TYPE_3D_INFORMATION_DATA = 0x3D;//< 3D Information Data. 47 | public static final int BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;//< Manufacturer Specific Data. 48 | 49 | public static final Creator CREATOR = new Creator() { 50 | public AdRecord createFromParcel(final Parcel in) { 51 | return new AdRecord(in); 52 | } 53 | 54 | public AdRecord[] newArray(final int size) { 55 | return new AdRecord[size]; 56 | } 57 | }; 58 | private static final String PARCEL_RECORD_DATA = "record_data"; 59 | private static final String PARCEL_RECORD_TYPE = "record_type"; 60 | private static final String PARCEL_RECORD_LENGTH = "record_length"; 61 | /* Model Object Definition */ 62 | private final int mLength; 63 | private final int mType; 64 | private final byte[] mData; 65 | 66 | public AdRecord(final int length, final int type, final byte[] data) { 67 | mLength = length; 68 | mType = type; 69 | mData = data; 70 | } 71 | 72 | public AdRecord(final Parcel in) { 73 | final Bundle b = in.readBundle(getClass().getClassLoader()); 74 | mLength = b.getInt(PARCEL_RECORD_LENGTH); 75 | mType = b.getInt(PARCEL_RECORD_TYPE); 76 | mData = b.getByteArray(PARCEL_RECORD_DATA); 77 | } 78 | 79 | @Override 80 | public int describeContents() { 81 | return 0; 82 | } 83 | 84 | public byte[] getData() { 85 | return mData; 86 | } 87 | 88 | public String getHumanReadableType() { 89 | return getHumanReadableAdType(mType); 90 | } 91 | 92 | public int getLength() { 93 | return mLength; 94 | } 95 | 96 | public int getType() { 97 | return mType; 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "AdRecord [mLength=" + mLength + ", mType=" + mType + ", mData=" + Arrays.toString(mData) + ", getHumanReadableType()=" + 103 | getHumanReadableType() + "]"; 104 | } 105 | 106 | @Override 107 | public void writeToParcel(final Parcel parcel, final int arg1) { 108 | final Bundle b = new Bundle(getClass().getClassLoader()); 109 | 110 | b.putInt(PARCEL_RECORD_LENGTH, mLength); 111 | b.putInt(PARCEL_RECORD_TYPE, mType); 112 | b.putByteArray(PARCEL_RECORD_DATA, mData); 113 | 114 | parcel.writeBundle(b); 115 | } 116 | 117 | private static String getHumanReadableAdType(final int type) { 118 | switch (type) { 119 | case BLE_GAP_AD_TYPE_FLAGS: 120 | return "Flags for discoverAbility."; 121 | case BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE: 122 | return "Partial list of 16 bit service UUIDs."; 123 | case BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE: 124 | return "Complete list of 16 bit service UUIDs."; 125 | case BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE: 126 | return "Partial list of 32 bit service UUIDs."; 127 | case BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE: 128 | return "Complete list of 32 bit service UUIDs."; 129 | case BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE: 130 | return "Partial list of 128 bit service UUIDs."; 131 | case BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE: 132 | return "Complete list of 128 bit service UUIDs."; 133 | case BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME: 134 | return "Short local device name."; 135 | case BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME: 136 | return "Complete local device name."; 137 | case BLE_GAP_AD_TYPE_TX_POWER_LEVEL: 138 | return "Transmit power level."; 139 | case BLE_GAP_AD_TYPE_CLASS_OF_DEVICE: 140 | return "Class of device."; 141 | case BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C: 142 | return "Simple Pairing Hash C."; 143 | case BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R: 144 | return "Simple Pairing Randomizer R."; 145 | case BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE: 146 | return "Security Manager TK Value."; 147 | case BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS: 148 | return "Security Manager Out Of Band Flags."; 149 | case BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE: 150 | return "Slave Connection Interval Range."; 151 | case BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT: 152 | return "List of 16-bit Service Solicitation UUIDs."; 153 | case BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT: 154 | return "List of 128-bit Service Solicitation UUIDs."; 155 | case BLE_GAP_AD_TYPE_SERVICE_DATA: 156 | return "Service Data - 16-bit UUID."; 157 | case BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS: 158 | return "Public Target Address."; 159 | case BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS: 160 | return "Random Target Address."; 161 | case BLE_GAP_AD_TYPE_APPEARANCE: 162 | return "Appearance."; 163 | case BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL: 164 | return "Advertising Interval."; 165 | case BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS: 166 | return "LE Bluetooth Device Address."; 167 | case BLE_GAP_AD_TYPE_LE_ROLE: 168 | return "LE Role."; 169 | case BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256: 170 | return "Simple Pairing Hash C-256."; 171 | case BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256: 172 | return "Simple Pairing Randomizer R-256."; 173 | case BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID: 174 | return "Service Data - 32-bit UUID."; 175 | case BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID: 176 | return "Service Data - 128-bit UUID."; 177 | case BLE_GAP_AD_TYPE_3D_INFORMATION_DATA: 178 | return "3D Information Data."; 179 | case BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA: 180 | return "Manufacturer Specific Data."; 181 | default: 182 | return "Unknown AdRecord Structure: " + type; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/model/adrecord/AdRecordStore.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.model.adrecord; 2 | 3 | import android.os.Bundle; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | import android.util.SparseArray; 7 | 8 | import com.vise.baseble.utils.AdRecordUtil; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | 14 | /** 15 | * @Description: 广播包解析仓库 16 | * @author: DAWI 17 | * @date: 16/8/7 21:54. 18 | */ 19 | public class AdRecordStore implements Parcelable { 20 | 21 | public static final Creator CREATOR = new Creator() { 22 | public AdRecordStore createFromParcel(final Parcel in) { 23 | return new AdRecordStore(in); 24 | } 25 | 26 | public AdRecordStore[] newArray(final int size) { 27 | return new AdRecordStore[size]; 28 | } 29 | }; 30 | private static final String RECORDS_ARRAY = "records_array"; 31 | private static final String LOCAL_NAME_COMPLETE = "local_name_complete"; 32 | private static final String LOCAL_NAME_SHORT = "local_name_short"; 33 | private final SparseArray mAdRecords; 34 | private final String mLocalNameComplete; 35 | private final String mLocalNameShort; 36 | 37 | public AdRecordStore(final Parcel in) { 38 | final Bundle b = in.readBundle(getClass().getClassLoader()); 39 | mAdRecords = b.getSparseParcelableArray(RECORDS_ARRAY); 40 | mLocalNameComplete = b.getString(LOCAL_NAME_COMPLETE); 41 | mLocalNameShort = b.getString(LOCAL_NAME_SHORT); 42 | } 43 | 44 | /** 45 | * Instantiates a new Bluetooth LE device Ad Record Store. 46 | * 47 | * @param adRecords the ad records 48 | */ 49 | public AdRecordStore(final SparseArray adRecords) { 50 | mAdRecords = adRecords; 51 | mLocalNameComplete = AdRecordUtil.getRecordDataAsString(mAdRecords.get(AdRecord.BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME)); 52 | mLocalNameShort = AdRecordUtil.getRecordDataAsString(mAdRecords.get(AdRecord.BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME)); 53 | 54 | } 55 | 56 | /* (non-Javadoc) 57 | * @see android.os.Parcelable#describeContents() 58 | */ 59 | @Override 60 | public int describeContents() { 61 | return 0; 62 | } 63 | 64 | /** 65 | * Gets the short local device name. 66 | * 67 | * @return the local name complete 68 | */ 69 | public String getLocalNameComplete() { 70 | return mLocalNameComplete; 71 | } 72 | 73 | /** 74 | * Gets the complete local device name. 75 | * 76 | * @return the local name short 77 | */ 78 | public String getLocalNameShort() { 79 | return mLocalNameShort; 80 | } 81 | 82 | /** 83 | * retrieves an individual record. 84 | * 85 | * @param record the record 86 | * @return the record 87 | */ 88 | public AdRecord getRecord(final int record) { 89 | return mAdRecords.get(record); 90 | } 91 | 92 | /** 93 | * Gets the record data as string. 94 | * 95 | * @param record the record 96 | * @return the record data as string 97 | */ 98 | public String getRecordDataAsString(final int record) { 99 | return AdRecordUtil.getRecordDataAsString(mAdRecords.get(record)); 100 | } 101 | 102 | /** 103 | * Gets the record as collection. 104 | * 105 | * @return the records as collection 106 | */ 107 | public Collection getRecordsAsCollection() { 108 | return Collections.unmodifiableCollection(asList(mAdRecords)); 109 | } 110 | 111 | /** 112 | * Checks if is record present. 113 | * 114 | * @param record the record 115 | * @return true, if is record present 116 | */ 117 | public boolean isRecordPresent(final int record) { 118 | return mAdRecords.indexOfKey(record) >= 0; 119 | } 120 | 121 | /* (non-Javadoc) 122 | * @see java.lang.Object#toString() 123 | */ 124 | @Override 125 | public String toString() { 126 | return "AdRecordStore [mLocalNameComplete=" + mLocalNameComplete + ", mLocalNameShort=" + mLocalNameShort + "]"; 127 | } 128 | 129 | /* (non-Javadoc) 130 | * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) 131 | */ 132 | @Override 133 | public void writeToParcel(final Parcel parcel, final int arg1) { 134 | final Bundle b = new Bundle(); 135 | b.putString(LOCAL_NAME_COMPLETE, mLocalNameComplete); 136 | b.putString(LOCAL_NAME_SHORT, mLocalNameShort); 137 | b.putSparseParcelableArray(RECORDS_ARRAY, mAdRecords); 138 | parcel.writeBundle(b); 139 | } 140 | 141 | /** 142 | * As list. 143 | * 144 | * @param the generic type 145 | * @param sparseArray the sparse array 146 | * @return the collection 147 | */ 148 | public static Collection asList(final SparseArray sparseArray) { 149 | if (sparseArray == null) return null; 150 | final Collection arrayList = new ArrayList<>(sparseArray.size()); 151 | for (int i = 0; i < sparseArray.size(); i++) { 152 | arrayList.add(sparseArray.valueAt(i)); 153 | } 154 | return arrayList; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/model/resolver/BluetoothClassResolver.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.model.resolver; 2 | 3 | import android.bluetooth.BluetoothClass; 4 | 5 | /** 6 | * @Description: 蓝牙设备类别 7 | * @author: DAWI 8 | * @date: 16/8/7 21:48. 9 | */ 10 | public class BluetoothClassResolver { 11 | public static String resolveDeviceClass(final int btClass) { 12 | switch (btClass) { 13 | case BluetoothClass.Device.AUDIO_VIDEO_CAMCORDER: 14 | return "A/V, Camcorder"; 15 | case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 16 | return "A/V, Car Audio"; 17 | case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 18 | return "A/V, Handsfree"; 19 | case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 20 | return "A/V, Headphones"; 21 | case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 22 | return "A/V, HiFi Audio"; 23 | case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: 24 | return "A/V, Loudspeaker"; 25 | case BluetoothClass.Device.AUDIO_VIDEO_MICROPHONE: 26 | return "A/V, Microphone"; 27 | case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 28 | return "A/V, Portable Audio"; 29 | case BluetoothClass.Device.AUDIO_VIDEO_SET_TOP_BOX: 30 | return "A/V, Set Top Box"; 31 | case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED: 32 | return "A/V, Uncategorized"; 33 | case BluetoothClass.Device.AUDIO_VIDEO_VCR: 34 | return "A/V, VCR"; 35 | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CAMERA: 36 | return "A/V, Video Camera"; 37 | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_CONFERENCING: 38 | return "A/V, Video Conferencing"; 39 | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER: 40 | return "A/V, Video Display and Loudspeaker"; 41 | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_GAMING_TOY: 42 | return "A/V, Video Gaming Toy"; 43 | case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_MONITOR: 44 | return "A/V, Video Monitor"; 45 | case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 46 | return "A/V, Video Wearable Headset"; 47 | case BluetoothClass.Device.COMPUTER_DESKTOP: 48 | return "Computer, Desktop"; 49 | case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA: 50 | return "Computer, Handheld PC/PDA"; 51 | case BluetoothClass.Device.COMPUTER_LAPTOP: 52 | return "Computer, Laptop"; 53 | case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA: 54 | return "Computer, Palm Size PC/PDA"; 55 | case BluetoothClass.Device.COMPUTER_SERVER: 56 | return "Computer, Server"; 57 | case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: 58 | return "Computer, Uncategorized"; 59 | case BluetoothClass.Device.COMPUTER_WEARABLE: 60 | return "Computer, Wearable"; 61 | case BluetoothClass.Device.HEALTH_BLOOD_PRESSURE: 62 | return "Health, Blood Pressure"; 63 | case BluetoothClass.Device.HEALTH_DATA_DISPLAY: 64 | return "Health, Data Display"; 65 | case BluetoothClass.Device.HEALTH_GLUCOSE: 66 | return "Health, Glucose"; 67 | case BluetoothClass.Device.HEALTH_PULSE_OXIMETER: 68 | return "Health, Pulse Oximeter"; 69 | case BluetoothClass.Device.HEALTH_PULSE_RATE: 70 | return "Health, Pulse Rate"; 71 | case BluetoothClass.Device.HEALTH_THERMOMETER: 72 | return "Health, Thermometer"; 73 | case BluetoothClass.Device.HEALTH_UNCATEGORIZED: 74 | return "Health, Uncategorized"; 75 | case BluetoothClass.Device.HEALTH_WEIGHING: 76 | return "Health, Weighting"; 77 | case BluetoothClass.Device.PHONE_CELLULAR: 78 | return "Phone, Cellular"; 79 | case BluetoothClass.Device.PHONE_CORDLESS: 80 | return "Phone, Cordless"; 81 | case BluetoothClass.Device.PHONE_ISDN: 82 | return "Phone, ISDN"; 83 | case BluetoothClass.Device.PHONE_MODEM_OR_GATEWAY: 84 | return "Phone, Modem or Gateway"; 85 | case BluetoothClass.Device.PHONE_SMART: 86 | return "Phone, Smart"; 87 | case BluetoothClass.Device.PHONE_UNCATEGORIZED: 88 | return "Phone, Uncategorized"; 89 | case BluetoothClass.Device.TOY_CONTROLLER: 90 | return "Toy, Controller"; 91 | case BluetoothClass.Device.TOY_DOLL_ACTION_FIGURE: 92 | return "Toy, Doll/Action Figure"; 93 | case BluetoothClass.Device.TOY_GAME: 94 | return "Toy, Game"; 95 | case BluetoothClass.Device.TOY_ROBOT: 96 | return "Toy, Robot"; 97 | case BluetoothClass.Device.TOY_UNCATEGORIZED: 98 | return "Toy, Uncategorized"; 99 | case BluetoothClass.Device.TOY_VEHICLE: 100 | return "Toy, Vehicle"; 101 | case BluetoothClass.Device.WEARABLE_GLASSES: 102 | return "Wearable, Glasses"; 103 | case BluetoothClass.Device.WEARABLE_HELMET: 104 | return "Wearable, Helmet"; 105 | case BluetoothClass.Device.WEARABLE_JACKET: 106 | return "Wearable, Jacket"; 107 | case BluetoothClass.Device.WEARABLE_PAGER: 108 | return "Wearable, Pager"; 109 | case BluetoothClass.Device.WEARABLE_UNCATEGORIZED: 110 | return "Wearable, Uncategorized"; 111 | case BluetoothClass.Device.WEARABLE_WRIST_WATCH: 112 | return "Wearable, Wrist Watch"; 113 | default: 114 | return "Unknown, Unknown (class=" + btClass + ")"; 115 | } 116 | } 117 | 118 | public static String resolveMajorDeviceClass(final int majorBtClass) { 119 | switch (majorBtClass) { 120 | case BluetoothClass.Device.Major.AUDIO_VIDEO: 121 | return "Audio/ Video"; 122 | case BluetoothClass.Device.Major.COMPUTER: 123 | return "Computer"; 124 | case BluetoothClass.Device.Major.HEALTH: 125 | return "Health"; 126 | case BluetoothClass.Device.Major.IMAGING: 127 | return "Imaging"; 128 | case BluetoothClass.Device.Major.MISC: 129 | return "Misc"; 130 | case BluetoothClass.Device.Major.NETWORKING: 131 | return "Networking"; 132 | case BluetoothClass.Device.Major.PERIPHERAL: 133 | return "Peripheral"; 134 | case BluetoothClass.Device.Major.PHONE: 135 | return "Phone"; 136 | case BluetoothClass.Device.Major.TOY: 137 | return "Toy"; 138 | case BluetoothClass.Device.Major.UNCATEGORIZED: 139 | return "Uncategorized"; 140 | case BluetoothClass.Device.Major.WEARABLE: 141 | return "Wearable"; 142 | default: 143 | return "Unknown (" + majorBtClass + ")"; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/utils/AdRecordUtil.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.utils; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.vise.baseble.model.adrecord.AdRecord; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * @Description: 广播包解析工具类 16 | * @author: DAWI 17 | * @date: 16/8/7 21:56. 18 | */ 19 | public class AdRecordUtil { 20 | private AdRecordUtil() { 21 | // TO AVOID INSTANTIATION 22 | } 23 | 24 | public static String getRecordDataAsString(final AdRecord nameRecord) { 25 | if (nameRecord == null) { 26 | return ""; 27 | } 28 | return new String(nameRecord.getData()); 29 | } 30 | 31 | public static byte[] getServiceData(final AdRecord serviceData) { 32 | if (serviceData == null) { 33 | return null; 34 | } 35 | if (serviceData.getType() != AdRecord.BLE_GAP_AD_TYPE_SERVICE_DATA) return null; 36 | 37 | final byte[] raw = serviceData.getData(); 38 | //Chop out the uuid 39 | return Arrays.copyOfRange(raw, 2, raw.length); 40 | } 41 | 42 | public static int getServiceDataUuid(final AdRecord serviceData) { 43 | if (serviceData == null) { 44 | return -1; 45 | } 46 | if (serviceData.getType() != AdRecord.BLE_GAP_AD_TYPE_SERVICE_DATA) return -1; 47 | 48 | final byte[] raw = serviceData.getData(); 49 | //Find UUID data in byte array 50 | int uuid = (raw[1] & 0xFF) << 8; 51 | uuid += (raw[0] & 0xFF); 52 | 53 | return uuid; 54 | } 55 | 56 | /* 57 | * Read out all the AD structures from the raw scan record 58 | */ 59 | public static List parseScanRecordAsList(final byte[] scanRecord) { 60 | final List records = new ArrayList<>(); 61 | 62 | int index = 0; 63 | while (index < scanRecord.length) { 64 | final int length = scanRecord[index++]; 65 | //Done once we run out of records 66 | if (length == 0) break; 67 | 68 | final int type = scanRecord[index] & 0xFF; 69 | 70 | //Done if our record isn't a valid type 71 | if (type == 0) break; 72 | 73 | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); 74 | 75 | records.add(new AdRecord(length, type, data)); 76 | 77 | //Advance 78 | index += length; 79 | } 80 | 81 | return Collections.unmodifiableList(records); 82 | } 83 | 84 | public static Map parseScanRecordAsMap(final byte[] scanRecord) { 85 | final Map records = new HashMap<>(); 86 | 87 | int index = 0; 88 | while (index < scanRecord.length) { 89 | final int length = scanRecord[index++]; 90 | //Done once we run out of records 91 | if (length == 0) break; 92 | 93 | final int type = scanRecord[index] & 0xFF; 94 | 95 | //Done if our record isn't a valid type 96 | if (type == 0) break; 97 | 98 | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); 99 | 100 | records.put(type, new AdRecord(length, type, data)); 101 | 102 | //Advance 103 | index += length; 104 | } 105 | 106 | return Collections.unmodifiableMap(records); 107 | } 108 | 109 | public static SparseArray parseScanRecordAsSparseArray(final byte[] scanRecord) { 110 | final SparseArray records = new SparseArray<>(); 111 | 112 | int index = 0; 113 | while (index < scanRecord.length) { 114 | final int length = scanRecord[index++]; 115 | //Done once we run out of records 116 | if (length == 0) break; 117 | 118 | final int type = scanRecord[index] & 0xFF; 119 | 120 | //Done if our record isn't a valid type 121 | if (type == 0) break; 122 | 123 | final byte[] data = Arrays.copyOfRange(scanRecord, index + 1, index + length); 124 | 125 | records.put(type, new AdRecord(length, type, data)); 126 | 127 | //Advance 128 | index += length; 129 | } 130 | 131 | return records; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/utils/BleUtil.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.utils; 2 | 3 | import android.app.Activity; 4 | import android.bluetooth.BluetoothAdapter; 5 | import android.bluetooth.BluetoothManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | 10 | /** 11 | * @Description: 蓝牙基础操作工具类 12 | * @author: DAWI 13 | * @date: 16/8/5 20:43. 14 | */ 15 | public class BleUtil { 16 | public static void enableBluetooth(Activity activity, int requestCode) { 17 | Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 18 | activity.startActivityForResult(intent, requestCode); 19 | } 20 | 21 | public static boolean isSupportBle(Context context) { 22 | if (context == null || !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 23 | return false; 24 | } 25 | BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); 26 | return manager.getAdapter() != null; 27 | } 28 | 29 | public static boolean isBleEnable(Context context) { 30 | if (!isSupportBle(context)) { 31 | return false; 32 | } 33 | BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); 34 | return manager.getAdapter().isEnabled(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /baseble/src/main/java/com/vise/baseble/utils/HexUtil.java: -------------------------------------------------------------------------------- 1 | package com.vise.baseble.utils; 2 | 3 | import com.vise.log.ViseLog; 4 | 5 | /** 6 | * @Description: 十六进制转换类 7 | * @author: DAWI 8 | * @date: 16/8/7 21:57. 9 | */ 10 | public class HexUtil { 11 | /** 12 | * 用于建立十六进制字符的输出的小写字符数组 13 | */ 14 | private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 15 | 16 | /** 17 | * 用于建立十六进制字符的输出的大写字符数组 18 | */ 19 | private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 20 | 21 | /** 22 | * 将字节数组转换为十六进制字符数组 23 | * 24 | * @param data byte[] 25 | * @return 十六进制char[] 26 | */ 27 | public static char[] encodeHex(byte[] data) { 28 | return encodeHex(data, true); 29 | } 30 | 31 | /** 32 | * 将字节数组转换为十六进制字符数组 33 | * 34 | * @param data byte[] 35 | * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 36 | * @return 十六进制char[] 37 | */ 38 | public static char[] encodeHex(byte[] data, boolean toLowerCase) { 39 | return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 40 | } 41 | 42 | /** 43 | * 将字节数组转换为十六进制字符数组 44 | * 45 | * @param data byte[] 46 | * @param toDigits 用于控制输出的char[] 47 | * @return 十六进制char[] 48 | */ 49 | protected static char[] encodeHex(byte[] data, char[] toDigits) { 50 | int l = data.length; 51 | char[] out = new char[l << 1]; 52 | // two characters form the hex value. 53 | for (int i = 0, j = 0; i < l; i++) { 54 | out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; 55 | out[j++] = toDigits[0x0F & data[i]]; 56 | } 57 | return out; 58 | } 59 | 60 | /** 61 | * 将字节数组转换为十六进制字符串 62 | * 63 | * @param data byte[] 64 | * @return 十六进制String 65 | */ 66 | public static String encodeHexStr(byte[] data) { 67 | return encodeHexStr(data, true); 68 | } 69 | 70 | /** 71 | * 将字节数组转换为十六进制字符串 72 | * 73 | * @param data byte[] 74 | * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 75 | * @return 十六进制String 76 | */ 77 | public static String encodeHexStr(byte[] data, boolean toLowerCase) { 78 | return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 79 | } 80 | 81 | /** 82 | * 将字节数组转换为十六进制字符串 83 | * 84 | * @param data byte[] 85 | * @param toDigits 用于控制输出的char[] 86 | * @return 十六进制String 87 | */ 88 | protected static String encodeHexStr(byte[] data, char[] toDigits) { 89 | if (data == null) { 90 | ViseLog.e("this data is null."); 91 | return ""; 92 | } 93 | return new String(encodeHex(data, toDigits)); 94 | } 95 | 96 | /** 97 | * 将十六进制字符串转换为字节数组 98 | * 99 | * @param data 100 | * @return 101 | */ 102 | public static byte[] decodeHex(String data) { 103 | if (data == null) { 104 | ViseLog.e("this data is null."); 105 | return new byte[0]; 106 | } 107 | return decodeHex(data.toCharArray()); 108 | } 109 | 110 | /** 111 | * 将十六进制字符数组转换为字节数组 112 | * 113 | * @param data 十六进制char[] 114 | * @return byte[] 115 | * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常 116 | */ 117 | public static byte[] decodeHex(char[] data) { 118 | 119 | int len = data.length; 120 | 121 | if ((len & 0x01) != 0) { 122 | throw new RuntimeException("Odd number of characters."); 123 | } 124 | 125 | byte[] out = new byte[len >> 1]; 126 | 127 | // two characters form the hex value. 128 | for (int i = 0, j = 0; j < len; i++) { 129 | int f = toDigit(data[j], j) << 4; 130 | j++; 131 | f = f | toDigit(data[j], j); 132 | j++; 133 | out[i] = (byte) (f & 0xFF); 134 | } 135 | 136 | return out; 137 | } 138 | 139 | /** 140 | * 将十六进制字符转换成一个整数 141 | * 142 | * @param ch 十六进制char 143 | * @param index 十六进制字符在字符数组中的位置 144 | * @return 一个整数 145 | * @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常 146 | */ 147 | protected static int toDigit(char ch, int index) { 148 | int digit = Character.digit(ch, 16); 149 | if (digit == -1) { 150 | throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); 151 | } 152 | return digit; 153 | } 154 | 155 | /** 156 | * 截取字节数组 157 | * 158 | * @param src byte [] 数组源 这里填16进制的 数组 159 | * @param begin 起始位置 源数组的起始位置。0位置有效 160 | * @param count 截取长度 161 | * @return 162 | */ 163 | public static byte[] subBytes(byte[] src, int begin, int count) { 164 | byte[] bs = new byte[count]; 165 | System.arraycopy(src, begin, bs, 0, count); // bs 目的数组 0 截取后存放的数值起始位置。0位置有效 166 | return bs; 167 | } 168 | 169 | /** 170 | * int转byte数组 171 | * 172 | * @param bb 173 | * @param x 174 | * @param index 第几位开始 175 | * @param flag 标识高低位顺序,高位在前为true,低位在前为false 176 | */ 177 | public static void intToByte(byte[] bb, int x, int index, boolean flag) { 178 | if (flag) { 179 | bb[index + 0] = (byte) (x >> 24); 180 | bb[index + 1] = (byte) (x >> 16); 181 | bb[index + 2] = (byte) (x >> 8); 182 | bb[index + 3] = (byte) (x >> 0); 183 | } else { 184 | bb[index + 3] = (byte) (x >> 24); 185 | bb[index + 2] = (byte) (x >> 16); 186 | bb[index + 1] = (byte) (x >> 8); 187 | bb[index + 0] = (byte) (x >> 0); 188 | } 189 | } 190 | 191 | /** 192 | * byte数组转int 193 | * 194 | * @param bb 195 | * @param index 第几位开始 196 | * @param flag 标识高低位顺序,高位在前为true,低位在前为false 197 | * @return 198 | */ 199 | public static int byteToInt(byte[] bb, int index, boolean flag) { 200 | if (flag) { 201 | return (int) ((((bb[index + 0] & 0xff) << 24) 202 | | ((bb[index + 1] & 0xff) << 16) 203 | | ((bb[index + 2] & 0xff) << 8) 204 | | ((bb[index + 3] & 0xff) << 0))); 205 | } else { 206 | return (int) ((((bb[index + 3] & 0xff) << 24) 207 | | ((bb[index + 2] & 0xff) << 16) 208 | | ((bb[index + 1] & 0xff) << 8) 209 | | ((bb[index + 0] & 0xff) << 0))); 210 | } 211 | } 212 | 213 | 214 | /** 215 | * 字节数组逆序 216 | * 217 | * @param data 218 | * @return 219 | */ 220 | public static byte[] reverse(byte[] data) { 221 | byte[] reverseData = new byte[data.length]; 222 | for (int i = 0; i < data.length; i++) { 223 | reverseData[i] = data[data.length - 1 - i]; 224 | } 225 | return reverseData; 226 | } 227 | 228 | /** 229 | * 蓝牙传输 16进制 高低位 读数的 转换 230 | * 231 | * @param data 截取数据源,字节数组 232 | * @param index 截取数据开始位置 233 | * @param count 截取数据长度,只能为2、4、8个字节 234 | * @param flag 标识高低位顺序,高位在前为true,低位在前为false 235 | * @return 236 | */ 237 | public static long byteToLong(byte[] data, int index, int count, boolean flag) { 238 | long lg = 0; 239 | if (flag) { 240 | switch (count) { 241 | case 2: 242 | lg = ((((long) data[index + 0] & 0xff) << 8) 243 | | (((long) data[index + 1] & 0xff) << 0)); 244 | break; 245 | 246 | case 4: 247 | lg = ((((long) data[index + 0] & 0xff) << 24) 248 | | (((long) data[index + 1] & 0xff) << 16) 249 | | (((long) data[index + 2] & 0xff) << 8) 250 | | (((long) data[index + 3] & 0xff) << 0)); 251 | break; 252 | 253 | case 8: 254 | lg = ((((long) data[index + 0] & 0xff) << 56) 255 | | (((long) data[index + 1] & 0xff) << 48) 256 | | (((long) data[index + 2] & 0xff) << 40) 257 | | (((long) data[index + 3] & 0xff) << 32) 258 | | (((long) data[index + 4] & 0xff) << 24) 259 | | (((long) data[index + 5] & 0xff) << 16) 260 | | (((long) data[index + 6] & 0xff) << 8) 261 | | (((long) data[index + 7] & 0xff) << 0)); 262 | break; 263 | } 264 | return lg; 265 | } else { 266 | switch (count) { 267 | case 2: 268 | lg = ((((long) data[index + 1] & 0xff) << 8) 269 | | (((long) data[index + 0] & 0xff) << 0)); 270 | break; 271 | case 4: 272 | lg = ((((long) data[index + 3] & 0xff) << 24) 273 | | (((long) data[index + 2] & 0xff) << 16) 274 | | (((long) data[index + 1] & 0xff) << 8) 275 | | (((long) data[index + 0] & 0xff) << 0)); 276 | break; 277 | case 8: 278 | lg = ((((long) data[index + 7] & 0xff) << 56) 279 | | (((long) data[index + 6] & 0xff) << 48) 280 | | (((long) data[index + 5] & 0xff) << 40) 281 | | (((long) data[index + 4] & 0xff) << 32) 282 | | (((long) data[index + 3] & 0xff) << 24) 283 | | (((long) data[index + 2] & 0xff) << 16) 284 | | (((long) data[index + 1] & 0xff) << 8) 285 | | (((long) data[index + 0] & 0xff) << 0)); 286 | break; 287 | } 288 | return lg; 289 | } 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /baseble/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | baseble 3 | 4 | -------------------------------------------------------------------------------- /bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | version = android.defaultConfig.versionName 5 | group = GROUP 6 | 7 | install{ 8 | repositories.mavenInstaller{ 9 | pom{ 10 | project{ 11 | packaging PACKAGING 12 | name DESCRIBE 13 | url SITE_URL 14 | licenses{ 15 | license{ 16 | name LICENSE_NAME 17 | url LICENSE_URL 18 | } 19 | } 20 | developers{ 21 | developer{ 22 | id DEVELOPER_ID 23 | name DEVELOPER_NAME 24 | email DEVELOPER_EMAIL 25 | } 26 | } 27 | scm{ 28 | connection GIT_URL 29 | developerConnection GIT_URL 30 | url SITE_URL 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | task sourcesJar(type: Jar){ 38 | from android.sourceSets.main.java.srcDirs 39 | classifier = 'sources' 40 | } 41 | 42 | task javadoc(type: Javadoc){ 43 | options.encoding = "UTF-8" 44 | source = android.sourceSets.main.java.srcDirs 45 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 46 | } 47 | 48 | task javadocJar(type: Jar, dependsOn: javadoc){ 49 | classifier = 'javadoc' 50 | from javadoc.destinationDir 51 | } 52 | 53 | tasks.withType(JavaCompile) { 54 | options.encoding = "UTF-8" 55 | } 56 | 57 | tasks.withType(Javadoc) { 58 | options.addStringOption('Xdoclint:none', '-quiet') 59 | options.addStringOption('encoding', 'UTF-8') 60 | options.addStringOption('charSet', 'UTF-8') 61 | } 62 | 63 | artifacts{ 64 | archives javadocJar 65 | archives sourcesJar 66 | } 67 | 68 | Properties properties = new Properties() 69 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 70 | bintray{ 71 | user = properties.getProperty("bintray.user") 72 | key = properties.getProperty("bintray.apikey") 73 | configurations = ['archives'] 74 | pkg{ 75 | repo = PROJECT_REPO 76 | name = PROJECT_NAME 77 | websiteUrl = SITE_URL 78 | vcsUrl = GIT_URL 79 | licenses = ["Apache-2.0"] 80 | publish = PROJECT_PUBLISH 81 | } 82 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | GROUP=com.vise.xiaoyaoyou 21 | 22 | SITE_URL=https://github.com/xiaoyaoyou1212/BLE 23 | GIT_URL=https://github.com/xiaoyaoyou1212/BLE.git 24 | 25 | PACKAGING=aar 26 | DESCRIBE=Custom Common Framework BLE Code For Android 27 | 28 | LICENSE_NAME=The Apache Software License, Version 2.0 29 | LICENSE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 30 | 31 | DEVELOPER_ID=xiaoyaoyou 32 | DEVELOPER_NAME=dawi 33 | DEVELOPER_EMAIL=553252628@qq.com 34 | 35 | PROJECT_REPO=maven 36 | PROJECT_NAME=BLE 37 | PROJECT_PUBLISH=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyaoyou1212/BLE/f2fd8dd55b29cd1e1e1c7a7c13b2048930726f75/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Oct 10 09:55:54 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /jar.gradle: -------------------------------------------------------------------------------- 1 | task buildJar(dependsOn: ['compileReleaseJavaWithJavac'], type: Jar) { 2 | 3 | appendix = "BLE" 4 | baseName = "baseble" 5 | version = "1.0.5" 6 | classifier = "release" 7 | 8 | extension = "jar" 9 | archiveName = "baseble.jar" 10 | 11 | def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"]; 12 | from srcClassDir 13 | 14 | exclude "com/vise/baseble/BuildConfig.class" 15 | exclude "com/vise/baseble/BuildConfig\$*.class" 16 | exclude "**/R.class" 17 | exclude "**/R\$*.class" 18 | 19 | include "com/vise/baseble/**/*.class" 20 | } 21 | -------------------------------------------------------------------------------- /newapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /newapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.vise.bledemo" 9 | minSdkVersion 18 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'com.android.support:appcompat-v7:23.2.1' 24 | compile 'com.android.support:design:23.0.1' 25 | compile 'com.vise.xiaoyaoyou:xsnow:2.1.3' 26 | compile project(':baseble') 27 | } 28 | -------------------------------------------------------------------------------- /newapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in F:\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /newapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/activity/DeviceDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.widget.LinearLayout; 10 | import android.widget.ListView; 11 | import android.widget.TextView; 12 | 13 | import com.vise.baseble.common.BluetoothServiceType; 14 | import com.vise.baseble.model.BluetoothLeDevice; 15 | import com.vise.baseble.model.adrecord.AdRecord; 16 | import com.vise.baseble.utils.AdRecordUtil; 17 | import com.vise.baseble.utils.HexUtil; 18 | import com.vise.bledemo.R; 19 | import com.vise.bledemo.adapter.MergeAdapter; 20 | 21 | import java.text.SimpleDateFormat; 22 | import java.util.Collection; 23 | import java.util.Date; 24 | import java.util.Locale; 25 | import java.util.TimeZone; 26 | 27 | /** 28 | * 设备详细信息展示界面 29 | */ 30 | public class DeviceDetailActivity extends AppCompatActivity { 31 | 32 | public static final String EXTRA_DEVICE = "extra_device"; 33 | private ListView mList; 34 | private View mEmpty; 35 | private BluetoothLeDevice mDevice; 36 | 37 | /** 38 | * 追加广播包信息 39 | * 40 | * @param adapter 41 | * @param title 42 | * @param record 43 | */ 44 | private void appendAdRecordView(final MergeAdapter adapter, final String title, final AdRecord record) { 45 | final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_adrecord, null); 46 | final TextView tvString = (TextView) lt.findViewById(R.id.data_as_string); 47 | final TextView tvArray = (TextView) lt.findViewById(R.id.data_as_array); 48 | final TextView tvTitle = (TextView) lt.findViewById(R.id.title); 49 | 50 | tvTitle.setText(title); 51 | tvString.setText("'" + AdRecordUtil.getRecordDataAsString(record) + "'"); 52 | tvArray.setText("'" + HexUtil.encodeHexStr(record.getData()) + "'"); 53 | 54 | adapter.addView(lt); 55 | } 56 | 57 | /** 58 | * 追加设备基础信息 59 | * 60 | * @param adapter 61 | * @param device 62 | */ 63 | private void appendDeviceInfo(final MergeAdapter adapter, final BluetoothLeDevice device) { 64 | final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_device_info, null); 65 | final TextView tvName = (TextView) lt.findViewById(R.id.deviceName); 66 | final TextView tvAddress = (TextView) lt.findViewById(R.id.deviceAddress); 67 | final TextView tvClass = (TextView) lt.findViewById(R.id.deviceClass); 68 | final TextView tvMajorClass = (TextView) lt.findViewById(R.id.deviceMajorClass); 69 | final TextView tvServices = (TextView) lt.findViewById(R.id.deviceServiceList); 70 | final TextView tvBondingState = (TextView) lt.findViewById(R.id.deviceBondingState); 71 | 72 | tvName.setText(device.getName()); 73 | tvAddress.setText(device.getAddress()); 74 | tvClass.setText(device.getBluetoothDeviceClassName()); 75 | tvMajorClass.setText(device.getBluetoothDeviceMajorClassName()); 76 | tvBondingState.setText(device.getBluetoothDeviceBondState()); 77 | 78 | final String supportedServices; 79 | if (device.getBluetoothDeviceKnownSupportedServices().isEmpty()) { 80 | supportedServices = getString(R.string.no_known_services); 81 | } else { 82 | final StringBuilder sb = new StringBuilder(); 83 | 84 | for (final BluetoothServiceType service : device.getBluetoothDeviceKnownSupportedServices()) { 85 | if (sb.length() > 0) { 86 | sb.append(", "); 87 | } 88 | 89 | sb.append(service); 90 | } 91 | supportedServices = sb.toString(); 92 | } 93 | 94 | tvServices.setText(supportedServices); 95 | 96 | adapter.addView(lt); 97 | } 98 | 99 | /** 100 | * 追加信息头 101 | * 102 | * @param adapter 103 | * @param title 104 | */ 105 | private void appendHeader(final MergeAdapter adapter, final String title) { 106 | final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_header, null); 107 | final TextView tvTitle = (TextView) lt.findViewById(R.id.title); 108 | tvTitle.setText(title); 109 | 110 | adapter.addView(lt); 111 | } 112 | 113 | /** 114 | * 追加设备信号信息 115 | * 116 | * @param adapter 117 | * @param device 118 | */ 119 | private void appendRssiInfo(final MergeAdapter adapter, final BluetoothLeDevice device) { 120 | final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_rssi_info, null); 121 | final TextView tvFirstTimestamp = (TextView) lt.findViewById(R.id.firstTimestamp); 122 | final TextView tvFirstRssi = (TextView) lt.findViewById(R.id.firstRssi); 123 | final TextView tvLastTimestamp = (TextView) lt.findViewById(R.id.lastTimestamp); 124 | final TextView tvLastRssi = (TextView) lt.findViewById(R.id.lastRssi); 125 | final TextView tvRunningAverageRssi = (TextView) lt.findViewById(R.id.runningAverageRssi); 126 | 127 | tvFirstTimestamp.setText(formatTime(device.getFirstTimestamp())); 128 | tvFirstRssi.setText(formatRssi(device.getFirstRssi())); 129 | tvLastTimestamp.setText(formatTime(device.getTimestamp())); 130 | tvLastRssi.setText(formatRssi(device.getRssi())); 131 | tvRunningAverageRssi.setText(formatRssi(device.getRunningAverageRssi())); 132 | 133 | adapter.addView(lt); 134 | } 135 | 136 | /** 137 | * 追加简单信息 138 | * 139 | * @param adapter 140 | * @param data 141 | */ 142 | private void appendSimpleText(final MergeAdapter adapter, final byte[] data) { 143 | appendSimpleText(adapter, HexUtil.encodeHexStr(data)); 144 | } 145 | 146 | private void appendSimpleText(final MergeAdapter adapter, final String data) { 147 | final LinearLayout lt = (LinearLayout) getLayoutInflater().inflate(R.layout.list_item_view_textview, null); 148 | final TextView tvData = (TextView) lt.findViewById(R.id.data); 149 | 150 | tvData.setText(data); 151 | 152 | adapter.addView(lt); 153 | } 154 | 155 | 156 | private String formatRssi(final double rssi) { 157 | return getString(R.string.formatter_db, String.valueOf(rssi)); 158 | } 159 | 160 | private String formatRssi(final int rssi) { 161 | return getString(R.string.formatter_db, String.valueOf(rssi)); 162 | } 163 | 164 | @Override 165 | protected void onCreate(final Bundle savedInstanceState) { 166 | super.onCreate(savedInstanceState); 167 | setContentView(R.layout.activity_device_detail); 168 | init(); 169 | } 170 | 171 | private void init() { 172 | mEmpty = findViewById(android.R.id.empty); 173 | mList = (ListView) findViewById(android.R.id.list); 174 | mList.setEmptyView(mEmpty); 175 | mDevice = getIntent().getParcelableExtra(EXTRA_DEVICE); 176 | pupulateDetails(mDevice); 177 | } 178 | 179 | @Override 180 | public boolean onCreateOptionsMenu(final Menu menu) { 181 | getMenuInflater().inflate(R.menu.details, menu); 182 | return true; 183 | } 184 | 185 | @Override 186 | public boolean onOptionsItemSelected(final MenuItem item) { 187 | switch (item.getItemId()) { 188 | case R.id.menu_connect: 189 | if (mDevice == null) return false; 190 | Intent intent = new Intent(DeviceDetailActivity.this, DeviceControlActivity.class); 191 | intent.putExtra(DeviceDetailActivity.EXTRA_DEVICE, mDevice); 192 | startActivity(intent); 193 | break; 194 | } 195 | return true; 196 | } 197 | 198 | /** 199 | * 展示设备详细信息 200 | * 201 | * @param device 设备信息 202 | */ 203 | private void pupulateDetails(final BluetoothLeDevice device) { 204 | final MergeAdapter adapter = new MergeAdapter(); 205 | if (device == null) { 206 | appendHeader(adapter, getString(R.string.header_device_info)); 207 | appendSimpleText(adapter, getString(R.string.invalid_device_data)); 208 | } else { 209 | appendHeader(adapter, getString(R.string.header_device_info)); 210 | appendDeviceInfo(adapter, device); 211 | 212 | appendHeader(adapter, getString(R.string.header_rssi_info)); 213 | appendRssiInfo(adapter, device); 214 | 215 | appendHeader(adapter, getString(R.string.header_scan_record)); 216 | appendSimpleText(adapter, device.getScanRecord()); 217 | 218 | final Collection adRecords = device.getAdRecordStore().getRecordsAsCollection(); 219 | if (adRecords.size() > 0) { 220 | appendHeader(adapter, getString(R.string.header_raw_ad_records)); 221 | 222 | for (final AdRecord record : adRecords) { 223 | 224 | appendAdRecordView(adapter, "#" + record.getType() + " " + record.getHumanReadableType(), record); 225 | } 226 | } 227 | } 228 | mList.setAdapter(adapter); 229 | } 230 | 231 | /** 232 | * 格式化时间 233 | * 234 | * @param time 235 | * @return 236 | */ 237 | private static String formatTime(final long time) { 238 | String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS zzz"; 239 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(ISO_FORMAT, Locale.CHINA); 240 | simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 241 | return simpleDateFormat.format(new Date(time)); 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/activity/DeviceScanActivity.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | import android.widget.ListView; 11 | import android.widget.TextView; 12 | 13 | import com.vise.baseble.ViseBle; 14 | import com.vise.baseble.callback.scan.IScanCallback; 15 | import com.vise.baseble.callback.scan.ScanCallback; 16 | import com.vise.baseble.model.BluetoothLeDevice; 17 | import com.vise.baseble.model.BluetoothLeDeviceStore; 18 | import com.vise.bledemo.R; 19 | import com.vise.bledemo.adapter.DeviceAdapter; 20 | import com.vise.log.ViseLog; 21 | 22 | import java.util.ArrayList; 23 | 24 | /** 25 | * 设备扫描展示界面 26 | */ 27 | public class DeviceScanActivity extends AppCompatActivity { 28 | 29 | private static final int MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 100; 30 | 31 | private ListView deviceLv; 32 | private TextView scanCountTv; 33 | 34 | //设备扫描结果展示适配器 35 | private DeviceAdapter adapter; 36 | 37 | private BluetoothLeDeviceStore bluetoothLeDeviceStore = new BluetoothLeDeviceStore(); 38 | 39 | /** 40 | * 扫描回调 41 | */ 42 | private ScanCallback periodScanCallback = new ScanCallback(new IScanCallback() { 43 | @Override 44 | public void onDeviceFound(final BluetoothLeDevice bluetoothLeDevice) { 45 | ViseLog.i("Founded Scan Device:" + bluetoothLeDevice); 46 | bluetoothLeDeviceStore.addDevice(bluetoothLeDevice); 47 | runOnUiThread(new Runnable() { 48 | @Override 49 | public void run() { 50 | if (adapter != null && bluetoothLeDeviceStore != null) { 51 | adapter.setListAll(bluetoothLeDeviceStore.getDeviceList()); 52 | updateItemCount(adapter.getCount()); 53 | } 54 | } 55 | }); 56 | } 57 | 58 | @Override 59 | public void onScanFinish(BluetoothLeDeviceStore bluetoothLeDeviceStore) { 60 | ViseLog.i("scan finish " + bluetoothLeDeviceStore); 61 | } 62 | 63 | @Override 64 | public void onScanTimeout() { 65 | ViseLog.i("scan timeout"); 66 | } 67 | 68 | }); 69 | 70 | @Override 71 | protected void onCreate(Bundle savedInstanceState) { 72 | super.onCreate(savedInstanceState); 73 | setContentView(R.layout.activity_device_scan); 74 | init(); 75 | } 76 | 77 | private void init() { 78 | deviceLv = (ListView) findViewById(android.R.id.list); 79 | scanCountTv = (TextView) findViewById(R.id.scan_device_count); 80 | 81 | adapter = new DeviceAdapter(this); 82 | deviceLv.setAdapter(adapter); 83 | 84 | deviceLv.setOnItemClickListener(new AdapterView.OnItemClickListener() { 85 | @Override 86 | public void onItemClick(AdapterView adapterView, View view, int position, long l) { 87 | //点击某个扫描到的设备进入设备详细信息界面 88 | BluetoothLeDevice device = (BluetoothLeDevice) adapter.getItem(position); 89 | if (device == null) return; 90 | Intent intent = new Intent(DeviceScanActivity.this, DeviceDetailActivity.class); 91 | intent.putExtra(DeviceDetailActivity.EXTRA_DEVICE, device); 92 | startActivity(intent); 93 | } 94 | }); 95 | } 96 | 97 | @Override 98 | protected void onResume() { 99 | super.onResume(); 100 | startScan(); 101 | invalidateOptionsMenu(); 102 | } 103 | 104 | @Override 105 | protected void onPause() { 106 | super.onPause(); 107 | stopScan(); 108 | invalidateOptionsMenu(); 109 | bluetoothLeDeviceStore.clear(); 110 | } 111 | 112 | @Override 113 | protected void onDestroy() { 114 | super.onDestroy(); 115 | } 116 | 117 | /** 118 | * 菜单栏的显示 119 | * 120 | * @param menu 菜单 121 | * @return 返回是否拦截操作 122 | */ 123 | @Override 124 | public boolean onCreateOptionsMenu(final Menu menu) { 125 | getMenuInflater().inflate(R.menu.scan, menu); 126 | if (periodScanCallback != null && !periodScanCallback.isScanning()) { 127 | menu.findItem(R.id.menu_stop).setVisible(false); 128 | menu.findItem(R.id.menu_scan).setVisible(true); 129 | menu.findItem(R.id.menu_refresh).setActionView(null); 130 | } else { 131 | menu.findItem(R.id.menu_stop).setVisible(true); 132 | menu.findItem(R.id.menu_scan).setVisible(false); 133 | menu.findItem(R.id.menu_refresh).setActionView(R.layout.actionbar_progress_indeterminate); 134 | } 135 | return true; 136 | } 137 | 138 | /** 139 | * 点击菜单栏的处理 140 | * 141 | * @param item 142 | * @return 143 | */ 144 | @Override 145 | public boolean onOptionsItemSelected(final MenuItem item) { 146 | switch (item.getItemId()) { 147 | case R.id.menu_scan://开始扫描 148 | startScan(); 149 | break; 150 | case R.id.menu_stop://停止扫描 151 | stopScan(); 152 | break; 153 | } 154 | return true; 155 | } 156 | 157 | /** 158 | * 开始扫描 159 | */ 160 | private void startScan() { 161 | updateItemCount(0); 162 | if (adapter != null) { 163 | adapter.setListAll(new ArrayList()); 164 | } 165 | ViseBle.getInstance().startScan(periodScanCallback); 166 | invalidateOptionsMenu(); 167 | } 168 | 169 | /** 170 | * 停止扫描 171 | */ 172 | private void stopScan() { 173 | ViseBle.getInstance().stopScan(periodScanCallback); 174 | invalidateOptionsMenu(); 175 | } 176 | 177 | /** 178 | * 更新扫描到的设备个数 179 | * 180 | * @param count 181 | */ 182 | private void updateItemCount(final int count) { 183 | scanCountTv.setText(getString(R.string.formatter_item_count, String.valueOf(count))); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.activity; 2 | 3 | import android.Manifest; 4 | import android.app.AlertDialog; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.support.annotation.Nullable; 10 | import android.support.design.widget.FloatingActionButton; 11 | import android.support.v4.content.ContextCompat; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.text.SpannableString; 14 | import android.text.method.LinkMovementMethod; 15 | import android.text.util.Linkify; 16 | import android.view.Menu; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.widget.AdapterView; 20 | import android.widget.ListView; 21 | import android.widget.TextView; 22 | 23 | import com.vise.baseble.ViseBle; 24 | import com.vise.baseble.model.BluetoothLeDevice; 25 | import com.vise.baseble.utils.BleUtil; 26 | import com.vise.bledemo.R; 27 | import com.vise.bledemo.adapter.DeviceMainAdapter; 28 | import com.vise.bledemo.common.BluetoothDeviceManager; 29 | import com.vise.bledemo.common.ToastUtil; 30 | import com.vise.bledemo.event.ConnectEvent; 31 | import com.vise.bledemo.event.NotifyDataEvent; 32 | import com.vise.log.ViseLog; 33 | import com.vise.log.inner.LogcatTree; 34 | import com.vise.xsnow.event.BusManager; 35 | import com.vise.xsnow.event.Subscribe; 36 | import com.vise.xsnow.permission.OnPermissionCallback; 37 | import com.vise.xsnow.permission.PermissionManager; 38 | 39 | import java.util.List; 40 | 41 | /** 42 | * @Description: 主页,展示已连接设备列表 43 | * @author: DAWI 44 | * @date: 2017/10/20 17:35 45 | */ 46 | public class MainActivity extends AppCompatActivity { 47 | 48 | private TextView supportTv; 49 | private TextView statusTv; 50 | private ListView deviceLv; 51 | private TextView emptyTv; 52 | private TextView countTv; 53 | 54 | private DeviceMainAdapter adapter; 55 | 56 | @Override 57 | protected void onCreate(@Nullable Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | setContentView(R.layout.activity_main); 60 | ViseLog.getLogConfig().configAllowLog(true);//配置日志信息 61 | ViseLog.plant(new LogcatTree());//添加Logcat打印信息 62 | BluetoothDeviceManager.getInstance().init(this); 63 | BusManager.getBus().register(this); 64 | init(); 65 | } 66 | 67 | private void init() { 68 | supportTv = (TextView) findViewById(R.id.main_ble_support); 69 | statusTv = (TextView) findViewById(R.id.main_ble_status); 70 | deviceLv = (ListView) findViewById(android.R.id.list); 71 | emptyTv = (TextView) findViewById(android.R.id.empty); 72 | countTv = (TextView) findViewById(R.id.connected_device_count); 73 | 74 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 75 | fab.setOnClickListener(new View.OnClickListener() { 76 | @Override 77 | public void onClick(View view) { 78 | Intent intent = new Intent(MainActivity.this, DeviceScanActivity.class); 79 | startActivity(intent); 80 | } 81 | }); 82 | 83 | adapter = new DeviceMainAdapter(this); 84 | deviceLv.setAdapter(adapter); 85 | 86 | deviceLv.setOnItemClickListener(new AdapterView.OnItemClickListener() { 87 | @Override 88 | public void onItemClick(AdapterView adapterView, View view, int position, long l) { 89 | BluetoothLeDevice device = (BluetoothLeDevice) adapter.getItem(position); 90 | if (device == null) return; 91 | Intent intent = new Intent(MainActivity.this, DeviceControlActivity.class); 92 | intent.putExtra(DeviceDetailActivity.EXTRA_DEVICE, device); 93 | startActivity(intent); 94 | } 95 | }); 96 | } 97 | 98 | @Subscribe 99 | public void showConnectedDevice(ConnectEvent event) { 100 | if (event != null) { 101 | updateConnectedDevice(); 102 | if (event.isDisconnected()) { 103 | ToastUtil.showToast(MainActivity.this, "Disconnect!"); 104 | } 105 | } 106 | } 107 | 108 | @Subscribe 109 | public void showDeviceNotifyData(NotifyDataEvent event) { 110 | if (event != null && adapter != null) { 111 | adapter.setNotifyData(event.getBluetoothLeDevice(), event.getData()); 112 | } 113 | } 114 | 115 | @Override 116 | protected void onResume() { 117 | super.onResume(); 118 | checkBluetoothPermission(); 119 | } 120 | 121 | @Override 122 | protected void onDestroy() { 123 | ViseBle.getInstance().clear(); 124 | BusManager.getBus().unregister(this); 125 | super.onDestroy(); 126 | } 127 | 128 | /** 129 | * 菜单栏的显示 130 | * 131 | * @param menu 菜单 132 | * @return 返回是否拦截操作 133 | */ 134 | @Override 135 | public boolean onCreateOptionsMenu(final Menu menu) { 136 | getMenuInflater().inflate(R.menu.about, menu); 137 | return true; 138 | } 139 | 140 | /** 141 | * 点击菜单栏的处理 142 | * 143 | * @param item 144 | * @return 145 | */ 146 | @Override 147 | public boolean onOptionsItemSelected(final MenuItem item) { 148 | switch (item.getItemId()) { 149 | case R.id.menu_about://关于 150 | displayAboutDialog(); 151 | break; 152 | } 153 | return true; 154 | } 155 | 156 | /** 157 | * 打开或关闭蓝牙后的回调 158 | * 159 | * @param requestCode 160 | * @param resultCode 161 | * @param data 162 | */ 163 | @Override 164 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 165 | if (resultCode == RESULT_OK) { 166 | if (requestCode == 1) { 167 | statusTv.setText(getString(R.string.on)); 168 | enableBluetooth(); 169 | } 170 | } else if (resultCode == RESULT_CANCELED) { 171 | finish(); 172 | } 173 | super.onActivityResult(requestCode, resultCode, data); 174 | } 175 | 176 | /** 177 | * 检查蓝牙权限 178 | */ 179 | private void checkBluetoothPermission() { 180 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 181 | //校验是否已具有模糊定位权限 182 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 183 | PermissionManager.instance().with(this).request(new OnPermissionCallback() { 184 | @Override 185 | public void onRequestAllow(String permissionName) { 186 | enableBluetooth(); 187 | } 188 | 189 | @Override 190 | public void onRequestRefuse(String permissionName) { 191 | finish(); 192 | } 193 | 194 | @Override 195 | public void onRequestNoAsk(String permissionName) { 196 | finish(); 197 | } 198 | }, Manifest.permission.ACCESS_COARSE_LOCATION); 199 | } else { 200 | enableBluetooth(); 201 | } 202 | } else { 203 | enableBluetooth(); 204 | } 205 | } 206 | 207 | private void enableBluetooth() { 208 | if (!BleUtil.isBleEnable(this)) { 209 | BleUtil.enableBluetooth(this, 1); 210 | } else { 211 | boolean isSupport = BleUtil.isSupportBle(this); 212 | boolean isOpenBle = BleUtil.isBleEnable(this); 213 | if (isSupport) { 214 | supportTv.setText(getString(R.string.supported)); 215 | } else { 216 | supportTv.setText(getString(R.string.not_supported)); 217 | } 218 | if (isOpenBle) { 219 | statusTv.setText(getString(R.string.on)); 220 | } else { 221 | statusTv.setText(getString(R.string.off)); 222 | } 223 | invalidateOptionsMenu(); 224 | updateConnectedDevice(); 225 | } 226 | } 227 | 228 | /** 229 | * 更新已经连接到的设备 230 | */ 231 | private void updateConnectedDevice() { 232 | if (adapter != null && ViseBle.getInstance().getDeviceMirrorPool() != null) { 233 | List bluetoothLeDeviceList = ViseBle.getInstance().getDeviceMirrorPool().getDeviceList(); 234 | if (bluetoothLeDeviceList != null && bluetoothLeDeviceList.size() > 0) { 235 | deviceLv.setVisibility(View.VISIBLE); 236 | } else { 237 | deviceLv.setVisibility(View.GONE); 238 | } 239 | adapter.setListAll(bluetoothLeDeviceList); 240 | updateItemCount(adapter.getCount()); 241 | } else { 242 | deviceLv.setVisibility(View.GONE); 243 | } 244 | } 245 | 246 | /** 247 | * 更新已经连接的设备个数 248 | * 249 | * @param count 250 | */ 251 | private void updateItemCount(final int count) { 252 | countTv.setText(getString(R.string.formatter_item_count, String.valueOf(count))); 253 | } 254 | 255 | /** 256 | * 显示项目信息 257 | */ 258 | private void displayAboutDialog() { 259 | final int paddingSizeDp = 5; 260 | final float scale = getResources().getDisplayMetrics().density; 261 | final int dpAsPixels = (int) (paddingSizeDp * scale + 0.5f); 262 | 263 | final TextView textView = new TextView(this); 264 | final SpannableString text = new SpannableString(getString(R.string.about_dialog_text)); 265 | 266 | textView.setText(text); 267 | textView.setAutoLinkMask(RESULT_OK); 268 | textView.setMovementMethod(LinkMovementMethod.getInstance()); 269 | textView.setPadding(dpAsPixels, dpAsPixels, dpAsPixels, dpAsPixels); 270 | 271 | Linkify.addLinks(text, Linkify.ALL); 272 | new AlertDialog.Builder(this).setTitle(R.string.menu_about).setCancelable(false).setPositiveButton(android.R 273 | .string.ok, null) 274 | .setView(textView).show(); 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/adapter/DeviceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | import com.vise.baseble.model.BluetoothLeDevice; 7 | import com.vise.baseble.utils.HexUtil; 8 | import com.vise.bledemo.R; 9 | import com.vise.xsnow.ui.adapter.helper.HelperAdapter; 10 | import com.vise.xsnow.ui.adapter.helper.HelperViewHolder; 11 | 12 | public class DeviceAdapter extends HelperAdapter { 13 | 14 | public DeviceAdapter(Context context) { 15 | super(context, R.layout.item_scan_layout); 16 | } 17 | 18 | @Override 19 | public void HelpConvert(HelperViewHolder viewHolder, int position, BluetoothLeDevice bluetoothLeDevice) { 20 | TextView deviceNameTv = viewHolder.getView(R.id.device_name); 21 | TextView deviceMacTv = viewHolder.getView(R.id.device_mac); 22 | TextView deviceRssiTv = viewHolder.getView(R.id.device_rssi); 23 | TextView deviceScanRecordTv = viewHolder.getView(R.id.device_scanRecord); 24 | if (bluetoothLeDevice != null && bluetoothLeDevice.getDevice() != null) { 25 | String deviceName = bluetoothLeDevice.getDevice().getName(); 26 | if (deviceName != null && !deviceName.isEmpty()) { 27 | deviceNameTv.setText(deviceName); 28 | } else { 29 | deviceNameTv.setText(mContext.getString(R.string.unknown_device)); 30 | } 31 | deviceMacTv.setText(bluetoothLeDevice.getDevice().getAddress()); 32 | deviceRssiTv.setText(mContext.getString(R.string.label_rssi) + bluetoothLeDevice.getRssi() + "dB"); 33 | deviceScanRecordTv.setText(mContext.getString(R.string.header_scan_record) + ":" 34 | + HexUtil.encodeHexStr(bluetoothLeDevice.getScanRecord())); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/adapter/DeviceMainAdapter.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.adapter; 2 | 3 | import android.content.Context; 4 | import android.widget.TextView; 5 | 6 | import com.vise.baseble.model.BluetoothLeDevice; 7 | import com.vise.baseble.utils.HexUtil; 8 | import com.vise.bledemo.R; 9 | import com.vise.xsnow.ui.adapter.helper.HelperAdapter; 10 | import com.vise.xsnow.ui.adapter.helper.HelperViewHolder; 11 | 12 | public class DeviceMainAdapter extends HelperAdapter { 13 | 14 | private BluetoothLeDevice mBluetoothLeDevice; 15 | private byte[] mNotifyData; 16 | 17 | public DeviceMainAdapter(Context context) { 18 | super(context, R.layout.item_main_layout); 19 | } 20 | 21 | public DeviceMainAdapter setNotifyData(BluetoothLeDevice bluetoothLeDevice, byte[] notifyData) { 22 | this.mBluetoothLeDevice = bluetoothLeDevice; 23 | this.mNotifyData = notifyData; 24 | notifyDataSetChanged(); 25 | return this; 26 | } 27 | 28 | @Override 29 | public void HelpConvert(HelperViewHolder viewHolder, int position, BluetoothLeDevice bluetoothLeDevice) { 30 | TextView deviceNameTv = viewHolder.getView(R.id.device_name); 31 | TextView deviceMacTv = viewHolder.getView(R.id.device_mac); 32 | TextView deviceNotifyDataTv = viewHolder.getView(R.id.device_notify_data); 33 | if (bluetoothLeDevice != null && bluetoothLeDevice.getDevice() != null) { 34 | String deviceName = bluetoothLeDevice.getDevice().getName(); 35 | if (deviceName != null && !deviceName.isEmpty()) { 36 | deviceNameTv.setText(deviceName); 37 | } else { 38 | deviceNameTv.setText(mContext.getString(R.string.unknown_device)); 39 | } 40 | deviceMacTv.setText(bluetoothLeDevice.getDevice().getAddress()); 41 | if (mBluetoothLeDevice != null && mNotifyData != null && mNotifyData.length > 0 && bluetoothLeDevice != null 42 | && mBluetoothLeDevice.getAddress() != null && mBluetoothLeDevice.getAddress().equals(bluetoothLeDevice.getAddress())) { 43 | deviceNotifyDataTv.setText(mContext.getString(R.string.label_notify_data) + HexUtil.encodeHexStr(mNotifyData)); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/adapter/MergeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.adapter; 2 | 3 | import android.database.DataSetObserver; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.BaseAdapter; 7 | import android.widget.ListAdapter; 8 | import android.widget.SectionIndexer; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.Iterator; 13 | import java.util.List; 14 | 15 | public class MergeAdapter extends BaseAdapter implements SectionIndexer { 16 | protected MergeAdapter.PieceStateRoster pieces = new MergeAdapter.PieceStateRoster(); 17 | 18 | public MergeAdapter() { 19 | } 20 | 21 | public void addAdapter(ListAdapter adapter) { 22 | this.pieces.add(adapter); 23 | adapter.registerDataSetObserver(new MergeAdapter.CascadeDataSetObserver()); 24 | } 25 | 26 | public void addView(View view) { 27 | this.addView(view, false); 28 | } 29 | 30 | public void addView(View view, boolean enabled) { 31 | ArrayList list = new ArrayList(1); 32 | list.add(view); 33 | this.addViews(list, enabled); 34 | } 35 | 36 | public void addViews(List views) { 37 | this.addViews(views, false); 38 | } 39 | 40 | public void addViews(List views, boolean enabled) { 41 | if (enabled) { 42 | this.addAdapter(new MergeAdapter.EnabledSackAdapter(views)); 43 | } else { 44 | this.addAdapter(new SackOfViewsAdapter(views)); 45 | } 46 | 47 | } 48 | 49 | public Object getItem(int position) { 50 | int size; 51 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 52 | ListAdapter piece = (ListAdapter) i$.next(); 53 | size = piece.getCount(); 54 | if (position < size) { 55 | return piece.getItem(position); 56 | } 57 | } 58 | 59 | return null; 60 | } 61 | 62 | public ListAdapter getAdapter(int position) { 63 | int size; 64 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 65 | ListAdapter piece = (ListAdapter) i$.next(); 66 | size = piece.getCount(); 67 | if (position < size) { 68 | return piece; 69 | } 70 | } 71 | 72 | return null; 73 | } 74 | 75 | public int getCount() { 76 | int total = 0; 77 | 78 | ListAdapter piece; 79 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); total += piece.getCount()) { 80 | piece = (ListAdapter) i$.next(); 81 | } 82 | 83 | return total; 84 | } 85 | 86 | public int getViewTypeCount() { 87 | int total = 0; 88 | 89 | MergeAdapter.PieceState piece; 90 | for (Iterator i$ = this.pieces.getRawPieces().iterator(); i$.hasNext(); total += piece.adapter.getViewTypeCount()) { 91 | piece = (MergeAdapter.PieceState) i$.next(); 92 | } 93 | 94 | return Math.max(total, 1); 95 | } 96 | 97 | public int getItemViewType(int position) { 98 | int typeOffset = 0; 99 | int result = -1; 100 | 101 | MergeAdapter.PieceState piece; 102 | for (Iterator i$ = this.pieces.getRawPieces().iterator(); i$.hasNext(); typeOffset += piece.adapter.getViewTypeCount()) { 103 | piece = (MergeAdapter.PieceState) i$.next(); 104 | if (piece.isActive) { 105 | int size = piece.adapter.getCount(); 106 | if (position < size) { 107 | result = typeOffset + piece.adapter.getItemViewType(position); 108 | break; 109 | } 110 | 111 | position -= size; 112 | } 113 | } 114 | 115 | return result; 116 | } 117 | 118 | public boolean areAllItemsEnabled() { 119 | return false; 120 | } 121 | 122 | public boolean isEnabled(int position) { 123 | int size; 124 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 125 | ListAdapter piece = (ListAdapter) i$.next(); 126 | size = piece.getCount(); 127 | if (position < size) { 128 | return piece.isEnabled(position); 129 | } 130 | } 131 | 132 | return false; 133 | } 134 | 135 | public View getView(int position, View convertView, ViewGroup parent) { 136 | int size; 137 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 138 | ListAdapter piece = (ListAdapter) i$.next(); 139 | size = piece.getCount(); 140 | if (position < size) { 141 | return piece.getView(position, convertView, parent); 142 | } 143 | } 144 | 145 | return null; 146 | } 147 | 148 | public long getItemId(int position) { 149 | int size; 150 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 151 | ListAdapter piece = (ListAdapter) i$.next(); 152 | size = piece.getCount(); 153 | if (position < size) { 154 | return piece.getItemId(position); 155 | } 156 | } 157 | 158 | return -1L; 159 | } 160 | 161 | public int getPositionForSection(int section) { 162 | int position = 0; 163 | 164 | ListAdapter piece; 165 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position += piece.getCount()) { 166 | piece = (ListAdapter) i$.next(); 167 | if (piece instanceof SectionIndexer) { 168 | Object[] sections = ((SectionIndexer) piece).getSections(); 169 | int numSections = 0; 170 | if (sections != null) { 171 | numSections = sections.length; 172 | } 173 | 174 | if (section < numSections) { 175 | return position + ((SectionIndexer) piece).getPositionForSection(section); 176 | } 177 | 178 | if (sections != null) { 179 | section -= numSections; 180 | } 181 | } 182 | } 183 | 184 | return 0; 185 | } 186 | 187 | public int getSectionForPosition(int position) { 188 | int section = 0; 189 | 190 | int size; 191 | for (Iterator i$ = this.getPieces().iterator(); i$.hasNext(); position -= size) { 192 | ListAdapter piece = (ListAdapter) i$.next(); 193 | size = piece.getCount(); 194 | if (position < size) { 195 | if (piece instanceof SectionIndexer) { 196 | return section + ((SectionIndexer) piece).getSectionForPosition(position); 197 | } 198 | 199 | return 0; 200 | } 201 | 202 | if (piece instanceof SectionIndexer) { 203 | Object[] sections = ((SectionIndexer) piece).getSections(); 204 | if (sections != null) { 205 | section += sections.length; 206 | } 207 | } 208 | } 209 | 210 | return 0; 211 | } 212 | 213 | public Object[] getSections() { 214 | ArrayList sections = new ArrayList(); 215 | Iterator i$ = this.getPieces().iterator(); 216 | 217 | while (i$.hasNext()) { 218 | ListAdapter piece = (ListAdapter) i$.next(); 219 | if (piece instanceof SectionIndexer) { 220 | Object[] curSections = ((SectionIndexer) piece).getSections(); 221 | if (curSections != null) { 222 | Collections.addAll(sections, curSections); 223 | } 224 | } 225 | } 226 | 227 | if (sections.size() == 0) { 228 | return new String[0]; 229 | } else { 230 | return sections.toArray(new Object[sections.size()]); 231 | } 232 | } 233 | 234 | public void setActive(ListAdapter adapter, boolean isActive) { 235 | this.pieces.setActive(adapter, isActive); 236 | this.notifyDataSetChanged(); 237 | } 238 | 239 | public void setActive(View v, boolean isActive) { 240 | this.pieces.setActive(v, isActive); 241 | this.notifyDataSetChanged(); 242 | } 243 | 244 | protected List getPieces() { 245 | return this.pieces.getPieces(); 246 | } 247 | 248 | private class CascadeDataSetObserver extends DataSetObserver { 249 | private CascadeDataSetObserver() { 250 | } 251 | 252 | public void onChanged() { 253 | MergeAdapter.this.notifyDataSetChanged(); 254 | } 255 | 256 | public void onInvalidated() { 257 | MergeAdapter.this.notifyDataSetInvalidated(); 258 | } 259 | } 260 | 261 | private static class EnabledSackAdapter extends SackOfViewsAdapter { 262 | public EnabledSackAdapter(List views) { 263 | super(views); 264 | } 265 | 266 | public boolean areAllItemsEnabled() { 267 | return true; 268 | } 269 | 270 | public boolean isEnabled(int position) { 271 | return true; 272 | } 273 | } 274 | 275 | private static class PieceStateRoster { 276 | protected ArrayList pieces; 277 | protected ArrayList active; 278 | 279 | private PieceStateRoster() { 280 | this.pieces = new ArrayList(); 281 | this.active = null; 282 | } 283 | 284 | void add(ListAdapter adapter) { 285 | this.pieces.add(new MergeAdapter.PieceState(adapter, true)); 286 | } 287 | 288 | void setActive(ListAdapter adapter, boolean isActive) { 289 | Iterator i$ = this.pieces.iterator(); 290 | 291 | while (i$.hasNext()) { 292 | MergeAdapter.PieceState state = (MergeAdapter.PieceState) i$.next(); 293 | if (state.adapter == adapter) { 294 | state.isActive = isActive; 295 | this.active = null; 296 | break; 297 | } 298 | } 299 | 300 | } 301 | 302 | void setActive(View v, boolean isActive) { 303 | Iterator i$ = this.pieces.iterator(); 304 | 305 | while (i$.hasNext()) { 306 | MergeAdapter.PieceState state = (MergeAdapter.PieceState) i$.next(); 307 | if (state.adapter instanceof SackOfViewsAdapter && ((SackOfViewsAdapter) state.adapter).hasView(v)) { 308 | state.isActive = isActive; 309 | this.active = null; 310 | break; 311 | } 312 | } 313 | 314 | } 315 | 316 | List getRawPieces() { 317 | return this.pieces; 318 | } 319 | 320 | List getPieces() { 321 | if (this.active == null) { 322 | this.active = new ArrayList(); 323 | Iterator i$ = this.pieces.iterator(); 324 | 325 | while (i$.hasNext()) { 326 | MergeAdapter.PieceState state = (MergeAdapter.PieceState) i$.next(); 327 | if (state.isActive) { 328 | this.active.add(state.adapter); 329 | } 330 | } 331 | } 332 | 333 | return this.active; 334 | } 335 | } 336 | 337 | private static class PieceState { 338 | ListAdapter adapter; 339 | boolean isActive = true; 340 | 341 | PieceState(ListAdapter adapter, boolean isActive) { 342 | this.adapter = adapter; 343 | this.isActive = isActive; 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/adapter/SackOfViewsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.adapter; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.widget.BaseAdapter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class SackOfViewsAdapter extends BaseAdapter { 11 | private List views = null; 12 | 13 | public SackOfViewsAdapter(int count) { 14 | this.views = new ArrayList(count); 15 | 16 | for (int i = 0; i < count; ++i) { 17 | this.views.add(null); 18 | } 19 | 20 | } 21 | 22 | public SackOfViewsAdapter(List views) { 23 | this.views = views; 24 | } 25 | 26 | public Object getItem(int position) { 27 | return this.views.get(position); 28 | } 29 | 30 | public int getCount() { 31 | return this.views.size(); 32 | } 33 | 34 | public int getViewTypeCount() { 35 | return this.getCount(); 36 | } 37 | 38 | public int getItemViewType(int position) { 39 | return position; 40 | } 41 | 42 | public boolean areAllItemsEnabled() { 43 | return false; 44 | } 45 | 46 | public boolean isEnabled(int position) { 47 | return false; 48 | } 49 | 50 | public View getView(int position, View convertView, ViewGroup parent) { 51 | View result = this.views.get(position); 52 | if (result == null) { 53 | result = this.newView(position, parent); 54 | this.views.set(position, result); 55 | } 56 | 57 | return result; 58 | } 59 | 60 | public long getItemId(int position) { 61 | return (long) position; 62 | } 63 | 64 | public boolean hasView(View v) { 65 | return this.views.contains(v); 66 | } 67 | 68 | protected View newView(int position, ViewGroup parent) { 69 | throw new RuntimeException("You must override newView()!"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/common/BluetoothDeviceManager.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.common; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | 7 | import com.vise.baseble.ViseBle; 8 | import com.vise.baseble.callback.IBleCallback; 9 | import com.vise.baseble.callback.IConnectCallback; 10 | import com.vise.baseble.common.PropertyType; 11 | import com.vise.baseble.core.BluetoothGattChannel; 12 | import com.vise.baseble.core.DeviceMirror; 13 | import com.vise.baseble.core.DeviceMirrorPool; 14 | import com.vise.baseble.exception.BleException; 15 | import com.vise.baseble.model.BluetoothLeDevice; 16 | import com.vise.baseble.utils.HexUtil; 17 | import com.vise.bledemo.event.CallbackDataEvent; 18 | import com.vise.bledemo.event.ConnectEvent; 19 | import com.vise.bledemo.event.NotifyDataEvent; 20 | import com.vise.bledemo.event.ScanEvent; 21 | import com.vise.log.ViseLog; 22 | import com.vise.xsnow.event.BusManager; 23 | 24 | import java.util.LinkedList; 25 | import java.util.Queue; 26 | import java.util.UUID; 27 | 28 | /** 29 | * @Description: 蓝牙设备管理 30 | * @author: DAWI 31 | * @date: 2017/10/27 17:09 32 | */ 33 | public class BluetoothDeviceManager { 34 | 35 | private static BluetoothDeviceManager instance; 36 | private DeviceMirrorPool mDeviceMirrorPool; 37 | private ScanEvent scanEvent = new ScanEvent(); 38 | private ConnectEvent connectEvent = new ConnectEvent(); 39 | private CallbackDataEvent callbackDataEvent = new CallbackDataEvent(); 40 | private NotifyDataEvent notifyDataEvent = new NotifyDataEvent(); 41 | 42 | /** 43 | * 连接回调 44 | */ 45 | private IConnectCallback connectCallback = new IConnectCallback() { 46 | 47 | @Override 48 | public void onConnectSuccess(final DeviceMirror deviceMirror) { 49 | ViseLog.i("Connect Success!"); 50 | BusManager.getBus().post(connectEvent.setDeviceMirror(deviceMirror).setSuccess(true)); 51 | } 52 | 53 | @Override 54 | public void onConnectFailure(BleException exception) { 55 | ViseLog.i("Connect Failure!"); 56 | BusManager.getBus().post(connectEvent.setSuccess(false).setDisconnected(false)); 57 | } 58 | 59 | @Override 60 | public void onDisconnect(boolean isActive) { 61 | ViseLog.i("Disconnect!"); 62 | BusManager.getBus().post(connectEvent.setSuccess(false).setDisconnected(true)); 63 | } 64 | }; 65 | 66 | /** 67 | * 接收数据回调 68 | */ 69 | private IBleCallback receiveCallback = new IBleCallback() { 70 | @Override 71 | public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) { 72 | if (data == null) { 73 | return; 74 | } 75 | ViseLog.i("notify success:" + HexUtil.encodeHexStr(data)); 76 | BusManager.getBus().post(notifyDataEvent.setData(data) 77 | .setBluetoothLeDevice(bluetoothLeDevice) 78 | .setBluetoothGattChannel(bluetoothGattInfo)); 79 | } 80 | 81 | @Override 82 | public void onFailure(BleException exception) { 83 | if (exception == null) { 84 | return; 85 | } 86 | ViseLog.i("notify fail:" + exception.getDescription()); 87 | } 88 | }; 89 | 90 | /** 91 | * 操作数据回调 92 | */ 93 | private IBleCallback bleCallback = new IBleCallback() { 94 | @Override 95 | public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) { 96 | if (data == null) { 97 | return; 98 | } 99 | ViseLog.i("callback success:" + HexUtil.encodeHexStr(data)); 100 | BusManager.getBus().post(callbackDataEvent.setData(data).setSuccess(true) 101 | .setBluetoothLeDevice(bluetoothLeDevice) 102 | .setBluetoothGattChannel(bluetoothGattInfo)); 103 | if (bluetoothGattInfo != null && (bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_INDICATE 104 | || bluetoothGattInfo.getPropertyType() == PropertyType.PROPERTY_NOTIFY)) { 105 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 106 | if (deviceMirror != null) { 107 | deviceMirror.setNotifyListener(bluetoothGattInfo.getGattInfoKey(), receiveCallback); 108 | } 109 | } 110 | } 111 | 112 | @Override 113 | public void onFailure(BleException exception) { 114 | if (exception == null) { 115 | return; 116 | } 117 | ViseLog.i("callback fail:" + exception.getDescription()); 118 | BusManager.getBus().post(callbackDataEvent.setSuccess(false)); 119 | } 120 | }; 121 | 122 | private BluetoothDeviceManager() { 123 | 124 | } 125 | 126 | public static BluetoothDeviceManager getInstance() { 127 | if (instance == null) { 128 | synchronized (BluetoothDeviceManager.class) { 129 | if (instance == null) { 130 | instance = new BluetoothDeviceManager(); 131 | } 132 | } 133 | } 134 | return instance; 135 | } 136 | 137 | public void init(Context context) { 138 | if (context == null) { 139 | return; 140 | } 141 | //蓝牙相关配置修改 142 | ViseBle.config() 143 | .setScanTimeout(-1)//扫描超时时间,这里设置为永久扫描 144 | .setScanRepeatInterval(5 * 1000)//扫描间隔5秒 145 | .setConnectTimeout(10 * 1000)//连接超时时间 146 | .setOperateTimeout(5 * 1000)//设置数据操作超时时间 147 | .setConnectRetryCount(3)//设置连接失败重试次数 148 | .setConnectRetryInterval(1000)//设置连接失败重试间隔时间 149 | .setOperateRetryCount(3)//设置数据操作失败重试次数 150 | .setOperateRetryInterval(1000)//设置数据操作失败重试间隔时间 151 | .setMaxConnectCount(3);//设置最大连接设备数量 152 | //蓝牙信息初始化,全局唯一,必须在应用初始化时调用 153 | ViseBle.getInstance().init(context.getApplicationContext()); 154 | mDeviceMirrorPool = ViseBle.getInstance().getDeviceMirrorPool(); 155 | } 156 | 157 | public void connect(BluetoothLeDevice bluetoothLeDevice) { 158 | ViseBle.getInstance().connect(bluetoothLeDevice, connectCallback); 159 | } 160 | 161 | public void disconnect(BluetoothLeDevice bluetoothLeDevice) { 162 | ViseBle.getInstance().disconnect(bluetoothLeDevice); 163 | } 164 | 165 | public boolean isConnected(BluetoothLeDevice bluetoothLeDevice) { 166 | return ViseBle.getInstance().isConnect(bluetoothLeDevice); 167 | } 168 | 169 | public void bindChannel(BluetoothLeDevice bluetoothLeDevice, PropertyType propertyType, UUID serviceUUID, 170 | UUID characteristicUUID, UUID descriptorUUID) { 171 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 172 | if (deviceMirror != null) { 173 | BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder() 174 | .setBluetoothGatt(deviceMirror.getBluetoothGatt()) 175 | .setPropertyType(propertyType) 176 | .setServiceUUID(serviceUUID) 177 | .setCharacteristicUUID(characteristicUUID) 178 | .setDescriptorUUID(descriptorUUID) 179 | .builder(); 180 | deviceMirror.bindChannel(bleCallback, bluetoothGattChannel); 181 | } 182 | } 183 | 184 | public void write(final BluetoothLeDevice bluetoothLeDevice, byte[] data) { 185 | if (dataInfoQueue != null) { 186 | dataInfoQueue.clear(); 187 | dataInfoQueue = splitPacketFor20Byte(data); 188 | new Handler(Looper.getMainLooper()).post(new Runnable() { 189 | @Override 190 | public void run() { 191 | send(bluetoothLeDevice); 192 | } 193 | }); 194 | } 195 | } 196 | 197 | public void read(BluetoothLeDevice bluetoothLeDevice) { 198 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 199 | if (deviceMirror != null) { 200 | deviceMirror.readData(); 201 | } 202 | } 203 | 204 | public void registerNotify(BluetoothLeDevice bluetoothLeDevice, boolean isIndicate) { 205 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 206 | if (deviceMirror != null) { 207 | deviceMirror.registerNotify(isIndicate); 208 | } 209 | } 210 | 211 | //发送队列,提供一种简单的处理方式,实际项目场景需要根据需求优化 212 | private Queue dataInfoQueue = new LinkedList<>(); 213 | private void send(final BluetoothLeDevice bluetoothLeDevice) { 214 | if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) { 215 | DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice); 216 | if (dataInfoQueue.peek() != null && deviceMirror != null) { 217 | deviceMirror.writeData(dataInfoQueue.poll()); 218 | } 219 | if (dataInfoQueue.peek() != null) { 220 | new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { 221 | @Override 222 | public void run() { 223 | send(bluetoothLeDevice); 224 | } 225 | }, 100); 226 | } 227 | } 228 | } 229 | 230 | /** 231 | * 数据分包 232 | * 233 | * @param data 234 | * @return 235 | */ 236 | private Queue splitPacketFor20Byte(byte[] data) { 237 | Queue dataInfoQueue = new LinkedList<>(); 238 | if (data != null) { 239 | int index = 0; 240 | do { 241 | byte[] surplusData = new byte[data.length - index]; 242 | byte[] currentData; 243 | System.arraycopy(data, index, surplusData, 0, data.length - index); 244 | if (surplusData.length <= 20) { 245 | currentData = new byte[surplusData.length]; 246 | System.arraycopy(surplusData, 0, currentData, 0, surplusData.length); 247 | index += surplusData.length; 248 | } else { 249 | currentData = new byte[20]; 250 | System.arraycopy(data, index, currentData, 0, 20); 251 | index += 20; 252 | } 253 | dataInfoQueue.offer(currentData); 254 | } while (index < data.length); 255 | } 256 | return dataInfoQueue; 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/common/ToastUtil.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.common; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.os.Message; 8 | import android.view.Gravity; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import com.vise.bledemo.R; 15 | import com.vise.utils.view.ViewUtil; 16 | 17 | public class ToastUtil { 18 | private static final long DROP_DUPLICATE_TOAST_TS = 2 * 1000; // 2s 19 | 20 | private static String sLast = ""; 21 | 22 | private static long sLastTs = 0; 23 | 24 | private static Toast mBasicToast = null; 25 | 26 | public enum CommonToastType { 27 | TOAST_TYPE_NORMAL, // 普通toast-笑脸 28 | TOAST_TYPE_SUC, // 成功toast-笑脸 29 | TOAST_TYPE_ACC, // 加速toast-笑脸 30 | TOAST_TYPE_SMILE, // 微笑toast-笑脸 31 | TOAST_TYPE_ALARM// 失败or警告toast-哭脸 32 | } 33 | 34 | /** 35 | * 上下文. 36 | */ 37 | private static Context mContext = null; 38 | 39 | /** 40 | * 显示Toast. 41 | */ 42 | public static final int SHOW_TOAST = 0; 43 | /** 44 | * 主要Handler类,在线程中可用 45 | * what:0.提示文本信息 46 | */ 47 | private static Handler baseHandler = new Handler(Looper.getMainLooper()) { 48 | 49 | @Override 50 | public void handleMessage(Message msg) { 51 | switch (msg.what) { 52 | case SHOW_TOAST: 53 | showToast(mContext, msg.getData().getString("TEXT")); 54 | break; 55 | default: 56 | break; 57 | } 58 | } 59 | }; 60 | 61 | 62 | public static void showToast(Context context, int strRes) { 63 | showToast(context, context.getResources().getString(strRes)); 64 | } 65 | 66 | public static void showShortToast(Context context, String prompt) { 67 | showToast(context, prompt); 68 | 69 | } 70 | 71 | public static void showLongToast(Context context, String prompt) { 72 | showToast(context, prompt); 73 | } 74 | 75 | public static Toast makeText(Context context, CharSequence text, int duration) { 76 | mContext = context.getApplicationContext(); 77 | Toast result = new Toast(context); 78 | 79 | LayoutInflater inflate = (LayoutInflater) context 80 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 81 | View v = inflate.inflate(R.layout.common_layout_toast, null); 82 | TextView tv = (TextView) v.findViewById(R.id.tvToast); 83 | tv.setText(text); 84 | 85 | result.setView(v); 86 | result.setGravity(Gravity.BOTTOM, 0, 163); 87 | result.setDuration(duration); 88 | 89 | return result; 90 | } 91 | 92 | public static synchronized void showToast(Context context, String str) { 93 | showToast(context, CommonToastType.TOAST_TYPE_NORMAL, str); 94 | } 95 | 96 | /** 97 | * toast(带动画)-显示时间2S 98 | * 99 | * @param context 100 | * @param type 101 | * @param str 102 | */ 103 | public static synchronized void showToast(Context context, CommonToastType type, String str) { 104 | try { 105 | mContext = context.getApplicationContext(); 106 | long newTs = System.currentTimeMillis(); 107 | if (str != null 108 | && (!str.equals(sLast) || newTs < sLastTs || (newTs - sLastTs) > DROP_DUPLICATE_TOAST_TS)) { 109 | sLast = str; 110 | sLastTs = newTs; 111 | if (mBasicToast == null) { 112 | mBasicToast = new Toast(context); 113 | } 114 | View toastView = LayoutInflater.from(context).inflate( 115 | R.layout.common_layout_toast, null); 116 | TextView txt = (TextView) toastView.findViewById(R.id.tvToast); 117 | txt.setText(str); 118 | 119 | mBasicToast.setView(toastView); 120 | int px = (int) ViewUtil.dip2px(context, 60); 121 | mBasicToast.setGravity(Gravity.BOTTOM, 0, px); 122 | mBasicToast.setDuration(Toast.LENGTH_SHORT);// 默认只显示2S 123 | mBasicToast.show(); 124 | } 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | 130 | /** 131 | * 描述:在线程中提示文本信息. 132 | * 133 | * @param resId 要提示的字符串资源ID,消息what值为0, 134 | */ 135 | public static void showToastInThread(Context context, int resId) { 136 | mContext = context.getApplicationContext(); 137 | Message msg = baseHandler.obtainMessage(SHOW_TOAST); 138 | Bundle bundle = new Bundle(); 139 | bundle.putString("TEXT", context.getResources().getString(resId)); 140 | msg.setData(bundle); 141 | baseHandler.sendMessage(msg); 142 | } 143 | 144 | /** 145 | * 描述:在线程中提示文本信息. 146 | * 147 | * @param context 148 | * @param text 149 | */ 150 | public static void showToastInThread(Context context, String text) { 151 | mContext = context.getApplicationContext(); 152 | Message msg = baseHandler.obtainMessage(SHOW_TOAST); 153 | Bundle bundle = new Bundle(); 154 | bundle.putString("TEXT", text); 155 | msg.setData(bundle); 156 | baseHandler.sendMessage(msg); 157 | } 158 | 159 | /** 160 | * 让Toast立马消失 161 | */ 162 | public static void cancelToast() { 163 | if (mBasicToast != null) { 164 | mBasicToast.cancel(); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/event/CallbackDataEvent.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.event; 2 | 3 | import com.vise.baseble.core.BluetoothGattChannel; 4 | import com.vise.baseble.model.BluetoothLeDevice; 5 | import com.vise.xsnow.event.IEvent; 6 | 7 | public class CallbackDataEvent implements IEvent { 8 | private byte[] data; 9 | private boolean isSuccess; 10 | private BluetoothLeDevice bluetoothLeDevice; 11 | private BluetoothGattChannel bluetoothGattChannel; 12 | 13 | public CallbackDataEvent setSuccess(boolean success) { 14 | isSuccess = success; 15 | return this; 16 | } 17 | 18 | public byte[] getData() { 19 | return data; 20 | } 21 | 22 | public CallbackDataEvent setData(byte[] data) { 23 | this.data = data; 24 | return this; 25 | } 26 | 27 | public boolean isSuccess() { 28 | return isSuccess; 29 | } 30 | 31 | public BluetoothLeDevice getBluetoothLeDevice() { 32 | return bluetoothLeDevice; 33 | } 34 | 35 | public CallbackDataEvent setBluetoothLeDevice(BluetoothLeDevice bluetoothLeDevice) { 36 | this.bluetoothLeDevice = bluetoothLeDevice; 37 | return this; 38 | } 39 | 40 | public BluetoothGattChannel getBluetoothGattChannel() { 41 | return bluetoothGattChannel; 42 | } 43 | 44 | public CallbackDataEvent setBluetoothGattChannel(BluetoothGattChannel bluetoothGattChannel) { 45 | this.bluetoothGattChannel = bluetoothGattChannel; 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/event/ConnectEvent.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.event; 2 | 3 | import com.vise.baseble.core.DeviceMirror; 4 | import com.vise.xsnow.event.IEvent; 5 | 6 | public class ConnectEvent implements IEvent { 7 | private boolean isSuccess; 8 | private boolean isDisconnected; 9 | private DeviceMirror deviceMirror; 10 | 11 | public boolean isSuccess() { 12 | return isSuccess; 13 | } 14 | 15 | public ConnectEvent setSuccess(boolean success) { 16 | isSuccess = success; 17 | return this; 18 | } 19 | 20 | public boolean isDisconnected() { 21 | return isDisconnected; 22 | } 23 | 24 | public ConnectEvent setDisconnected(boolean disconnected) { 25 | isDisconnected = disconnected; 26 | return this; 27 | } 28 | 29 | public DeviceMirror getDeviceMirror() { 30 | return deviceMirror; 31 | } 32 | 33 | public ConnectEvent setDeviceMirror(DeviceMirror deviceMirror) { 34 | this.deviceMirror = deviceMirror; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/event/NotifyDataEvent.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.event; 2 | 3 | import com.vise.baseble.core.BluetoothGattChannel; 4 | import com.vise.baseble.model.BluetoothLeDevice; 5 | import com.vise.xsnow.event.IEvent; 6 | 7 | public class NotifyDataEvent implements IEvent { 8 | private byte[] data; 9 | private BluetoothLeDevice bluetoothLeDevice; 10 | private BluetoothGattChannel bluetoothGattChannel; 11 | 12 | public byte[] getData() { 13 | return data; 14 | } 15 | 16 | public NotifyDataEvent setData(byte[] data) { 17 | this.data = data; 18 | return this; 19 | } 20 | 21 | public BluetoothLeDevice getBluetoothLeDevice() { 22 | return bluetoothLeDevice; 23 | } 24 | 25 | public NotifyDataEvent setBluetoothLeDevice(BluetoothLeDevice bluetoothLeDevice) { 26 | this.bluetoothLeDevice = bluetoothLeDevice; 27 | return this; 28 | } 29 | 30 | public BluetoothGattChannel getBluetoothGattChannel() { 31 | return bluetoothGattChannel; 32 | } 33 | 34 | public NotifyDataEvent setBluetoothGattChannel(BluetoothGattChannel bluetoothGattChannel) { 35 | this.bluetoothGattChannel = bluetoothGattChannel; 36 | return this; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /newapp/src/main/java/com/vise/bledemo/event/ScanEvent.java: -------------------------------------------------------------------------------- 1 | package com.vise.bledemo.event; 2 | 3 | import com.vise.baseble.model.BluetoothLeDeviceStore; 4 | import com.vise.xsnow.event.IEvent; 5 | 6 | public class ScanEvent implements IEvent { 7 | private boolean isScanTimeout; 8 | private boolean isScanFinish; 9 | private BluetoothLeDeviceStore bluetoothLeDeviceStore; 10 | 11 | public ScanEvent() { 12 | } 13 | 14 | public boolean isScanTimeout() { 15 | return isScanTimeout; 16 | } 17 | 18 | public ScanEvent setScanTimeout(boolean scanTimeout) { 19 | isScanTimeout = scanTimeout; 20 | return this; 21 | } 22 | 23 | public boolean isScanFinish() { 24 | return isScanFinish; 25 | } 26 | 27 | public ScanEvent setScanFinish(boolean scanFinish) { 28 | isScanFinish = scanFinish; 29 | return this; 30 | } 31 | 32 | public BluetoothLeDeviceStore getBluetoothLeDeviceStore() { 33 | return bluetoothLeDeviceStore; 34 | } 35 | 36 | public ScanEvent setBluetoothLeDeviceStore(BluetoothLeDeviceStore bluetoothLeDeviceStore) { 37 | this.bluetoothLeDeviceStore = bluetoothLeDeviceStore; 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /newapp/src/main/res/drawable/toast_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /newapp/src/main/res/layout/actionbar_progress_indeterminate.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /newapp/src/main/res/layout/activity_device_control.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 27 | 28 | 31 | 32 | 37 | 38 | 41 | 42 | 43 | 47 | 48 | 53 | 54 |