├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── Administrator.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── blelib ├── .gitignore ├── LICENSE ├── bintray.gradle ├── build.gradle ├── install.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── junkchen │ │ └── blelib │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── junkchen │ │ │ └── blelib │ │ │ ├── BleListener.java │ │ │ ├── BleService.java │ │ │ ├── Constants.java │ │ │ ├── GattAttributes.java │ │ │ └── MultipleBleService.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── junkchen │ └── blelib │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── screenshots │ ├── connect_multiple_devices.png │ └── connect_single_device.png └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── junkchen │ │ └── blelib │ │ └── sample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── junkchen │ │ │ └── blelib │ │ │ └── sample │ │ │ ├── BleScanActivity.java │ │ │ ├── CharacteristicActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── MultipleBleActivity.java │ │ │ ├── MyGattAttributes.java │ │ │ └── adapter │ │ │ ├── CommonAdapter.java │ │ │ └── ViewHolder.java │ └── res │ │ ├── layout │ │ ├── activity_ble_scan.xml │ │ ├── activity_characteristic.xml │ │ ├── activity_main.xml │ │ └── item_device.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_bluetooth_black_36dp.png │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── junkchen │ └── blelib │ └── sample │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/Administrator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **BleLib** # 2 | 3 | BleLib 是 Android 低功耗蓝牙 4.0 及以上开发的辅助库,一行代码解决 Ble 初始化、扫描、连接、特性读写、设置通知等操作。 4 | 5 | BleLib 中的关键类: 6 | 7 | - BleService 是单个 Ble 连接操作的服务类 8 | - GattAttributes 类中包含了蓝牙联盟规定的服务和特征的 UUID 值 9 | - MultipleBleService 类是可多个蓝牙设备同时连接的服务类 10 | 11 | 12 | ## **Screenshots** ## 13 | 14 | ![](sample/screenshots/connect_single_device.png) ![](sample/screenshots/connect_multiple_devices.png) 15 | 16 | 17 | ## **Usage** ## 18 | 19 | 可看博客: [使用 BleLib 的轻松搞定 Android 低功耗蓝牙 Ble 4.0 开发详解](http://blog.csdn.net/kjunchen/article/details/50909410) 20 | 21 | ## **引入** ## 22 | BleLib 库已上传至 jcenter、maven central 仓库 23 | 因此,在你项目 Module 中的 build.gradle 文件中添加库依赖即可,如下: 24 | Gradle: 25 | 26 | ```.gradle 27 | dependencies { 28 | compile 'com.junkchen.blelib:blelib:1.2.4' 29 | } 30 | ``` 31 | 32 | 只此一句即可使用 BleLib 库,方便吧,要的就是这效果。 33 | 34 | BleLib 中的 Ble 继承了 Service,因此建议绑定服务进行使用。 35 | 36 | ```.java 37 | private BleService mBleService; 38 | private boolean mIsBind; 39 | private ServiceConnection serviceConnection = new ServiceConnection() { 40 | @Override 41 | public void onServiceConnected(ComponentName name, IBinder service) { 42 | mBleService = ((BleService.LocalBinder) service).getService(); 43 | if (mBleService != null) mHandler.sendEmptyMessage(SERVICE_BIND); 44 | if (mBleService.initialize()) { 45 | if (mBleService.enableBluetooth(true)) { 46 | mBleService.scanLeDevice(true); 47 | Toast.makeText(BleScanActivity.this, "Bluetooth was opened", Toast.LENGTH_SHORT).show(); 48 | } 49 | } else { 50 | Toast.makeText(BleScanActivity.this, "not support Bluetooth", Toast.LENGTH_SHORT).show(); 51 | } 52 | } 53 | 54 | @Override 55 | public void onServiceDisconnected(ComponentName name) { 56 | mBleService = null; 57 | mIsBind = false; 58 | } 59 | }; 60 | 61 | 62 | private void doBindService() { 63 | Intent serviceIntent = new Intent(this, BleService.class); 64 | bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); 65 | } 66 | 67 | private void doUnBindService() { 68 | if (mIsBind) { 69 | unbindService(serviceConnection); 70 | mBleService = null; 71 | mIsBind = false; 72 | } 73 | } 74 | ``` 75 | 76 | 当服务绑定后就可以进行如下操作了: 77 | 78 | ```.java 79 | mBleService.initialize();//Ble初始化操作 80 | mBleService.enableBluetooth(boolean enable);//打开或关闭蓝牙 81 | mBleService.scanLeDevice(boolean enable, long scanPeriod);//启动或停止扫描Ble设备 82 | mBleService.connect(String address);//连接Ble 83 | mBleService.disconnect();//取消连接 84 | mBleService.getSupportedGattServices();//获取服务 85 | mBleService.setCharacteristicNotification(BluetoothGattCharacteristic characteristic, 86 | boolean enabled);//设置通知 87 | mBleService.readCharacteristic(BluetoothGattCharacteristic characteristic);//读取数据 88 | mBleService.writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value);//写入数据 89 | ``` 90 | 91 | 设置监听回调接口,获取相应返回数据,获取扫描Ble结果、连接等操作也可以以接收广播的方式获取,但我个人觉得用监听的方式更好,广播有的值无法传递,而接口传递过来的是原始数据,在我的样例中有使用广播来接收扫描的结果和连接状态的改变。 92 | 93 | ```.java 94 | //Ble扫描回调 95 | mBleService.setOnLeScanListener(new BleService.OnLeScanListener() { 96 | @Override 97 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 98 | //每当扫描到一个Ble设备时就会返回,(扫描结果重复的库中已处理) 99 | } 100 | }); 101 | //Ble连接回调 102 | mBleService.setOnConnectListener(new BleService.OnConnectListener() { 103 | @Override 104 | public void onConnect(BluetoothGatt gatt, int status, int newState) { 105 | if (newState == BluetoothProfile.STATE_DISCONNECTED) { 106 | //Ble连接已断开 107 | } else if (newState == BluetoothProfile.STATE_CONNECTING) { 108 | //Ble正在连接 109 | } else if (newState == BluetoothProfile.STATE_CONNECTED) { 110 | //Ble已连接 111 | } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { 112 | //Ble正在断开连接 113 | } 114 | } 115 | }); 116 | //Ble服务发现回调 117 | mBleService.setOnServicesDiscoveredListener(new BleService.OnServicesDiscoveredListener() { 118 | @Override 119 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 120 | 121 | } 122 | }); 123 | //Ble数据回调 124 | mBleService.setOnDataAvailableListener(new BleService.OnDataAvailableListener() { 125 | @Override 126 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 127 | //处理特性读取返回的数据 128 | } 129 | 130 | @Override 131 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 132 | //处理通知返回的数据 133 | } 134 | }); 135 | ``` 136 | 137 | ## **Release Notes** ## 138 | 139 | - **blelib-1.2.4**(2017-05-31) 140 | 141 | - 修复上个版本未解决的 bug,并优化代码。 142 | 143 | 144 | - **blelib-1.2.3**(2016-09-23) 145 | 146 | - 修复扫描结果返回时出现空指针的 bug 。 147 | 148 | 149 | - **blelib-1.2.2**(2016-09-08) 150 | 151 | - 修复调用 scanLeDevice(false) 不能停止扫描的 bug 。 152 | 153 | 154 | - **blelib-1.2.1**(2016-07-30) 155 | 156 | - 添加了 readDescriptor() 、 readRemoteRssi() 和 requestMtu()三个方法。上个版本中添加了相应的接口而没有添加方法。 157 | 158 | 159 | - **blelib-1.2.0**(2016-05-21) 160 | 161 | - 新增两个接口 OnReadRemoteRssiListener 和 OnMtuChangedListener 。 162 | 163 | - 在 OnDataAvailableListener 接口中新增 onDescriptorRead() 方法 。 164 | 165 | 166 | ## **Reference** ## 167 | 168 | [https://developer.android.com/guide/topics/connectivity/bluetooth-le.html](https://developer.android.com/guide/topics/connectivity/bluetooth-le.html) 169 | 170 | 171 | ## **License** ## 172 | 173 | BleLib is released under the [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0). 174 | 175 | ```.html 176 | Copyright 2016 Junk Chen. 177 | 178 | Licensed under the Apache License, Version 2.0 (the "License"); 179 | you may not use this file except in compliance with the License. 180 | You may obtain a copy of the License at 181 | 182 | http://www.apache.org/licenses/LICENSE-2.0 183 | 184 | Unless required by applicable law or agreed to in writing, software 185 | distributed under the License is distributed on an "AS IS" BASIS, 186 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 187 | See the License for the specific language governing permissions and 188 | limitations under the License. 189 | ``` 190 | -------------------------------------------------------------------------------- /blelib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /blelib/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /blelib/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = libraryVersion 4 | 5 | task sourcesJar(type: Jar) { 6 | from android.sourceSets.main.java.srcDirs 7 | classifier = 'sources' 8 | } 9 | 10 | task javadoc(type: Javadoc) { 11 | source = android.sourceSets.main.java.srcDirs 12 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 13 | } 14 | 15 | task javadocJar(type: Jar, dependsOn: javadoc) { 16 | classifier = 'javadoc' 17 | from javadoc.destinationDir 18 | } 19 | 20 | artifacts { 21 | archives javadocJar 22 | archives sourcesJar 23 | } 24 | 25 | // Bintray 26 | Properties properties = new Properties() 27 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 28 | 29 | bintray { 30 | user = properties.getProperty("bintray.user") 31 | key = properties.getProperty("bintray.apikey") 32 | 33 | configurations = ['archives'] 34 | pkg { 35 | repo = bintrayRepo 36 | name = bintrayName 37 | desc = libraryDescription 38 | websiteUrl = siteUrl 39 | issueTrackerUrl = issueUrl 40 | vcsUrl = gitUrl 41 | licenses = allLicenses 42 | publish = true 43 | publicDownloadNumbers = true 44 | version { 45 | desc = libraryDescription 46 | gpg { 47 | sign = true //Determines whether to GPG sign the files. The default is false 48 | passphrase = properties.getProperty("bintray.gpg.password") 49 | //Optional. The passphrase for GPG signing' 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /blelib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | libraryVersion = '1.2.4' 5 | 6 | bintrayRepo = 'maven' 7 | bintrayName = 'blelib' 8 | 9 | publishedGroupId = 'com.junkchen.blelib' 10 | libraryName = 'blelib' 11 | artifactId = 'blelib' 12 | 13 | libraryDescription = 'BleLib是Android低功耗蓝牙4.0及以上开发的辅助库,一行代码解决Ble初始化、扫描、连接等操作。' 14 | 15 | siteUrl = 'https://github.com/junkchen/BleLib' 16 | issueUrl = 'https://github.com/junkchen/BleLib/issues' 17 | gitUrl = 'https://github.com/junkchen/BleLib.git' 18 | 19 | developerId = 'junkchen' 20 | developerName = 'Junk Chen' 21 | developerEmail = 'junkchen@vip.qq.com' 22 | 23 | licenseName = 'The Apache Software License, Version 2.0' 24 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 25 | allLicenses = ["Apache-2.0"] 26 | } 27 | 28 | android { 29 | compileSdkVersion 23 30 | buildToolsVersion '26.0.0 rc2' 31 | 32 | defaultConfig { 33 | minSdkVersion 18 34 | targetSdkVersion 23 35 | versionCode 1 36 | versionName "1.2.4" 37 | } 38 | buildTypes { 39 | release { 40 | minifyEnabled false 41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | 45 | lintOptions { 46 | abortOnError false 47 | // disable 'MissingTranslation' 48 | // ignoreWarnings true 49 | } 50 | } 51 | 52 | dependencies { 53 | compile fileTree(include: ['*.jar'], dir: 'libs') 54 | testCompile 'junit:junit:4.12' 55 | compile 'com.android.support:appcompat-v7:23.1.1' 56 | } 57 | 58 | apply from: 'install.gradle' 59 | apply from: 'bintray.gradle' 60 | //https://oss.sonatype.org/content/repositories/releases/com/junkchen/blelib 61 | //Android Studio 发布库到jcenter步骤: 62 | // 1、Gradle->other->install 63 | // 2、将build/poms/下的 pom-default.xml 文件复制到 build/libs/中,并修改其后缀名为 blelib-1.0.0.pom 64 | // 3、将build/outputs/aar/下的 blelib-release.aar 文件复制到 build/libs/中,更名如:blelib-1.0.0.aar 65 | // 4、Gradle->publishing->bintrayUpload 66 | -------------------------------------------------------------------------------- /blelib/install.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | group = publishedGroupId // Maven Group ID for the artifact 4 | 5 | install { 6 | repositories.mavenInstaller { 7 | // This generates POM.xml with proper parameters 8 | pom { 9 | project { 10 | packaging 'aar' 11 | groupId publishedGroupId 12 | artifactId artifactId 13 | 14 | // Add your description here 15 | name libraryName 16 | description libraryDescription 17 | url siteUrl 18 | 19 | // Set your license 20 | licenses { 21 | license { 22 | name licenseName 23 | url licenseUrl 24 | } 25 | } 26 | developers { 27 | developer { 28 | id developerId 29 | name developerName 30 | email developerEmail 31 | } 32 | } 33 | scm { 34 | connection gitUrl 35 | developerConnection gitUrl 36 | url siteUrl 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blelib/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 D:\ProgramFiles\Android\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 | -------------------------------------------------------------------------------- /blelib/src/androidTest/java/com/junkchen/blelib/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.junkchen.blelib; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /blelib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /blelib/src/main/java/com/junkchen/blelib/BleListener.java: -------------------------------------------------------------------------------- 1 | package com.junkchen.blelib; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.BluetoothDevice; 5 | import android.bluetooth.BluetoothGatt; 6 | import android.bluetooth.BluetoothGattCharacteristic; 7 | import android.bluetooth.BluetoothGattDescriptor; 8 | import android.bluetooth.BluetoothProfile; 9 | 10 | /** 11 | * Created by JunkChen on 2016/5/21 0021. 12 | */ 13 | 14 | interface BleListener { 15 | interface OnLeScanListener { 16 | /** 17 | * Callback reporting an LE device found during a device scan initiated 18 | * by the {@link BluetoothAdapter#startLeScan} function. 19 | * 20 | * @param device Identifies the remote device 21 | * @param rssi The RSSI value for the remote device as reported by the 22 | * Bluetooth hardware. 0 if no RSSI value is available. 23 | * @param scanRecord The content of the advertisement record offered by 24 | * the remote device. 25 | */ 26 | void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord); 27 | } 28 | 29 | interface OnConnectionStateChangeListener { 30 | /** 31 | * Callback indicating when GATT client has connected/disconnected to/from a remote 32 | * GATT server. 33 | * 34 | * @param gatt GATT client 35 | * @param status Status of the connect or disconnect operation. 36 | * {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds. 37 | * @param newState Returns the new connection state. Can be one of 38 | * {@link BluetoothProfile#STATE_DISCONNECTED} or 39 | * {@link BluetoothProfile#STATE_CONNECTED} 40 | */ 41 | void onConnectionStateChange(BluetoothGatt gatt, int status, int newState); 42 | } 43 | 44 | interface OnServicesDiscoveredListener { 45 | /** 46 | * Callback invoked when the list of remote services, characteristics and descriptors 47 | * for the remote device have been updated, ie new services have been discovered. 48 | * 49 | * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices} 50 | * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device 51 | * has been explored successfully. 52 | */ 53 | void onServicesDiscovered(BluetoothGatt gatt, int status); 54 | } 55 | 56 | interface OnDataAvailableListener { 57 | /** 58 | * Callback reporting the result of a characteristic read operation. 59 | * 60 | * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic} 61 | * @param characteristic Characteristic that was read from the associated 62 | * remote device. 63 | * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation 64 | * was completed successfully. 65 | */ 66 | void onCharacteristicRead(BluetoothGatt gatt, 67 | BluetoothGattCharacteristic characteristic, int status); 68 | 69 | /** 70 | * Callback triggered as a result of a remote characteristic notification. 71 | * 72 | * @param gatt GATT client the characteristic is associated with 73 | * @param characteristic Characteristic that has been updated as a result 74 | * of a remote notification event. 75 | */ 76 | void onCharacteristicChanged(BluetoothGatt gatt, 77 | BluetoothGattCharacteristic characteristic); 78 | 79 | /** 80 | * Callback reporting the result of a descriptor read operation. 81 | * 82 | * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} 83 | * @param descriptor Descriptor that was read from the associated 84 | * remote device. 85 | * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation 86 | * was completed successfully 87 | */ 88 | void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status); 89 | } 90 | 91 | interface OnReadRemoteRssiListener { 92 | /** 93 | * Callback reporting the RSSI for a remote device connection. 94 | * 95 | * This callback is triggered in response to the 96 | * {@link BluetoothGatt#readRemoteRssi} function. 97 | * 98 | * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi} 99 | * @param rssi The RSSI value for the remote device 100 | * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully 101 | */ 102 | void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status); 103 | } 104 | 105 | interface OnMtuChangedListener { 106 | /** 107 | * Callback indicating the MTU for a given device connection has changed. 108 | * 109 | * This callback is triggered in response to the 110 | * {@link BluetoothGatt#requestMtu} function, or in response to a connection 111 | * event. 112 | * 113 | * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu} 114 | * @param mtu The new MTU size 115 | * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully 116 | */ 117 | void onMtuChanged(BluetoothGatt gatt, int mtu, int status); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /blelib/src/main/java/com/junkchen/blelib/BleService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib; 18 | 19 | import android.Manifest; 20 | import android.app.Service; 21 | import android.bluetooth.BluetoothAdapter; 22 | import android.bluetooth.BluetoothDevice; 23 | import android.bluetooth.BluetoothGatt; 24 | import android.bluetooth.BluetoothGattCallback; 25 | import android.bluetooth.BluetoothGattCharacteristic; 26 | import android.bluetooth.BluetoothGattDescriptor; 27 | import android.bluetooth.BluetoothGattService; 28 | import android.bluetooth.BluetoothManager; 29 | import android.bluetooth.BluetoothProfile; 30 | import android.bluetooth.le.ScanCallback; 31 | import android.bluetooth.le.ScanResult; 32 | import android.content.Context; 33 | import android.content.Intent; 34 | import android.content.pm.PackageManager; 35 | import android.os.Binder; 36 | import android.os.Build; 37 | import android.os.Handler; 38 | import android.os.IBinder; 39 | import android.util.Log; 40 | 41 | import java.util.ArrayList; 42 | import java.util.List; 43 | import java.util.UUID; 44 | 45 | /** 46 | * Created by JunkChen on 2015/9/11 0009. 47 | */ 48 | public class BleService extends Service implements Constants, BleListener { 49 | //Debug 50 | private static final String TAG = BleService.class.getName(); 51 | 52 | //Member fields 53 | private BluetoothManager mBluetoothManager; 54 | private BluetoothAdapter mBluetoothAdapter; 55 | private BluetoothGatt mBluetoothGatt; 56 | private List mScanLeDeviceList = new ArrayList<>(); 57 | private boolean isScanning; 58 | private boolean isConnect; 59 | private String mBluetoothDeviceAddress; 60 | private int mConnState = STATE_DISCONNECTED; 61 | // Stop scanning after 10 seconds. 62 | private static final long SCAN_PERIOD = 10 * 1000; 63 | private long mScanPeriod; 64 | 65 | private OnLeScanListener mOnLeScanListener; 66 | private OnConnectionStateChangeListener mOnConnectionStateChangeListener; 67 | private OnServicesDiscoveredListener mOnServicesDiscoveredListener; 68 | private OnDataAvailableListener mOnDataAvailableListener; 69 | private OnReadRemoteRssiListener mOnReadRemoteRssiListener; 70 | private OnMtuChangedListener mOnMtuChangedListener; 71 | 72 | private final IBinder mBinder = new LocalBinder(); 73 | private static BleService instance = null; 74 | 75 | public BleService() { 76 | instance = this; 77 | Log.d(TAG, "BleService initialized."); 78 | } 79 | 80 | public static BleService getInstance() { 81 | if (instance == null) throw new NullPointerException("BleService is not bind."); 82 | return instance; 83 | } 84 | 85 | @Override 86 | public IBinder onBind(Intent intent) { 87 | return mBinder; 88 | } 89 | 90 | @Override 91 | public boolean onUnbind(Intent intent) { 92 | close(); 93 | instance = null; 94 | return super.onUnbind(intent); 95 | } 96 | 97 | public class LocalBinder extends Binder { 98 | public BleService getService() { 99 | return BleService.this; 100 | } 101 | } 102 | 103 | /** 104 | * Check for your device to support Ble 105 | * 106 | * @return true is support false is not support 107 | */ 108 | public boolean isSupportBle() { 109 | return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 110 | } 111 | 112 | /** 113 | * Initializes a reference to the local Bluetooth adapter. 114 | * 115 | * @return If return true, the initialization is successful. 116 | */ 117 | public boolean initialize() { 118 | //For API level 18 and above, get a reference to BluetoothAdapter through BluetoothManager. 119 | if (mBluetoothManager == null) { 120 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 121 | if (mBluetoothManager == null) { 122 | Log.e(TAG, "Unable to initialize BluetoothManager."); 123 | return false; 124 | } 125 | } 126 | mBluetoothAdapter = mBluetoothManager.getAdapter(); 127 | if (mBluetoothAdapter == null) { 128 | Log.e(TAG, "Unable to initialize BluetoothAdapter."); 129 | return false; 130 | } 131 | return true; 132 | } 133 | 134 | /** 135 | * Turn on or off the local Bluetooth adapter;do not use without explicit 136 | * user action to turn on Bluetooth. 137 | * 138 | * @param enable if open ble 139 | * @return if ble is open return true 140 | */ 141 | public boolean enableBluetooth(boolean enable) { 142 | if (enable) { 143 | if (!mBluetoothAdapter.isEnabled()) { 144 | return mBluetoothAdapter.enable(); 145 | } 146 | return true; 147 | } else { 148 | if (mBluetoothAdapter.isEnabled()) { 149 | return mBluetoothAdapter.disable(); 150 | } 151 | return false; 152 | } 153 | } 154 | 155 | /** 156 | * Return true if Bluetooth is currently enabled and ready for use. 157 | * 158 | * @return true if the local adapter is turned on 159 | */ 160 | public boolean isEnableBluetooth() { 161 | return mBluetoothAdapter.isEnabled(); 162 | } 163 | 164 | /** 165 | * Scan Ble device. 166 | * 167 | * @param enable If true, start scan ble device.False stop scan. 168 | * @param scanPeriod scan ble period time 169 | */ 170 | public void scanLeDevice(final boolean enable, long scanPeriod) { 171 | if (enable) { 172 | if (isScanning) return; 173 | //Stop scanning after a predefined scan period. 174 | new Handler().postDelayed(new Runnable() { 175 | @Override 176 | public void run() { 177 | isScanning = false; 178 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 179 | mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); 180 | } else { 181 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 182 | } 183 | broadcastUpdate(ACTION_SCAN_FINISHED); 184 | mScanLeDeviceList.clear(); 185 | } 186 | }, scanPeriod); 187 | mScanLeDeviceList.clear(); 188 | isScanning = true; 189 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 190 | mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback); 191 | } else { 192 | mBluetoothAdapter.startLeScan(mLeScanCallback); 193 | } 194 | } else { 195 | isScanning = false; 196 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 197 | mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); 198 | } else { 199 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 200 | } 201 | broadcastUpdate(ACTION_SCAN_FINISHED); 202 | mScanLeDeviceList.clear(); 203 | } 204 | } 205 | 206 | /** 207 | * Scan Ble device. 208 | * 209 | * @param enable If true, start scan ble device.False stop scan. 210 | */ 211 | public void scanLeDevice(boolean enable) { 212 | this.scanLeDevice(enable, SCAN_PERIOD); 213 | } 214 | 215 | /** 216 | * If Ble is scaning return true, if not return false. 217 | * 218 | * @return ble whether scanning 219 | */ 220 | public boolean isScanning() { 221 | return isScanning; 222 | } 223 | 224 | /** 225 | * Connects to the GATT server hosted on the Bluetooth LE device. 226 | * 227 | * @param address The device address of the destination device. 228 | * @return Return true if the connection is initiated successfully. The connection result 229 | * is reported asynchronously through the BluetoothGattCallback#onConnectionStateChange. 230 | */ 231 | public boolean connect(final String address) { 232 | if (isScanning) scanLeDevice(false); 233 | close(); 234 | if (mBluetoothAdapter == null || address == null) { 235 | Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); 236 | return false; 237 | } 238 | //Previously connected device. Try to reconnect. 239 | if (mBluetoothGatt != null && mBluetoothDeviceAddress != null 240 | && address.equals(mBluetoothDeviceAddress)) { 241 | Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); 242 | if (mBluetoothGatt.connect()) { 243 | mConnState = STATE_CONNECTING; 244 | return true; 245 | } else { 246 | return false; 247 | } 248 | } 249 | final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); 250 | if (device == null) { 251 | Log.w(TAG, "Device not found. Unable to connect."); 252 | return false; 253 | } 254 | //We want to directly connect to the device, so we are setting the autoConnect 255 | // parameter to false. 256 | mBluetoothGatt = device.connectGatt(this, false, mGattCallback); 257 | Log.d(TAG, "Trying to create a new connection."); 258 | mBluetoothDeviceAddress = address; 259 | mConnState = STATE_CONNECTING; 260 | return true; 261 | } 262 | 263 | /** 264 | * Disconnects an existing connection or cancel a pending connection. The disconnection result 265 | * is reported asynchronously through the BluetoothGattCallback#onConnectionStateChange. 266 | */ 267 | public void disconnect() { 268 | if (mBluetoothAdapter == null || mBluetoothGatt == null) { 269 | Log.e(TAG, "BluetoothAdapter not initialized."); 270 | return; 271 | } 272 | mBluetoothGatt.disconnect(); 273 | } 274 | 275 | /** 276 | * After using a given BLE device, the app must call this method to ensure resources are 277 | * released properly. 278 | */ 279 | public void close() { 280 | isConnect = false; 281 | if (mBluetoothGatt == null) { 282 | return; 283 | } 284 | mBluetoothGatt.close(); 285 | mBluetoothGatt = null; 286 | } 287 | 288 | /** 289 | * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported 290 | * asynchronously through the BluetoothGattCallback#onCharacteristicRead. 291 | * 292 | * @param characteristic The characteristic to read from. 293 | */ 294 | public void readCharacteristic(BluetoothGattCharacteristic characteristic) { 295 | if (mBluetoothAdapter == null || mBluetoothGatt == null) { 296 | Log.w(TAG, "BluetoothAdapter not initialized."); 297 | return; 298 | } 299 | mBluetoothGatt.readCharacteristic(characteristic); 300 | } 301 | 302 | /** 303 | * Request a read on a given {@code BluetoothGattCharacteristic}, specific service UUID 304 | * and characteristic UUID. The read result is reported asynchronously through the 305 | * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, 306 | * android.bluetooth.BluetoothGattCharacteristic, int)} callback. 307 | * 308 | * @param serviceUUID remote device service uuid 309 | * @param characteristicUUID remote device characteristic uuid 310 | */ 311 | public void readCharacteristic(String serviceUUID, String characteristicUUID) { 312 | if (mBluetoothGatt != null) { 313 | BluetoothGattService service = 314 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 315 | BluetoothGattCharacteristic characteristic = 316 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 317 | mBluetoothGatt.readCharacteristic(characteristic); 318 | } 319 | } 320 | 321 | public void readCharacteristic(String address, String serviceUUID, String characteristicUUID) { 322 | if (mBluetoothGatt != null) { 323 | BluetoothGattService service = 324 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 325 | BluetoothGattCharacteristic characteristic = 326 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 327 | mBluetoothGatt.readCharacteristic(characteristic); 328 | } 329 | } 330 | 331 | /** 332 | * Write data to characteristic, and send to remote bluetooth le device. 333 | * 334 | * @param serviceUUID remote device service uuid 335 | * @param characteristicUUID remote device characteristic uuid 336 | * @param value Send to remote ble device data. 337 | */ 338 | public void writeCharacteristic(String serviceUUID, String characteristicUUID, String value) { 339 | if (mBluetoothGatt != null) { 340 | BluetoothGattService service = 341 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 342 | BluetoothGattCharacteristic characteristic = 343 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 344 | characteristic.setValue(value); 345 | mBluetoothGatt.writeCharacteristic(characteristic); 346 | } 347 | } 348 | 349 | public boolean writeCharacteristic(String serviceUUID, String characteristicUUID, byte[] value) { 350 | if (mBluetoothGatt != null) { 351 | BluetoothGattService service = 352 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 353 | BluetoothGattCharacteristic characteristic = 354 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 355 | characteristic.setValue(value); 356 | return mBluetoothGatt.writeCharacteristic(characteristic); 357 | } 358 | return false; 359 | } 360 | 361 | /** 362 | * Write value to characteristic, and send to remote bluetooth le device. 363 | * 364 | * @param characteristic remote device characteristic 365 | * @param value New value for this characteristic 366 | * @return if write success return true 367 | */ 368 | public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, String value) { 369 | return writeCharacteristic(characteristic, value.getBytes()); 370 | } 371 | 372 | /** 373 | * Writes a given characteristic and its values to the associated remote device. 374 | * 375 | * @param characteristic remote device characteristic 376 | * @param value New value for this characteristic 377 | * @return if write success return true 378 | */ 379 | public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value) { 380 | if (mBluetoothGatt != null) { 381 | characteristic.setValue(value); 382 | return mBluetoothGatt.writeCharacteristic(characteristic); 383 | } 384 | return false; 385 | } 386 | 387 | /** 388 | * Enables or disables notification on a give characteristic. 389 | * 390 | * @param characteristic Characteristic to act on. 391 | * @param enabled If true, enable notification. False otherwise. 392 | */ 393 | public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, 394 | boolean enabled) { 395 | if (mBluetoothAdapter == null || mBluetoothGatt == null) { 396 | Log.w(TAG, "BluetoothAdapter not initialized"); 397 | return; 398 | } 399 | mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); 400 | 401 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 402 | UUID.fromString(GattAttributes.DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION)); 403 | descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : 404 | BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); 405 | mBluetoothGatt.writeDescriptor(descriptor); 406 | } 407 | 408 | public void setCharacteristicNotification(String serviceUUID, String characteristicUUID, 409 | boolean enabled) { 410 | if (mBluetoothAdapter == null || mBluetoothGatt == null) { 411 | Log.w(TAG, "BluetoothAdapter not initialized"); 412 | return; 413 | } 414 | BluetoothGattService service = 415 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 416 | BluetoothGattCharacteristic characteristic = 417 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 418 | 419 | mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); 420 | 421 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 422 | UUID.fromString(GattAttributes.DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION)); 423 | descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : 424 | BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); 425 | mBluetoothGatt.writeDescriptor(descriptor); 426 | } 427 | 428 | /** 429 | * Reads the value for a given descriptor from the associated remote device. 430 | * 431 | *

Once the read operation has been completed, the 432 | * {@link BluetoothGattCallback#onDescriptorRead} callback is 433 | * triggered, signaling the result of the operation. 434 | * 435 | *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. 436 | * 437 | * @param descriptor Descriptor value to read from the remote device 438 | * @return true, if the read operation was initiated successfully 439 | */ 440 | public boolean readDescriptor(BluetoothGattDescriptor descriptor) { 441 | if (mBluetoothGatt == null) { 442 | Log.w(TAG, "BluetoothGatt is null"); 443 | return false; 444 | } 445 | return mBluetoothGatt.readDescriptor(descriptor); 446 | } 447 | 448 | /** 449 | * Reads the value for a given descriptor from the associated remote device. 450 | * 451 | * @param serviceUUID remote device service uuid 452 | * @param characteristicUUID remote device characteristic uuid 453 | * @param descriptorUUID remote device descriptor uuid 454 | * @return true, if the read operation was initiated successfully 455 | */ 456 | public boolean readDescriptor(String serviceUUID, String characteristicUUID, 457 | String descriptorUUID) { 458 | if (mBluetoothGatt == null) { 459 | Log.w(TAG, "BluetoothGatt is null"); 460 | return false; 461 | } 462 | // try { 463 | BluetoothGattService service = 464 | mBluetoothGatt.getService(UUID.fromString(serviceUUID)); 465 | BluetoothGattCharacteristic characteristic = 466 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 467 | BluetoothGattDescriptor descriptor = 468 | characteristic.getDescriptor(UUID.fromString(descriptorUUID)); 469 | return mBluetoothGatt.readDescriptor(descriptor); 470 | // } catch (Exception e) { 471 | // Log.e(TAG, "read descriptor exception", e); 472 | // return false; 473 | // } 474 | } 475 | 476 | /** 477 | * Read the RSSI for a connected remote device. 478 | * 479 | *

The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be 480 | * invoked when the RSSI value has been read. 481 | * 482 | *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. 483 | * 484 | * @return true, if the RSSI value has been requested successfully 485 | */ 486 | public boolean readRemoteRssi() { 487 | if (mBluetoothGatt == null) return false; 488 | return mBluetoothGatt.readRemoteRssi(); 489 | } 490 | 491 | /** 492 | * Request an MTU size used for a given connection. 493 | * 494 | *

When performing a write request operation (write without response), 495 | * the data sent is truncated to the MTU size. This function may be used 496 | * to request a larger MTU size to be able to send more data at once. 497 | * 498 | *

A {@link BluetoothGattCallback#onMtuChanged} callback will indicate 499 | * whether this operation was successful. 500 | * 501 | *

Requires {@link Manifest.permission#BLUETOOTH} permission. 502 | * 503 | * @param mtu mtu 504 | * @return true, if the new MTU value has been requested successfully 505 | */ 506 | public boolean requestMtu(int mtu) { 507 | if (mBluetoothGatt == null) return false; 508 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//Android API level >= 21 509 | return mBluetoothGatt.requestMtu(mtu); 510 | } else { 511 | return false; 512 | } 513 | } 514 | 515 | public boolean isConnect() { 516 | return isConnect; 517 | } 518 | 519 | public BluetoothDevice getConnectDevice() { 520 | if (mBluetoothGatt == null) return null; 521 | return mBluetoothGatt.getDevice(); 522 | } 523 | 524 | public BluetoothGatt getBluetoothGatt() { 525 | return mBluetoothGatt; 526 | } 527 | 528 | /** 529 | * Retrieves a list of supported GATT services on the connected device. This should be 530 | * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. 531 | * 532 | * @return A {@code List} of supported services. 533 | */ 534 | public List getSupportedGattServices() { 535 | if (mBluetoothGatt == null) return null; 536 | return mBluetoothGatt.getServices(); 537 | } 538 | 539 | public List getConnectDevices() { 540 | if (mBluetoothManager == null) return null; 541 | return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); 542 | } 543 | 544 | /** 545 | * Device scan callback. 546 | *

547 | * Use mScanCallback if Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP, 548 | * else use mLeScanCallback. 549 | */ 550 | private ScanCallback mScanCallback; 551 | private BluetoothAdapter.LeScanCallback mLeScanCallback; 552 | 553 | { 554 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 555 | mScanCallback = new ScanCallback() { 556 | @Override 557 | public void onScanResult(int callbackType, ScanResult result) { 558 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 559 | if (mScanLeDeviceList.contains(result.getDevice())) return; 560 | mScanLeDeviceList.add(result.getDevice()); 561 | if (mOnLeScanListener != null) { 562 | mOnLeScanListener.onLeScan(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); 563 | } 564 | broadcastUpdate(ACTION_BLUETOOTH_DEVICE, result.getDevice()); 565 | Log.i(TAG, "onScanResult: name: " + result.getDevice().getName() + 566 | ", address: " + result.getDevice().getAddress() + 567 | ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord()); 568 | } 569 | } 570 | }; 571 | } else { 572 | mLeScanCallback = new BluetoothAdapter.LeScanCallback() { 573 | @Override 574 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 575 | Log.i(TAG, "device name: " + device.getName() + ", address: " + device.getAddress()); 576 | if (device == null || mScanLeDeviceList.contains(device)) return; 577 | mScanLeDeviceList.add(device); 578 | if (mOnLeScanListener != null) { 579 | mOnLeScanListener.onLeScan(device, rssi, scanRecord); 580 | } 581 | broadcastUpdate(ACTION_BLUETOOTH_DEVICE, device); 582 | } 583 | }; 584 | } 585 | } 586 | 587 | /** 588 | * Implements callback methods for GATT events that the app cares about. For example, 589 | * connection change and services discovered. 590 | */ 591 | private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 592 | @Override 593 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 594 | if (mOnConnectionStateChangeListener != null) { 595 | mOnConnectionStateChangeListener.onConnectionStateChange(gatt, status, newState); 596 | } 597 | String intentAction; 598 | String address = gatt.getDevice().getAddress(); 599 | if (newState == BluetoothProfile.STATE_DISCONNECTED) { 600 | Log.i(TAG, "onConnectionStateChange: DISCONNECTED: " + getConnectDevices().size()); 601 | intentAction = ACTION_GATT_DISCONNECTED; 602 | isConnect = false; 603 | mConnState = STATE_DISCONNECTED; 604 | Log.i(TAG, "Disconnected from GATT server."); 605 | broadcastUpdate(intentAction, address); 606 | close(); 607 | } else if (newState == BluetoothProfile.STATE_CONNECTING) { 608 | Log.i(TAG, "onConnectionStateChange: CONNECTING: " + getConnectDevices().size()); 609 | isConnect = false; 610 | intentAction = ACTION_GATT_CONNECTING; 611 | mConnState = STATE_CONNECTING; 612 | Log.i(TAG, "Connecting to GATT server."); 613 | broadcastUpdate(intentAction, address); 614 | } else if (newState == BluetoothProfile.STATE_CONNECTED) { 615 | Log.i(TAG, "onConnectionStateChange: CONNECTED: " + getConnectDevices().size()); 616 | intentAction = ACTION_GATT_CONNECTED; 617 | isConnect = true; 618 | mConnState = STATE_CONNECTED; 619 | broadcastUpdate(intentAction, address); 620 | Log.i(TAG, "Connected to GATT server."); 621 | // Attempts to discover services after successful connection. 622 | Log.i(TAG, "Attempting to start service discovery:" + 623 | mBluetoothGatt.discoverServices()); 624 | } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { 625 | Log.i(TAG, "onConnectionStateChange: DISCONNECTING: " + getConnectDevices().size()); 626 | isConnect = false; 627 | intentAction = ACTION_GATT_DISCONNECTING; 628 | mConnState = STATE_DISCONNECTING; 629 | Log.i(TAG, "Disconnecting from GATT server."); 630 | broadcastUpdate(intentAction, address); 631 | } 632 | } 633 | 634 | // New services discovered 635 | @Override 636 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 637 | if (mOnServicesDiscoveredListener != null) { 638 | mOnServicesDiscoveredListener.onServicesDiscovered(gatt, status); 639 | } 640 | if (status == BluetoothGatt.GATT_SUCCESS) { 641 | broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); 642 | } else { 643 | Log.w(TAG, "onServicesDiscovered received: " + status); 644 | } 645 | } 646 | 647 | // Result of a characteristic read operation 648 | @Override 649 | public void onCharacteristicRead(BluetoothGatt gatt, 650 | BluetoothGattCharacteristic characteristic, int status) { 651 | if (mOnDataAvailableListener != null) { 652 | mOnDataAvailableListener.onCharacteristicRead(gatt, characteristic, status); 653 | } 654 | } 655 | 656 | @Override 657 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 658 | super.onCharacteristicWrite(gatt, characteristic, status); 659 | String address = gatt.getDevice().getAddress(); 660 | for (int i = 0; i < characteristic.getValue().length; i++) { 661 | Log.i(TAG, "address: " + address + ",Write: " + characteristic.getValue()[i]); 662 | } 663 | } 664 | 665 | @Override 666 | public void onCharacteristicChanged(BluetoothGatt gatt, 667 | BluetoothGattCharacteristic characteristic) { 668 | if (mOnDataAvailableListener != null) { 669 | mOnDataAvailableListener.onCharacteristicChanged(gatt, characteristic); 670 | } 671 | } 672 | 673 | @Override 674 | public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 675 | if (mOnDataAvailableListener != null) { 676 | mOnDataAvailableListener.onDescriptorRead(gatt, descriptor, status); 677 | } 678 | } 679 | 680 | @Override 681 | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { 682 | if (mOnReadRemoteRssiListener != null) { 683 | mOnReadRemoteRssiListener.onReadRemoteRssi(gatt, rssi, status); 684 | } 685 | } 686 | 687 | @Override 688 | public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { 689 | if (mOnMtuChangedListener != null) { 690 | mOnMtuChangedListener.onMtuChanged(gatt, mtu, status); 691 | } 692 | } 693 | }; 694 | 695 | private void broadcastUpdate(final String action) { 696 | final Intent intent = new Intent(action); 697 | sendBroadcast(intent); 698 | } 699 | 700 | private void broadcastUpdate(final String action, final String address) { 701 | final Intent intent = new Intent(action); 702 | intent.putExtra("address", address); 703 | sendBroadcast(intent); 704 | } 705 | 706 | private void broadcastUpdate(final String action, BluetoothDevice device) { 707 | final Intent intent = new Intent(action); 708 | intent.putExtra("name", device.getName()); 709 | intent.putExtra("address", device.getAddress()); 710 | sendBroadcast(intent); 711 | } 712 | 713 | public void setOnLeScanListener(OnLeScanListener l) { 714 | mOnLeScanListener = l; 715 | } 716 | 717 | public void setOnConnectListener(OnConnectionStateChangeListener l) { 718 | mOnConnectionStateChangeListener = l; 719 | } 720 | 721 | public void setOnServicesDiscoveredListener(OnServicesDiscoveredListener l) { 722 | mOnServicesDiscoveredListener = l; 723 | } 724 | 725 | public void setOnDataAvailableListener(OnDataAvailableListener l) { 726 | mOnDataAvailableListener = l; 727 | } 728 | 729 | public void setOnReadRemoteRssiListener(OnReadRemoteRssiListener l) { 730 | mOnReadRemoteRssiListener = l; 731 | } 732 | 733 | public void setOnMtuChangedListener(OnMtuChangedListener l) { 734 | mOnMtuChangedListener = l; 735 | } 736 | } -------------------------------------------------------------------------------- /blelib/src/main/java/com/junkchen/blelib/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib; 18 | 19 | /** 20 | * Created by JunkChen on 2015/9/11 0008. 21 | */ 22 | public interface Constants { 23 | //Connection state 24 | int STATE_DISCONNECTED = 0; 25 | int STATE_CONNECTING = 1; 26 | int STATE_CONNECTED = 2; 27 | int STATE_DISCONNECTING = 3; 28 | 29 | //Action 30 | String ACTION_GATT_DISCONNECTED = "com.junkchen.blelib.ACTION_GATT_DISCONNECTED"; 31 | String ACTION_GATT_CONNECTING = "com.junkchen.blelib.ACTION_GATT_CONNECTING"; 32 | String ACTION_GATT_CONNECTED = "com.junkchen.blelib.ACTION_GATT_CONNECTED"; 33 | String ACTION_GATT_DISCONNECTING = "com.junkchen.blelib.ACTION_GATT_DISCONNECTING"; 34 | String ACTION_GATT_SERVICES_DISCOVERED = "com.junkchen.blelib.ACTION_GATT_SERVICES_DISCOVERED"; 35 | String ACTION_BLUETOOTH_DEVICE = "com.junkchen.blelib.ACTION_BLUETOOTH_DEVICE"; 36 | String ACTION_SCAN_FINISHED = "com.junkchen.blelib.ACTION_SCAN_FINISHED"; 37 | } 38 | -------------------------------------------------------------------------------- /blelib/src/main/java/com/junkchen/blelib/MultipleBleService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib; 18 | 19 | import android.Manifest; 20 | import android.app.Service; 21 | import android.bluetooth.BluetoothAdapter; 22 | import android.bluetooth.BluetoothDevice; 23 | import android.bluetooth.BluetoothGatt; 24 | import android.bluetooth.BluetoothGattCallback; 25 | import android.bluetooth.BluetoothGattCharacteristic; 26 | import android.bluetooth.BluetoothGattDescriptor; 27 | import android.bluetooth.BluetoothGattService; 28 | import android.bluetooth.BluetoothManager; 29 | import android.bluetooth.BluetoothProfile; 30 | import android.bluetooth.le.ScanCallback; 31 | import android.bluetooth.le.ScanResult; 32 | import android.content.Context; 33 | import android.content.Intent; 34 | import android.content.pm.PackageManager; 35 | import android.os.Binder; 36 | import android.os.Build; 37 | import android.os.Handler; 38 | import android.os.IBinder; 39 | import android.util.Log; 40 | 41 | import java.util.ArrayList; 42 | import java.util.HashMap; 43 | import java.util.List; 44 | import java.util.Map; 45 | import java.util.UUID; 46 | 47 | /** 48 | * Created by JunkChen on 2015/9/11 0009. 49 | */ 50 | //@TargetApi(Build.VERSION_CODES.LOLLIPOP) 51 | public class MultipleBleService extends Service implements Constants, BleListener { 52 | //Debug 53 | private static final String TAG = MultipleBleService.class.getName(); 54 | 55 | //Member fields 56 | private BluetoothManager mBluetoothManager; 57 | private BluetoothAdapter mBluetoothAdapter; 58 | private Map mBluetoothGattMap; 59 | private List mScanLeDeviceList = new ArrayList<>(); 60 | private boolean isScanning; 61 | private List mConnectedAddressList;//Already connected remote device address 62 | //Stop scanning after 10 seconds. 63 | private static final long SCAN_PERIOD = 10 * 1000; 64 | private static final int MAX_CONNECT_NUM = 16;//Can connect remote device max number. 65 | 66 | private OnLeScanListener mOnLeScanListener; 67 | private OnConnectionStateChangeListener mOnConnectionStateChangeListener; 68 | private OnServicesDiscoveredListener mOnServicesDiscoveredListener; 69 | private OnDataAvailableListener mOnDataAvailableListener; 70 | private OnReadRemoteRssiListener mOnReadRemoteRssiListener; 71 | private OnMtuChangedListener mOnMtuChangedListener; 72 | 73 | private final IBinder mBinder = new LocalBinder(); 74 | private static MultipleBleService instance = null; 75 | 76 | public MultipleBleService() { 77 | instance = this; 78 | Log.d(TAG, "BleService initialized."); 79 | } 80 | 81 | public static MultipleBleService getInstance() { 82 | if (instance == null) throw new NullPointerException("MultipleBleService is not bind."); 83 | return instance; 84 | } 85 | 86 | @Override 87 | public IBinder onBind(Intent intent) { 88 | return mBinder; 89 | } 90 | 91 | @Override 92 | public boolean onUnbind(Intent intent) { 93 | close(); 94 | instance = null; 95 | return super.onUnbind(intent); 96 | } 97 | 98 | public class LocalBinder extends Binder { 99 | public MultipleBleService getService() { 100 | return MultipleBleService.this; 101 | } 102 | } 103 | 104 | /** 105 | * Check for your device to support Ble 106 | * 107 | * @return true is support false is not support 108 | */ 109 | public boolean isSupportBle() { 110 | return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 111 | } 112 | 113 | /** 114 | * Initializes a reference to the local Bluetooth adapter. 115 | * 116 | * @return If return true, the initialization is successful. 117 | */ 118 | public boolean initialize() { 119 | //For API level 18 and above, get a reference to BluetoothAdapter through BluetoothManager. 120 | if (mBluetoothManager == null) { 121 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 122 | if (mBluetoothManager == null) { 123 | Log.e(TAG, "Unable to initialize BluetoothManager."); 124 | return false; 125 | } 126 | } 127 | mBluetoothAdapter = mBluetoothManager.getAdapter(); 128 | if (mBluetoothAdapter == null) { 129 | Log.e(TAG, "Unable to initialize BluetoothAdapter."); 130 | return false; 131 | } 132 | return true; 133 | } 134 | 135 | /** 136 | * Turn on or off the local Bluetooth adapter;do not use without explicit 137 | * user action to turn on Bluetooth. 138 | * 139 | * @param enable if open ble 140 | * @return if ble is open return true 141 | */ 142 | public boolean enableBluetooth(boolean enable) { 143 | if (enable) { 144 | if (!mBluetoothAdapter.isEnabled()) { 145 | mBluetoothAdapter.enable(); 146 | } 147 | return true; 148 | } else { 149 | if (mBluetoothAdapter.isEnabled()) { 150 | mBluetoothAdapter.disable(); 151 | } 152 | return false; 153 | } 154 | } 155 | 156 | /** 157 | * Return true if Bluetooth is currently enabled and ready for use. 158 | * 159 | * @return true if the local adapter is turned on 160 | */ 161 | public boolean isEnableBluetooth() { 162 | return mBluetoothAdapter.isEnabled(); 163 | } 164 | 165 | /** 166 | * Scan Ble device. 167 | * 168 | * @param enable If true, start scan ble device.False stop scan. 169 | * @param scanPeriod scan ble period time 170 | */ 171 | public void scanLeDevice(final boolean enable, long scanPeriod) { 172 | if (enable) { 173 | if (isScanning) return; 174 | //Stop scanning after a predefined scan period. 175 | new Handler().postDelayed(new Runnable() { 176 | @Override 177 | public void run() { 178 | isScanning = false; 179 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 180 | mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); 181 | } else { 182 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 183 | } 184 | broadcastUpdate(ACTION_SCAN_FINISHED); 185 | mScanLeDeviceList.clear(); 186 | } 187 | }, scanPeriod); 188 | mScanLeDeviceList.clear(); 189 | isScanning = true; 190 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 191 | mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback); 192 | } else { 193 | mBluetoothAdapter.startLeScan(mLeScanCallback); 194 | } 195 | } else { 196 | isScanning = false; 197 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 198 | mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); 199 | } else { 200 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 201 | } 202 | broadcastUpdate(ACTION_SCAN_FINISHED); 203 | mScanLeDeviceList.clear(); 204 | } 205 | } 206 | 207 | /** 208 | * Scan Ble device. 209 | * 210 | * @param enable If true, start scan ble device.False stop scan. 211 | */ 212 | public void scanLeDevice(boolean enable) { 213 | this.scanLeDevice(enable, SCAN_PERIOD); 214 | } 215 | 216 | /** 217 | * If Ble is scaning return true, if not return false. 218 | * 219 | * @return ble whether scanning 220 | */ 221 | public boolean isScanning() { 222 | return isScanning; 223 | } 224 | 225 | /** 226 | * Connects to the GATT server hosted on the Bluetooth LE device. 227 | * 228 | * @param address The device address of the destination device. 229 | * @return Return true if the connection is initiated successfully. The connection result 230 | * is reported asynchronously through the BluetoothGattCallback#onConnectionStateChange. 231 | */ 232 | public boolean connect(final String address) { 233 | if (isScanning) scanLeDevice(false); 234 | if (getConnectDevices().size() > MAX_CONNECT_NUM) return false; 235 | if (mConnectedAddressList == null) { 236 | mConnectedAddressList = new ArrayList<>(); 237 | } 238 | if (mConnectedAddressList.contains(address)) { 239 | Log.d(TAG, "This is device already connected."); 240 | return true; 241 | } 242 | if (mBluetoothAdapter == null || address == null) { 243 | Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); 244 | return false; 245 | } 246 | //Previously connected device. Try to reconnect. 247 | if (mBluetoothGattMap == null) { 248 | mBluetoothGattMap = new HashMap<>(); 249 | } 250 | if (mBluetoothGattMap.get(address) != null && mConnectedAddressList.contains(address)) { 251 | Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); 252 | if (mBluetoothGattMap.get(address).connect()) { 253 | return true; 254 | } else { 255 | return false; 256 | } 257 | } 258 | final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); 259 | if (device == null) { 260 | Log.w(TAG, "Device not found. Unable to connect."); 261 | return false; 262 | } 263 | //We want to directly connect to the device, so we are setting the autoConnect 264 | // parameter to false. 265 | BluetoothGatt bluetoothGatt = device.connectGatt(this, false, mGattCallback); 266 | if (bluetoothGatt != null) { 267 | mBluetoothGattMap.put(address, bluetoothGatt); 268 | Log.d(TAG, "Trying to create a new connection."); 269 | mConnectedAddressList.add(address); 270 | return true; 271 | } 272 | return false; 273 | } 274 | 275 | /** 276 | * Disconnects an existing connection or cancel a pending connection. The disconnection result 277 | * is reported asynchronously through the BluetoothGattCallback#onConnectionStateChange. 278 | * 279 | * @param address disconnect address 280 | */ 281 | public void disconnect(final String address) { 282 | if (mBluetoothAdapter == null || mBluetoothGattMap.get(address) == null) { 283 | Log.e(TAG, "BluetoothAdapter not initialized."); 284 | return; 285 | } 286 | mBluetoothGattMap.get(address).disconnect(); 287 | } 288 | 289 | /** 290 | * Discovers services offered by a remote device as well as their 291 | * characteristics and descriptors. 292 | *

293 | * Requires {@link android.Manifest.permission#BLUETOOTH} permission. 294 | * 295 | * @param address Remote device address 296 | * @return true, if the remote service discovery has been started 297 | */ 298 | public boolean discoverServices(String address) { 299 | if (mBluetoothGattMap.get(address) == null) return false; 300 | return mBluetoothGattMap.get(address).discoverServices(); 301 | } 302 | 303 | /** 304 | * After using a given BLE device, the app must call this method to ensure resources are 305 | * released properly. 306 | *

307 | * Close this Bluetooth GATT client. 308 | * 309 | * @param address You will close Gatt client's address. 310 | */ 311 | public void close(String address) { 312 | mConnectedAddressList.remove(address); 313 | if (mBluetoothGattMap.get(address) != null) { 314 | mBluetoothGattMap.get(address).close(); 315 | mBluetoothGattMap.remove(address); 316 | } 317 | } 318 | 319 | /** 320 | * After using a given BLE device, the app must call this method to ensure resources are 321 | * released properly. 322 | */ 323 | public void close() { 324 | if (mConnectedAddressList == null) return; 325 | for (String address : 326 | mConnectedAddressList) { 327 | if (mBluetoothGattMap.get(address) != null) { 328 | mBluetoothGattMap.get(address).close(); 329 | } 330 | } 331 | mBluetoothGattMap.clear(); 332 | mConnectedAddressList.clear(); 333 | } 334 | 335 | /** 336 | * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported 337 | * asynchronously through the BluetoothGattCallback#onCharacteristicRead. 338 | * 339 | * @param address The address to read from. 340 | * @param characteristic The characteristic to read from. 341 | */ 342 | public void readCharacteristic(String address, BluetoothGattCharacteristic characteristic) { 343 | if (mBluetoothAdapter == null || mBluetoothGattMap.get(address) == null) { 344 | Log.w(TAG, "BluetoothAdapter not initialized."); 345 | return; 346 | } 347 | mBluetoothGattMap.get(address).readCharacteristic(characteristic); 348 | } 349 | 350 | /** 351 | * Request a read on a given {@code BluetoothGattCharacteristic}, specific service UUID 352 | * and characteristic UUID. The read result is reported asynchronously through the 353 | * {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, 354 | * android.bluetooth.BluetoothGattCharacteristic, int)} callback. 355 | * 356 | * @param address The address to read from. 357 | * @param serviceUUID remote device service uuid 358 | * @param characteristicUUID remote device characteristic uuid 359 | */ 360 | public void readCharacteristic(String address, String serviceUUID, String characteristicUUID) { 361 | if (mBluetoothGattMap.get(address) != null) { 362 | BluetoothGattService service = 363 | mBluetoothGattMap.get(address).getService(UUID.fromString(serviceUUID)); 364 | BluetoothGattCharacteristic characteristic = 365 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 366 | mBluetoothGattMap.get(address).readCharacteristic(characteristic); 367 | } 368 | } 369 | 370 | /** 371 | * Write data to characteristic, and send to remote bluetooth le device. 372 | * 373 | * @param address The address to read from. 374 | * @param serviceUUID remote device service uuid 375 | * @param characteristicUUID remote device characteristic uuid 376 | * @param value Send to remote ble device data. 377 | * @return if write success return true 378 | */ 379 | public boolean writeCharacteristic(String address, String serviceUUID, 380 | String characteristicUUID, String value) { 381 | BluetoothGatt bluetoothGatt = mBluetoothGattMap.get(address); 382 | if (bluetoothGatt != null) { 383 | BluetoothGattService service = 384 | bluetoothGatt.getService(UUID.fromString(serviceUUID)); 385 | BluetoothGattCharacteristic characteristic = 386 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 387 | characteristic.setValue(value); 388 | return bluetoothGatt.writeCharacteristic(characteristic); 389 | } 390 | return false; 391 | } 392 | 393 | /** 394 | * Write data to characteristic, and send to remote bluetooth le device. 395 | * 396 | * @param address The address to read from. 397 | * @param serviceUUID remote device service uuid 398 | * @param characteristicUUID remote device characteristic uuid 399 | * @param value Send to remote ble device data. 400 | * @return if write success return true 401 | */ 402 | public boolean writeCharacteristic(String address, String serviceUUID, 403 | String characteristicUUID, byte[] value) { 404 | BluetoothGatt bluetoothGatt = mBluetoothGattMap.get(address); 405 | if (bluetoothGatt != null) { 406 | BluetoothGattService service = 407 | bluetoothGatt.getService(UUID.fromString(serviceUUID)); 408 | BluetoothGattCharacteristic characteristic = 409 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 410 | characteristic.setValue(value); 411 | return bluetoothGatt.writeCharacteristic(characteristic); 412 | } 413 | return false; 414 | } 415 | 416 | /** 417 | * Enables or disables notification on a give characteristic. 418 | * 419 | * @param address The address. 420 | * @param characteristic Characteristic to act on. 421 | * @param enabled If true, enable notification. False otherwise. 422 | */ 423 | public void setCharacteristicNotification(String address, 424 | BluetoothGattCharacteristic characteristic, 425 | boolean enabled) { 426 | if (mBluetoothAdapter == null || mBluetoothGattMap.get(address) == null) { 427 | Log.w(TAG, "BluetoothAdapter not initialized"); 428 | return; 429 | } 430 | mBluetoothGattMap.get(address).setCharacteristicNotification(characteristic, enabled); 431 | 432 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 433 | UUID.fromString(GattAttributes.DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION)); 434 | descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : 435 | BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); 436 | mBluetoothGattMap.get(address).writeDescriptor(descriptor); 437 | } 438 | 439 | /** 440 | * Enables or disables notification on a give characteristic. 441 | * 442 | * @param address The address to read from. 443 | * @param serviceUUID remote device service uuid 444 | * @param characteristicUUID remote device characteristic uuid 445 | * @param enabled if notify is true 446 | */ 447 | public void setCharacteristicNotification(String address, String serviceUUID, 448 | String characteristicUUID, boolean enabled) { 449 | if (mBluetoothAdapter == null || mBluetoothGattMap.get(address) == null) { 450 | Log.w(TAG, "BluetoothAdapter not initialized"); 451 | return; 452 | } 453 | BluetoothGattService service = 454 | mBluetoothGattMap.get(address).getService(UUID.fromString(serviceUUID)); 455 | BluetoothGattCharacteristic characteristic = 456 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 457 | 458 | mBluetoothGattMap.get(address).setCharacteristicNotification(characteristic, enabled); 459 | 460 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 461 | UUID.fromString(GattAttributes.DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION)); 462 | descriptor.setValue(enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : 463 | BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); 464 | mBluetoothGattMap.get(address).writeDescriptor(descriptor); 465 | 466 | } 467 | 468 | /** 469 | * Reads the value for a given descriptor from the associated remote device. 470 | *

471 | *

Once the read operation has been completed, the 472 | * {@link BluetoothGattCallback#onDescriptorRead} callback is 473 | * triggered, signaling the result of the operation. 474 | *

475 | *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. 476 | * 477 | * @param address The address of remote device 478 | * @param descriptor Descriptor value to read from the remote device 479 | * @return true, if the read operation was initiated successfully 480 | */ 481 | public boolean readDescriptor(String address, BluetoothGattDescriptor descriptor) { 482 | if (mBluetoothGattMap.get(address) == null) { 483 | Log.w(TAG, "BluetoothGatt is null"); 484 | return false; 485 | } 486 | return mBluetoothGattMap.get(address).readDescriptor(descriptor); 487 | } 488 | 489 | /** 490 | * Reads the value for a given descriptor from the associated remote device. 491 | * 492 | * @param address The address of remote device 493 | * @param serviceUUID remote device service uuid 494 | * @param characteristicUUID remote device characteristic uuid 495 | * @param descriptorUUID remote device descriptor uuid 496 | * @return true, if the read operation was initiated successfully 497 | */ 498 | public boolean readDescriptor(String address, String serviceUUID, String characteristicUUID, 499 | String descriptorUUID) { 500 | if (mBluetoothGattMap.get(address) == null) { 501 | Log.w(TAG, "BluetoothGatt is null"); 502 | return false; 503 | } 504 | // try { 505 | BluetoothGattService service = 506 | mBluetoothGattMap.get(address).getService(UUID.fromString(serviceUUID)); 507 | BluetoothGattCharacteristic characteristic = 508 | service.getCharacteristic(UUID.fromString(characteristicUUID)); 509 | BluetoothGattDescriptor descriptor = 510 | characteristic.getDescriptor(UUID.fromString(descriptorUUID)); 511 | return mBluetoothGattMap.get(address).readDescriptor(descriptor); 512 | // } catch (Exception e) { 513 | // Log.e(TAG, "read descriptor exception", e); 514 | // return false; 515 | // } 516 | } 517 | 518 | /** 519 | * Read the RSSI for a connected remote device. 520 | *

521 | *

The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be 522 | * invoked when the RSSI value has been read. 523 | *

524 | *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. 525 | * 526 | * @param address The address of remote device 527 | * @return true, if the RSSI value has been requested successfully 528 | */ 529 | public boolean readRemoteRssi(String address) { 530 | if (mBluetoothGattMap.get(address) == null) return false; 531 | return mBluetoothGattMap.get(address).readRemoteRssi(); 532 | } 533 | 534 | /** 535 | * Request an MTU size used for a given connection. 536 | *

537 | *

When performing a write request operation (write without response), 538 | * the data sent is truncated to the MTU size. This function may be used 539 | * to request a larger MTU size to be able to send more data at once. 540 | *

541 | *

A {@link BluetoothGattCallback#onMtuChanged} callback will indicate 542 | * whether this operation was successful. 543 | *

544 | *

Requires {@link Manifest.permission#BLUETOOTH} permission. 545 | * 546 | * @param address The address of remote device 547 | * @param mtu mtu 548 | * @return true, if the new MTU value has been requested successfully 549 | */ 550 | public boolean requestMtu(String address, int mtu) { 551 | if (mBluetoothGattMap.get(address) == null) return false; 552 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//Android API level >= 21 553 | return mBluetoothGattMap.get(address).requestMtu(mtu); 554 | } else { 555 | return false; 556 | } 557 | } 558 | 559 | public List getConnectDevices() { 560 | if (mBluetoothManager == null) return null; 561 | return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); 562 | } 563 | 564 | /** 565 | * Get connected number of devices at the present. 566 | * 567 | * @return Number of devices currently connected 568 | */ 569 | public int getConnectNum() { 570 | return getConnectDevices().size(); 571 | } 572 | 573 | /** 574 | * Retrieves a list of supported GATT services on the connected device. This should be 575 | * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. 576 | * 577 | * @param address address 578 | * @return A {@code List} of supported services. 579 | */ 580 | public List getSupportedGattServices(String address) { 581 | if (mBluetoothGattMap.get(address) == null) return null; 582 | return mBluetoothGattMap.get(address).getServices(); 583 | } 584 | 585 | /** 586 | * Device scan callback. 587 | *

588 | * Use mScanCallback if Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP, 589 | * else use mLeScanCallback. 590 | */ 591 | private ScanCallback mScanCallback; 592 | private BluetoothAdapter.LeScanCallback mLeScanCallback; 593 | 594 | { 595 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 596 | mScanCallback = new ScanCallback() { 597 | @Override 598 | public void onScanResult(int callbackType, ScanResult result) { 599 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 600 | if (mScanLeDeviceList.contains(result.getDevice())) return; 601 | mScanLeDeviceList.add(result.getDevice()); 602 | if (mOnLeScanListener != null) { 603 | mOnLeScanListener.onLeScan(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes()); 604 | } 605 | broadcastUpdate(ACTION_BLUETOOTH_DEVICE, result.getDevice()); 606 | Log.i(TAG, "onScanResult: name: " + result.getDevice().getName() + 607 | ", address: " + result.getDevice().getAddress() + 608 | ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord()); 609 | } 610 | } 611 | }; 612 | } else { 613 | mLeScanCallback = new BluetoothAdapter.LeScanCallback() { 614 | @Override 615 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 616 | Log.i(TAG, "device name: " + device.getName() + ", address: " + device.getAddress()); 617 | if (device == null || mScanLeDeviceList.contains(device)) return; 618 | mScanLeDeviceList.add(device); 619 | if (mOnLeScanListener != null) { 620 | mOnLeScanListener.onLeScan(device, rssi, scanRecord); 621 | } 622 | broadcastUpdate(ACTION_BLUETOOTH_DEVICE, device); 623 | } 624 | }; 625 | } 626 | } 627 | 628 | /** 629 | * Implements callback methods for GATT events that the app cares about. For example, 630 | * connection change and services discovered. 631 | */ 632 | private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 633 | @Override 634 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 635 | if (mOnConnectionStateChangeListener != null) { 636 | mOnConnectionStateChangeListener.onConnectionStateChange(gatt, status, newState); 637 | } 638 | String intentAction; 639 | String tmpAddress = gatt.getDevice().getAddress(); 640 | if (newState == BluetoothProfile.STATE_DISCONNECTED) { 641 | intentAction = ACTION_GATT_DISCONNECTED; 642 | Log.i(TAG, "Disconnected from GATT server."); 643 | broadcastUpdate(intentAction, tmpAddress); 644 | close(tmpAddress); 645 | } else if (newState == BluetoothProfile.STATE_CONNECTING) { 646 | intentAction = ACTION_GATT_CONNECTING; 647 | Log.i(TAG, "Connecting to GATT server."); 648 | broadcastUpdate(intentAction, tmpAddress); 649 | } else if (newState == BluetoothProfile.STATE_CONNECTED) { 650 | mConnectedAddressList.add(tmpAddress); 651 | intentAction = ACTION_GATT_CONNECTED; 652 | broadcastUpdate(intentAction, tmpAddress); 653 | Log.i(TAG, "Connected to GATT server."); 654 | // Attempts to discover services after successful connection. 655 | Log.i(TAG, "Attempting to start service discovery:" + 656 | mBluetoothGattMap.get(tmpAddress).discoverServices()); 657 | } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { 658 | mConnectedAddressList.remove(tmpAddress); 659 | intentAction = ACTION_GATT_DISCONNECTING; 660 | Log.i(TAG, "Disconnecting from GATT server."); 661 | broadcastUpdate(intentAction, tmpAddress); 662 | } 663 | } 664 | 665 | // New services discovered 666 | @Override 667 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 668 | if (mOnServicesDiscoveredListener != null) { 669 | mOnServicesDiscoveredListener.onServicesDiscovered(gatt, status); 670 | } 671 | if (status == BluetoothGatt.GATT_SUCCESS) { 672 | broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED, gatt.getDevice().getAddress()); 673 | } else { 674 | Log.w(TAG, "onServicesDiscovered received: " + status); 675 | } 676 | } 677 | 678 | // Result of a characteristic read operation 679 | @Override 680 | public void onCharacteristicRead(BluetoothGatt gatt, 681 | BluetoothGattCharacteristic characteristic, int status) { 682 | if (mOnDataAvailableListener != null) { 683 | mOnDataAvailableListener.onCharacteristicRead(gatt, characteristic, status); 684 | } 685 | } 686 | 687 | @Override 688 | public void onCharacteristicWrite(BluetoothGatt gatt, 689 | BluetoothGattCharacteristic characteristic, int status) { 690 | String address = gatt.getDevice().getAddress(); 691 | for (int i = 0; i < characteristic.getValue().length; i++) { 692 | Log.i(TAG, "address: " + address + ",Write: " + characteristic.getValue()[i]); 693 | } 694 | } 695 | 696 | @Override 697 | public void onCharacteristicChanged(BluetoothGatt gatt, 698 | BluetoothGattCharacteristic characteristic) { 699 | if (mOnDataAvailableListener != null) { 700 | mOnDataAvailableListener.onCharacteristicChanged(gatt, characteristic); 701 | } 702 | } 703 | 704 | @Override 705 | public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 706 | if (mOnDataAvailableListener != null) { 707 | mOnDataAvailableListener.onDescriptorRead(gatt, descriptor, status); 708 | } 709 | } 710 | 711 | @Override 712 | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { 713 | if (mOnReadRemoteRssiListener != null) { 714 | mOnReadRemoteRssiListener.onReadRemoteRssi(gatt, rssi, status); 715 | } 716 | } 717 | 718 | @Override 719 | public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { 720 | if (mOnMtuChangedListener != null) { 721 | mOnMtuChangedListener.onMtuChanged(gatt, mtu, status); 722 | } 723 | } 724 | }; 725 | 726 | private void broadcastUpdate(final String action) { 727 | final Intent intent = new Intent(action); 728 | sendBroadcast(intent); 729 | } 730 | 731 | private void broadcastUpdate(final String action, final String address) { 732 | final Intent intent = new Intent(action); 733 | intent.putExtra("address", address); 734 | sendBroadcast(intent); 735 | } 736 | 737 | private void broadcastUpdate(final String action, BluetoothDevice device) { 738 | final Intent intent = new Intent(action); 739 | intent.putExtra("name", device.getName()); 740 | intent.putExtra("address", device.getAddress()); 741 | sendBroadcast(intent); 742 | } 743 | 744 | public void setOnLeScanListener(OnLeScanListener l) { 745 | mOnLeScanListener = l; 746 | } 747 | 748 | public void setOnConnectListener(OnConnectionStateChangeListener l) { 749 | mOnConnectionStateChangeListener = l; 750 | } 751 | 752 | public void setOnServicesDiscoveredListener(OnServicesDiscoveredListener l) { 753 | mOnServicesDiscoveredListener = l; 754 | } 755 | 756 | public void setOnDataAvailableListener(OnDataAvailableListener l) { 757 | mOnDataAvailableListener = l; 758 | } 759 | 760 | public void serOnReadRemoteRssiListener(OnReadRemoteRssiListener l) { 761 | mOnReadRemoteRssiListener = l; 762 | } 763 | 764 | public void setOnMtuChangedListener(OnMtuChangedListener l) { 765 | mOnMtuChangedListener = l; 766 | } 767 | } 768 | -------------------------------------------------------------------------------- /blelib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | BleLib 19 | 20 | -------------------------------------------------------------------------------- /blelib/src/test/java/com/junkchen/blelib/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.junkchen.blelib; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /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.3.2' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.3' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junkchen/BleLib/2764bf50e10a62ee6faec4dbcc56801e85ad7aad/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 31 09:03:55 CST 2017 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-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '26.0.0 rc2' 6 | 7 | defaultConfig { 8 | applicationId "com.junkchen.blelib.sample" 9 | minSdkVersion 18 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0.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 fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | compile project(':blelib') 27 | compile 'com.android.support:design:23.4.0' 28 | compile 'com.android.support:support-v4:23.4.0' 29 | } 30 | -------------------------------------------------------------------------------- /sample/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 D:\ProgramFiles\Android\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 | -------------------------------------------------------------------------------- /sample/screenshots/connect_multiple_devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junkchen/BleLib/2764bf50e10a62ee6faec4dbcc56801e85ad7aad/sample/screenshots/connect_multiple_devices.png -------------------------------------------------------------------------------- /sample/screenshots/connect_single_device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junkchen/BleLib/2764bf50e10a62ee6faec4dbcc56801e85ad7aad/sample/screenshots/connect_single_device.png -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/junkchen/blelib/sample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.junkchen.blelib.sample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/BleScanActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample; 18 | 19 | import android.Manifest; 20 | import android.app.ProgressDialog; 21 | import android.bluetooth.BluetoothGatt; 22 | import android.bluetooth.BluetoothGattCharacteristic; 23 | import android.bluetooth.BluetoothGattService; 24 | import android.content.BroadcastReceiver; 25 | import android.content.ComponentName; 26 | import android.content.Context; 27 | import android.content.Intent; 28 | import android.content.IntentFilter; 29 | import android.content.ServiceConnection; 30 | import android.content.pm.PackageManager; 31 | import android.os.Build; 32 | import android.os.Bundle; 33 | import android.os.Handler; 34 | import android.os.IBinder; 35 | import android.os.Message; 36 | import android.support.annotation.NonNull; 37 | import android.support.v4.app.ActivityCompat; 38 | import android.support.v4.content.ContextCompat; 39 | import android.support.v7.app.AppCompatActivity; 40 | import android.util.Log; 41 | import android.view.View; 42 | import android.widget.AdapterView; 43 | import android.widget.ArrayAdapter; 44 | import android.widget.Button; 45 | import android.widget.ListView; 46 | import android.widget.TextView; 47 | import android.widget.Toast; 48 | 49 | import com.junkchen.blelib.BleService; 50 | import com.junkchen.blelib.sample.adapter.CommonAdapter; 51 | import com.junkchen.blelib.sample.adapter.ViewHolder; 52 | 53 | import java.util.ArrayList; 54 | import java.util.HashMap; 55 | import java.util.List; 56 | import java.util.Map; 57 | 58 | public class BleScanActivity extends AppCompatActivity { 59 | //Debugging 60 | private static final String TAG = BleScanActivity.class.getSimpleName(); 61 | 62 | //Constant 63 | public static final int SERVICE_BIND = 1; 64 | public static final int SERVICE_SHOW = 2; 65 | public static final int REQUEST_CODE_ACCESS_COARSE_LOCATION = 1; 66 | 67 | //Member fields 68 | private BleService mBleService; 69 | private boolean mIsBind; 70 | private CommonAdapter> deviceAdapter; 71 | private ArrayAdapter serviceAdapter; 72 | private List> deviceList; 73 | private String connDeviceName; 74 | private String connDeviceAddress; 75 | 76 | //Layout view 77 | private Button btn_scanBle; 78 | private ListView lstv_devList; 79 | private ListView lstv_showService; 80 | 81 | private ServiceConnection serviceConnection = new ServiceConnection() { 82 | @Override 83 | public void onServiceConnected(ComponentName name, IBinder service) { 84 | mBleService = ((BleService.LocalBinder) service).getService(); 85 | if (mBleService != null) mHandler.sendEmptyMessage(SERVICE_BIND); 86 | if (mBleService.initialize()) { 87 | if (mBleService.enableBluetooth(true)) { 88 | verifyIfRequestPermission(); 89 | Toast.makeText(BleScanActivity.this, "Bluetooth was opened", Toast.LENGTH_SHORT).show(); 90 | } 91 | } else { 92 | Toast.makeText(BleScanActivity.this, "not support Bluetooth", Toast.LENGTH_SHORT).show(); 93 | } 94 | } 95 | 96 | @Override 97 | public void onServiceDisconnected(ComponentName name) { 98 | mBleService = null; 99 | mIsBind = false; 100 | } 101 | }; 102 | 103 | private Handler mHandler = new Handler() { 104 | @Override 105 | public void handleMessage(Message msg) { 106 | switch (msg.what) { 107 | case SERVICE_BIND: 108 | setBleServiceListener(); 109 | break; 110 | case SERVICE_SHOW: 111 | serviceAdapter.notifyDataSetChanged(); 112 | break; 113 | } 114 | } 115 | }; 116 | 117 | @Override 118 | protected void onCreate(Bundle savedInstanceState) { 119 | super.onCreate(savedInstanceState); 120 | setContentView(R.layout.activity_ble_scan); 121 | initView(); 122 | initAdapter(); 123 | registerReceiver(bleReceiver, makeIntentFilter()); 124 | doBindService(); 125 | } 126 | 127 | private void verifyIfRequestPermission() { 128 | if (Build.VERSION.SDK_INT >= 23) { 129 | Log.i(TAG, "onCreate: checkSelfPermission"); 130 | if (ContextCompat.checkSelfPermission(this, 131 | Manifest.permission.ACCESS_COARSE_LOCATION) 132 | != PackageManager.PERMISSION_GRANTED) { 133 | Log.i(TAG, "onCreate: Android 6.0 动态申请权限"); 134 | 135 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, 136 | Manifest.permission.READ_CONTACTS)) { 137 | Log.i(TAG, "*********onCreate: shouldShowRequestPermissionRationale**********"); 138 | Toast.makeText(this, "只有允许访问位置才能搜索到蓝牙设备", Toast.LENGTH_SHORT).show(); 139 | } else { 140 | ActivityCompat.requestPermissions(this, 141 | new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, 142 | Manifest.permission.ACCESS_FINE_LOCATION}, 143 | REQUEST_CODE_ACCESS_COARSE_LOCATION); 144 | } 145 | } else { 146 | showDialog(getResources().getString(R.string.scanning)); 147 | mBleService.scanLeDevice(true); 148 | } 149 | } else { 150 | showDialog(getResources().getString(R.string.scanning)); 151 | mBleService.scanLeDevice(true); 152 | } 153 | } 154 | 155 | @Override 156 | protected void onDestroy() { 157 | super.onDestroy(); 158 | doUnBindService(); 159 | unregisterReceiver(bleReceiver); 160 | } 161 | 162 | @Override 163 | public void onBackPressed() { 164 | if (mBleService.isScanning()) { 165 | mBleService.scanLeDevice(false); 166 | return; 167 | } 168 | super.onBackPressed(); 169 | } 170 | 171 | @Override 172 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 173 | if (requestCode == REQUEST_CODE_ACCESS_COARSE_LOCATION) { 174 | Log.i(TAG, "onRequestPermissionsResult: permissions.length = " + permissions.length + 175 | ", grantResults.length = " + grantResults.length); 176 | if (grantResults.length > 0 177 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 178 | // permission was granted, yay! Do the 179 | // contacts-related task you need to do. 180 | showDialog(getResources().getString(R.string.scanning)); 181 | mBleService.scanLeDevice(true); 182 | } else { 183 | // permission denied, boo! Disable the 184 | // functionality that depends on this permission. 185 | Toast.makeText(BleScanActivity.this, "位置访问权限被拒绝将无法搜索到ble设备", Toast.LENGTH_SHORT).show(); 186 | } 187 | } else { 188 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 189 | } 190 | } 191 | 192 | private void initView() { 193 | btn_scanBle = (Button) findViewById(R.id.btn_scanBle); 194 | lstv_devList = (ListView) findViewById(R.id.lstv_devList); 195 | lstv_showService = (ListView) findViewById(R.id.lstv_showService); 196 | TextView txtv = new TextView(this); 197 | txtv.setText("Services"); 198 | lstv_showService.addHeaderView(txtv); 199 | lstv_showService.setVisibility(View.VISIBLE); 200 | btn_scanBle.setOnClickListener(new View.OnClickListener() { 201 | @Override 202 | public void onClick(View v) { 203 | if (!mBleService.isScanning()) { 204 | verifyIfRequestPermission(); 205 | // mBleService.close(); 206 | deviceList.clear(); 207 | mBleService.scanLeDevice(true); 208 | } 209 | } 210 | }); 211 | lstv_showService.setOnItemClickListener(new AdapterView.OnItemClickListener() { 212 | @Override 213 | public void onItemClick(AdapterView parent, View view, int position, long id) { 214 | Log.i(TAG, "position = " + position + ", id = " + id); 215 | String s = serviceList.get((int) id); 216 | Intent intent = new Intent(BleScanActivity.this, CharacteristicActivity.class); 217 | intent.putExtra("characteristic", characteristicList.get((int) id)); 218 | startActivity(intent); 219 | } 220 | }); 221 | } 222 | 223 | private void initAdapter() { 224 | deviceList = new ArrayList<>(); 225 | deviceAdapter = new CommonAdapter>( 226 | this, R.layout.item_device, deviceList) { 227 | @Override 228 | public void convert(ViewHolder holder, final Map deviceMap) { 229 | holder.setText(R.id.txtv_name, deviceMap.get("name").toString()); 230 | holder.setText(R.id.txtv_address, deviceMap.get("address").toString()); 231 | holder.setText(R.id.txtv_connState, ((boolean) deviceMap.get("isConnect")) ? 232 | getResources().getString(R.string.state_connected) : 233 | getResources().getString(R.string.state_disconnected)); 234 | holder.setText(R.id.btn_connect, ((boolean) deviceMap.get("isConnect")) ? 235 | getResources().getString(R.string.disconnected) : 236 | getResources().getString(R.string.connected)); 237 | holder.getView(R.id.btn_connect).setOnClickListener(new View.OnClickListener() { 238 | @Override 239 | public void onClick(View v) { 240 | if ((boolean) deviceMap.get("isConnect")) { 241 | mBleService.disconnect(); 242 | showDialog(getString(R.string.disconnecting)); 243 | } else { 244 | connDeviceAddress = (String) deviceMap.get("address"); 245 | connDeviceName = (String) deviceMap.get("name"); 246 | HashMap connDevMap = new HashMap(); 247 | connDevMap.put("name", connDeviceName); 248 | connDevMap.put("address", connDeviceAddress); 249 | connDevMap.put("isConnect", false); 250 | deviceList.clear(); 251 | deviceList.add(connDevMap); 252 | deviceAdapter.notifyDataSetChanged(); 253 | mBleService.connect(connDeviceAddress); 254 | showDialog(getString(R.string.connecting)); 255 | } 256 | } 257 | }); 258 | } 259 | }; 260 | lstv_devList.setAdapter(deviceAdapter); 261 | serviceList = new ArrayList<>(); 262 | characteristicList = new ArrayList<>(); 263 | serviceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, serviceList); 264 | lstv_showService.setAdapter(serviceAdapter); 265 | } 266 | 267 | private List gattServiceList; 268 | private List serviceList; 269 | private List characteristicList; 270 | 271 | private void setBleServiceListener() { 272 | mBleService.setOnServicesDiscoveredListener(new BleService.OnServicesDiscoveredListener() { 273 | @Override 274 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 275 | if (status == BluetoothGatt.GATT_SUCCESS) { 276 | gattServiceList = gatt.getServices(); 277 | serviceList.clear(); 278 | for (BluetoothGattService service : 279 | gattServiceList) { 280 | String serviceUuid = service.getUuid().toString(); 281 | serviceList.add(MyGattAttributes.lookup(serviceUuid, "Unknown") + "\n" + serviceUuid); 282 | Log.i(TAG, MyGattAttributes.lookup(serviceUuid, "Unknown") + "\n" + serviceUuid); 283 | 284 | List characteristics = service.getCharacteristics(); 285 | String[] charArra = new String[characteristics.size()]; 286 | for (int i = 0; i < characteristics.size(); i++) { 287 | String charUuid = characteristics.get(i).getUuid().toString(); 288 | charArra[i] = MyGattAttributes.lookup(charUuid, "Unknown") + "\n" + charUuid; 289 | } 290 | characteristicList.add(charArra); 291 | } 292 | mHandler.sendEmptyMessage(SERVICE_SHOW); 293 | } 294 | } 295 | }); 296 | // //Ble扫描回调 297 | // mBleService.setOnLeScanListener(new BleService.OnLeScanListener() { 298 | // @Override 299 | // public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 300 | // //每当扫描到一个Ble设备时就会返回,(扫描结果重复的库中已处理) 301 | // } 302 | // }); 303 | // //Ble连接回调 304 | // mBleService.setOnConnectListener(new BleService.OnConnectListener() { 305 | // @Override 306 | // public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 307 | // if (newState == BluetoothProfile.STATE_DISCONNECTED) { 308 | // //Ble连接已断开 309 | // } else if (newState == BluetoothProfile.STATE_CONNECTING) { 310 | // //Ble正在连接 311 | // } else if (newState == BluetoothProfile.STATE_CONNECTED) { 312 | // //Ble已连接 313 | // } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { 314 | // //Ble正在断开连接 315 | // } 316 | // } 317 | // }); 318 | // //Ble服务发现回调 319 | // mBleService.setOnServicesDiscoveredListener(new BleService.OnServicesDiscoveredListener() { 320 | // @Override 321 | // public void onServicesDiscovered(BluetoothGatt gatt, int status) { 322 | // 323 | // } 324 | // }); 325 | // //Ble数据回调 326 | // mBleService.setOnDataAvailableListener(new BleService.OnDataAvailableListener() { 327 | // @Override 328 | // public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 329 | // //处理特性读取返回的数据 330 | // } 331 | // 332 | // @Override 333 | // public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 334 | // //处理通知返回的数据 335 | // } 336 | // @Override 337 | // public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 338 | // 339 | // } 340 | // }); 341 | 342 | mBleService.setOnReadRemoteRssiListener(new BleService.OnReadRemoteRssiListener() { 343 | @Override 344 | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { 345 | Log.i(TAG, "onReadRemoteRssi: rssi = " + rssi); 346 | } 347 | }); 348 | } 349 | 350 | private void doOperation() { 351 | // mBleService.initialize();//Ble初始化操作 352 | // mBleService.enableBluetooth(boolean enable);//打开或关闭蓝牙 353 | // mBleService.scanLeDevice(boolean enable, long scanPeriod);//启动或停止扫描Ble设备 354 | // mBleService.connect(String address);//连接Ble 355 | // mBleService.disconnect();//取消连接 356 | // mBleService.getSupportedGattServices();//获取服务 357 | // mBleService.setCharacteristicNotification(BluetoothGattCharacteristic characteristic, 358 | // boolean enabled);//设置通知 359 | // mBleService.readCharacteristic(BluetoothGattCharacteristic characteristic);//读取数据 360 | // mBleService.writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value);//写入数据 361 | // mBleService.close();//关闭客户端 362 | } 363 | 364 | /** 365 | * 绑定服务 366 | */ 367 | private void doBindService() { 368 | Intent serviceIntent = new Intent(this, BleService.class); 369 | bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); 370 | } 371 | 372 | /** 373 | * 解绑服务 374 | */ 375 | private void doUnBindService() { 376 | if (mIsBind) { 377 | unbindService(serviceConnection); 378 | mBleService = null; 379 | mIsBind = false; 380 | } 381 | } 382 | 383 | private BroadcastReceiver bleReceiver = new BroadcastReceiver() { 384 | @Override 385 | public void onReceive(Context context, Intent intent) { 386 | if (intent.getAction().equals(BleService.ACTION_BLUETOOTH_DEVICE)) { 387 | String tmpDevName = intent.getStringExtra("name"); 388 | String tmpDevAddress = intent.getStringExtra("address"); 389 | Log.i(TAG, "name: " + tmpDevName + ", address: " + tmpDevAddress); 390 | HashMap deviceMap = new HashMap<>(); 391 | deviceMap.put("name", tmpDevName); 392 | deviceMap.put("address", tmpDevAddress); 393 | deviceMap.put("isConnect", false); 394 | deviceList.add(deviceMap); 395 | deviceAdapter.notifyDataSetChanged(); 396 | } else if (intent.getAction().equals(BleService.ACTION_GATT_CONNECTED)) { 397 | deviceList.get(0).put("isConnect", true); 398 | deviceAdapter.notifyDataSetChanged(); 399 | dismissDialog(); 400 | } else if (intent.getAction().equals(BleService.ACTION_GATT_DISCONNECTED)) { 401 | deviceList.get(0).put("isConnect", false); 402 | serviceList.clear(); 403 | characteristicList.clear(); 404 | deviceAdapter.notifyDataSetChanged(); 405 | serviceAdapter.notifyDataSetChanged(); 406 | dismissDialog(); 407 | } else if (intent.getAction().equals(BleService.ACTION_SCAN_FINISHED)) { 408 | btn_scanBle.setEnabled(true); 409 | dismissDialog(); 410 | } 411 | } 412 | }; 413 | 414 | private static IntentFilter makeIntentFilter() { 415 | final IntentFilter intentFilter = new IntentFilter(); 416 | intentFilter.addAction(BleService.ACTION_BLUETOOTH_DEVICE); 417 | intentFilter.addAction(BleService.ACTION_GATT_CONNECTED); 418 | intentFilter.addAction(BleService.ACTION_GATT_DISCONNECTED); 419 | intentFilter.addAction(BleService.ACTION_SCAN_FINISHED); 420 | return intentFilter; 421 | } 422 | 423 | /** 424 | * Show dialog 425 | */ 426 | private ProgressDialog progressDialog; 427 | 428 | private void showDialog(String message) { 429 | progressDialog = new ProgressDialog(this); 430 | progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); 431 | progressDialog.setCanceledOnTouchOutside(false); 432 | progressDialog.setMessage(message); 433 | progressDialog.show(); 434 | } 435 | 436 | private void dismissDialog() { 437 | if (progressDialog == null) return; 438 | progressDialog.dismiss(); 439 | progressDialog = null; 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/CharacteristicActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.app.AppCompatActivity; 21 | import android.widget.ArrayAdapter; 22 | import android.widget.ListView; 23 | 24 | public class CharacteristicActivity extends AppCompatActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_characteristic); 30 | String[] characteristics = getIntent().getStringArrayExtra("characteristic"); 31 | ((ListView) findViewById(R.id.lstv_showChar)).setAdapter( 32 | new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, characteristics)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample; 18 | 19 | import android.content.Intent; 20 | import android.support.v7.app.AppCompatActivity; 21 | import android.os.Bundle; 22 | import android.view.View; 23 | 24 | public class MainActivity extends AppCompatActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | } 31 | 32 | public void doClick(View v) { 33 | switch (v.getId()) { 34 | case R.id.btn_singleBle: 35 | startActivity(new Intent(this, BleScanActivity.class)); 36 | break; 37 | case R.id.btn_multipleBle: 38 | startActivity(new Intent(this, MultipleBleActivity.class)); 39 | break; 40 | } 41 | } 42 | 43 | @Override 44 | protected void onDestroy() { 45 | super.onDestroy(); 46 | android.os.Process.killProcess(android.os.Process.myPid()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/MultipleBleActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample; 18 | 19 | import android.Manifest; 20 | import android.app.ProgressDialog; 21 | import android.bluetooth.BluetoothGatt; 22 | import android.bluetooth.BluetoothGattCharacteristic; 23 | import android.bluetooth.BluetoothGattDescriptor; 24 | import android.bluetooth.BluetoothProfile; 25 | import android.content.BroadcastReceiver; 26 | import android.content.ComponentName; 27 | import android.content.Context; 28 | import android.content.Intent; 29 | import android.content.IntentFilter; 30 | import android.content.ServiceConnection; 31 | import android.content.pm.PackageManager; 32 | import android.os.Build; 33 | import android.os.Bundle; 34 | import android.os.Handler; 35 | import android.os.IBinder; 36 | import android.os.Message; 37 | import android.support.annotation.NonNull; 38 | import android.support.v4.app.ActivityCompat; 39 | import android.support.v4.content.ContextCompat; 40 | import android.support.v7.app.AppCompatActivity; 41 | import android.util.Log; 42 | import android.view.View; 43 | import android.widget.Button; 44 | import android.widget.ListView; 45 | import android.widget.TextView; 46 | import android.widget.Toast; 47 | 48 | import com.junkchen.blelib.BleService; 49 | import com.junkchen.blelib.MultipleBleService; 50 | import com.junkchen.blelib.sample.adapter.CommonAdapter; 51 | import com.junkchen.blelib.sample.adapter.ViewHolder; 52 | 53 | import java.util.ArrayList; 54 | import java.util.HashMap; 55 | import java.util.List; 56 | import java.util.Map; 57 | 58 | public class MultipleBleActivity extends AppCompatActivity { 59 | //Debugging 60 | private static final String TAG = MultipleBleActivity.class.getSimpleName(); 61 | 62 | //Constant 63 | public static final int SERVICE_BIND = 1; 64 | public static final int CONNECT_CHANGE = 2; 65 | public static final int REQUEST_CODE_ACCESS_COARSE_LOCATION = 1; 66 | 67 | //Member fields 68 | private boolean mIsBind; 69 | private MultipleBleService mBleService; 70 | private CommonAdapter> deviceAdapter; 71 | private List> deviceList; 72 | private String connDeviceName; 73 | private String connDeviceAddress; 74 | 75 | //Layout view 76 | private Button btn_scanBle; 77 | private TextView txtv_connNum; 78 | private ListView lstv_devList; 79 | 80 | private ServiceConnection serviceConnection = new ServiceConnection() { 81 | @Override 82 | public void onServiceConnected(ComponentName name, IBinder service) { 83 | mBleService = ((MultipleBleService.LocalBinder) service).getService(); 84 | if (mBleService != null) mHandler.sendEmptyMessage(SERVICE_BIND); 85 | if (mBleService.initialize()) { 86 | if (mBleService.enableBluetooth(true)) { 87 | handleVersionPermission(); 88 | Toast.makeText(MultipleBleActivity.this, "Bluetooth was opened", Toast.LENGTH_SHORT).show(); 89 | } 90 | } else { 91 | Toast.makeText(MultipleBleActivity.this, "not support Bluetooth", Toast.LENGTH_SHORT).show(); 92 | } 93 | } 94 | 95 | @Override 96 | public void onServiceDisconnected(ComponentName name) { 97 | mBleService = null; 98 | mIsBind = false; 99 | } 100 | }; 101 | 102 | private Handler mHandler = new Handler() { 103 | @Override 104 | public void handleMessage(Message msg) { 105 | switch (msg.what) { 106 | case SERVICE_BIND: 107 | setBleServiceListener(); 108 | break; 109 | case CONNECT_CHANGE: 110 | deviceAdapter.notifyDataSetChanged(); 111 | txtv_connNum.setText(getString(R.string.dev_conn_number) + 112 | mBleService.getConnectDevices().size()); 113 | Log.i(TAG, "handleMessage: " + mBleService.getConnectDevices().toString()); 114 | break; 115 | } 116 | } 117 | }; 118 | 119 | @Override 120 | protected void onCreate(Bundle savedInstanceState) { 121 | super.onCreate(savedInstanceState); 122 | setContentView(R.layout.activity_ble_scan); 123 | initView(); 124 | initAdapter(); 125 | registerReceiver(bleReceiver, makeIntentFilter()); 126 | doBindService(); 127 | } 128 | 129 | @Override 130 | protected void onStop() { 131 | super.onStop(); 132 | doUnBindService(); 133 | unregisterReceiver(bleReceiver); 134 | } 135 | 136 | @Override 137 | public void onBackPressed() { 138 | if (mBleService.isScanning()) { 139 | mBleService.scanLeDevice(false); 140 | return; 141 | } 142 | super.onBackPressed(); 143 | } 144 | 145 | @Override 146 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 147 | Log.i(TAG, "onRequestPermissionsResult: p[0] = " + permissions[0] + ", g[0] = " + grantResults[0]); 148 | switch (requestCode) { 149 | case REQUEST_CODE_ACCESS_COARSE_LOCATION: { 150 | // If request is cancelled, the result arrays are empty. 151 | if (grantResults.length > 0 152 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 153 | // permission was granted, yay! Do the 154 | // contacts-related task you need to do. 155 | showDialog(getResources().getString(R.string.scanning)); 156 | mBleService.scanLeDevice(true); 157 | } else { 158 | // permission denied, boo! Disable the 159 | // functionality that depends on this permission. 160 | Toast.makeText(MultipleBleActivity.this, "位置访问权限被拒绝将无法搜索到ble设备", Toast.LENGTH_SHORT).show(); 161 | } 162 | break; 163 | } 164 | // other 'case' lines to check for other 165 | // permissions this app might request 166 | default: 167 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 168 | } 169 | } 170 | 171 | private void handleVersionPermission() { 172 | if (Build.VERSION.SDK_INT >= 23) { 173 | Log.i(TAG, "onCreate: checkSelfPermission"); 174 | 175 | int checkSelfPermission = ContextCompat.checkSelfPermission(MultipleBleActivity.this, 176 | Manifest.permission.ACCESS_COARSE_LOCATION); 177 | Log.i(TAG, "handleVersionPermission: checkSelfPermission = " + checkSelfPermission); 178 | if (checkSelfPermission != PackageManager.PERMISSION_GRANTED) { 179 | Log.i(TAG, "onCreate: Android 6.0 动态申请权限"); 180 | 181 | boolean showRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(MultipleBleActivity.this, 182 | Manifest.permission.ACCESS_COARSE_LOCATION); 183 | Log.i(TAG, "handleVersionPermission: showRequestPermissionRationale = " + showRequestPermissionRationale); 184 | if (showRequestPermissionRationale) { 185 | Log.i(TAG, "*********onCreate: shouldShowRequestPermissionRationale**********"); 186 | Toast.makeText(MultipleBleActivity.this, "只有允许访问位置才能搜索到蓝牙设备", Toast.LENGTH_SHORT).show(); 187 | } 188 | ActivityCompat.requestPermissions(MultipleBleActivity.this, 189 | new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 190 | REQUEST_CODE_ACCESS_COARSE_LOCATION); 191 | } else { 192 | Log.i(TAG, "handleVersionPermission: scanning..."); 193 | showDialog(getResources().getString(R.string.scanning)); 194 | mBleService.scanLeDevice(true); 195 | } 196 | } else { 197 | Log.i(TAG, "handleVersionPermission: scanning..."); 198 | showDialog(getResources().getString(R.string.scanning)); 199 | mBleService.scanLeDevice(true); 200 | } 201 | } 202 | 203 | private void initView() { 204 | btn_scanBle = (Button) findViewById(R.id.btn_scanBle); 205 | txtv_connNum = (TextView) findViewById(R.id.txtv_connNum); 206 | lstv_devList = (ListView) findViewById(R.id.lstv_devList); 207 | 208 | txtv_connNum.setVisibility(View.VISIBLE); 209 | 210 | btn_scanBle.setOnClickListener(new View.OnClickListener() { 211 | @Override 212 | public void onClick(View v) { 213 | if (!mBleService.isScanning()) { 214 | mBleService.close(); 215 | deviceList.clear(); 216 | handleVersionPermission(); 217 | } 218 | } 219 | }); 220 | } 221 | 222 | private void initAdapter() { 223 | deviceList = new ArrayList<>(); 224 | deviceAdapter = new CommonAdapter>( 225 | this, R.layout.item_device, deviceList) { 226 | @Override 227 | public void convert(ViewHolder holder, final Map deviceMap) { 228 | holder.setText(R.id.txtv_name, deviceMap.get("name").toString()); 229 | holder.setText(R.id.txtv_address, deviceMap.get("address").toString()); 230 | holder.setText(R.id.txtv_connState, ((boolean) deviceMap.get("isConnect")) ? 231 | getResources().getString(R.string.state_connected) : 232 | getResources().getString(R.string.state_disconnected)); 233 | holder.setText(R.id.btn_connect, ((boolean) deviceMap.get("isConnect")) ? 234 | getResources().getString(R.string.disconnected) : 235 | getResources().getString(R.string.connected)); 236 | holder.getView(R.id.btn_connect).setOnClickListener(new View.OnClickListener() { 237 | @Override 238 | public void onClick(View v) { 239 | if ((boolean) deviceMap.get("isConnect")) { 240 | mBleService.disconnect(deviceMap.get("address").toString()); 241 | showDialog(getString(R.string.disconnecting)); 242 | } else { 243 | connDeviceAddress = (String) deviceMap.get("address"); 244 | connDeviceName = (String) deviceMap.get("name"); 245 | HashMap connDevMap = new HashMap(); 246 | connDevMap.put("name", connDeviceName); 247 | connDevMap.put("address", connDeviceAddress); 248 | connDevMap.put("isConnect", false); 249 | mBleService.connect(connDeviceAddress); 250 | showDialog(getString(R.string.connecting)); 251 | } 252 | } 253 | }); 254 | } 255 | }; 256 | lstv_devList.setAdapter(deviceAdapter); 257 | } 258 | 259 | private void setBleServiceListener() { 260 | mBleService.setOnConnectListener(new MultipleBleService.OnConnectionStateChangeListener() { 261 | @Override 262 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 263 | if (newState == BluetoothProfile.STATE_DISCONNECTED) { 264 | for (int i = 0; i < deviceList.size(); i++) { 265 | HashMap devMap = (HashMap) deviceList.get(i); 266 | if (devMap.get("address").toString().equals(gatt.getDevice().getAddress())) { 267 | ((HashMap) deviceList.get(i)).put("isConnect", false); 268 | return; 269 | } 270 | } 271 | } else if (newState == BluetoothProfile.STATE_CONNECTING) { 272 | 273 | } else if (newState == BluetoothProfile.STATE_CONNECTED) { 274 | for (int i = 0; i < deviceList.size(); i++) { 275 | HashMap devMap = (HashMap) deviceList.get(i); 276 | if (devMap.get("address").toString().equals(gatt.getDevice().getAddress())) { 277 | ((HashMap) deviceList.get(i)).put("isConnect", true); 278 | return; 279 | } 280 | } 281 | } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { 282 | 283 | } 284 | } 285 | }); 286 | mBleService.setOnDataAvailableListener(new MultipleBleService.OnDataAvailableListener() { 287 | @Override 288 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 289 | 290 | } 291 | 292 | @Override 293 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 294 | 295 | } 296 | 297 | @Override 298 | public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 299 | 300 | } 301 | }); 302 | } 303 | 304 | private void doBindService() { 305 | Intent serviceIntent = new Intent(this, MultipleBleService.class); 306 | bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); 307 | } 308 | 309 | private void doUnBindService() { 310 | if (mIsBind) { 311 | unbindService(serviceConnection); 312 | mBleService = null; 313 | mIsBind = false; 314 | } 315 | } 316 | 317 | protected BroadcastReceiver bleReceiver = new BroadcastReceiver() { 318 | @Override 319 | public void onReceive(Context context, Intent intent) { 320 | if (intent.getAction().equals(BleService.ACTION_BLUETOOTH_DEVICE)) { 321 | String tmpDevName = intent.getStringExtra("name"); 322 | String tmpDevAddress = intent.getStringExtra("address"); 323 | Log.i(TAG, "name: " + tmpDevName + ", address: " + tmpDevAddress); 324 | HashMap deviceMap = new HashMap<>(); 325 | deviceMap.put("name", tmpDevName); 326 | deviceMap.put("address", tmpDevAddress); 327 | deviceMap.put("isConnect", false); 328 | deviceList.add(deviceMap); 329 | deviceAdapter.notifyDataSetChanged(); 330 | } else if (intent.getAction().equals(BleService.ACTION_GATT_CONNECTED)) { 331 | Log.i(TAG, "onReceive: CONNECTED: " + mBleService.getConnectDevices().size()); 332 | mHandler.sendEmptyMessage(CONNECT_CHANGE); 333 | dismissDialog(); 334 | } else if (intent.getAction().equals(BleService.ACTION_GATT_DISCONNECTED)) { 335 | Log.i(TAG, "onReceive: DISCONNECTED: " + mBleService.getConnectDevices().size()); 336 | mHandler.sendEmptyMessage(CONNECT_CHANGE); 337 | dismissDialog(); 338 | } else if (intent.getAction().equals(BleService.ACTION_SCAN_FINISHED)) { 339 | btn_scanBle.setEnabled(true); 340 | dismissDialog(); 341 | } 342 | } 343 | }; 344 | 345 | private static IntentFilter makeIntentFilter() { 346 | final IntentFilter intentFilter = new IntentFilter(); 347 | intentFilter.addAction(BleService.ACTION_BLUETOOTH_DEVICE); 348 | intentFilter.addAction(BleService.ACTION_GATT_CONNECTED); 349 | intentFilter.addAction(BleService.ACTION_GATT_DISCONNECTED); 350 | intentFilter.addAction(BleService.ACTION_SCAN_FINISHED); 351 | return intentFilter; 352 | } 353 | 354 | /** 355 | * Show dialog 356 | */ 357 | private ProgressDialog progressDialog; 358 | 359 | private void showDialog(String message) { 360 | progressDialog = new ProgressDialog(this); 361 | progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); 362 | progressDialog.setCanceledOnTouchOutside(false); 363 | progressDialog.setMessage(message); 364 | progressDialog.show(); 365 | } 366 | 367 | private void dismissDialog() { 368 | if (progressDialog == null) return; 369 | progressDialog.dismiss(); 370 | progressDialog = null; 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/MyGattAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample; 18 | 19 | import com.junkchen.blelib.GattAttributes; 20 | 21 | /** 22 | * Created by JunkChen on 2016/3/14 0014. 23 | */ 24 | public class MyGattAttributes extends GattAttributes { 25 | //GATT Characteristics 26 | public static final String CHARACTERISTIC_CONFIG_CONTROL = "33221111-5544-7766-9988-AABBCCDDEEFF"; 27 | public static final String CHARACTERISTIC_CONFIG_PASSWORD = "33221112-5544-7766-9988-AABBCCDDEEFF"; 28 | public static final String CHARACTERISTIC_CONFIG_STATUS = "33221113-5544-7766-9988-AABBCCDDEEFF"; 29 | 30 | static { 31 | // Characteristics name. 32 | attributes.put(CHARACTERISTIC_CONFIG_CONTROL, "Config Control"); 33 | attributes.put(CHARACTERISTIC_CONFIG_PASSWORD, "Config Password"); 34 | attributes.put(CHARACTERISTIC_CONFIG_STATUS, "Config Status"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/adapter/CommonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.junkchen.blelib.sample.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | 9 | import java.util.List; 10 | 11 | public abstract class CommonAdapter extends BaseAdapter { 12 | protected List datas; 13 | protected Context context; 14 | protected LayoutInflater mInflater; 15 | protected int layoutId; 16 | 17 | public CommonAdapter(Context context, int layoutId, List datas) { 18 | this.context = context; 19 | this.layoutId = layoutId; 20 | this.datas = datas; 21 | mInflater = LayoutInflater.from(context); 22 | } 23 | 24 | /** 25 | * Returns the number of elements in this {@code List}. 26 | */ 27 | @Override 28 | public int getCount() { 29 | return datas.size(); 30 | } 31 | 32 | /** 33 | * Returns the element at the specified location in this {@code List}. 34 | */ 35 | @Override 36 | public T getItem(int position) { 37 | return datas.get(position); 38 | } 39 | 40 | /** 41 | * Get the row id associated with the specified position in the list. 42 | */ 43 | @Override 44 | public long getItemId(int position) { 45 | return position; 46 | } 47 | 48 | /** 49 | * Get show view in item. 50 | */ 51 | @Override 52 | public View getView(int position, View convertView, ViewGroup parent) { 53 | ViewHolder holder = ViewHolder.get(context, convertView, parent, 54 | layoutId, position); 55 | convert(holder, getItem(position)); 56 | return holder.getConvertView(); 57 | } 58 | 59 | /** 60 | * @return 返回false可禁止ListView中item被点击 61 | */ 62 | @Override 63 | public boolean areAllItemsEnabled() { 64 | return super.areAllItemsEnabled(); 65 | // return false; 66 | } 67 | 68 | /** 69 | * @param position 70 | * @return 返回false可禁止ListView中item被点击 71 | */ 72 | @Override 73 | public boolean isEnabled(int position) { 74 | return super.isEnabled(position); 75 | // return false; 76 | } 77 | 78 | 79 | public abstract void convert(ViewHolder holder, T t); 80 | } 81 | -------------------------------------------------------------------------------- /sample/src/main/java/com/junkchen/blelib/sample/adapter/ViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Junk Chen 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.junkchen.blelib.sample.adapter; 18 | 19 | import android.content.Context; 20 | import android.graphics.Bitmap; 21 | import android.net.Uri; 22 | import android.util.SparseArray; 23 | import android.view.LayoutInflater; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | 29 | /** 30 | * Custom common ViewHolder class 31 | * 32 | */ 33 | public class ViewHolder { 34 | private SparseArray mViews; 35 | private int position; 36 | private View convertView; 37 | 38 | public ViewHolder(Context context, ViewGroup parent, int layoutId, 39 | int position) { 40 | this.position = position; 41 | mViews = new SparseArray<>(); 42 | convertView = LayoutInflater.from(context).inflate(layoutId, parent, false); 43 | convertView.setTag(this); 44 | } 45 | 46 | public static ViewHolder get(Context context, View convertView, 47 | ViewGroup parent, int layoutId, int position) { 48 | if (convertView == null) { 49 | return new ViewHolder(context, parent, layoutId, position); 50 | } else { 51 | ViewHolder holder = (ViewHolder) convertView.getTag(); 52 | holder.position = position; 53 | return holder; 54 | } 55 | } 56 | 57 | /** 58 | * Get view by viewID 59 | * 60 | * @param viewId 61 | * @return 62 | */ 63 | public T getView(int viewId) { 64 | View view = mViews.get(viewId); 65 | if (view == null) { 66 | view = convertView.findViewById(viewId); 67 | mViews.put(viewId, view); 68 | } 69 | return (T) view; 70 | } 71 | 72 | public int getPosition() { 73 | return position; 74 | } 75 | 76 | public View getConvertView() { 77 | return convertView; 78 | } 79 | 80 | /** 81 | * Set show text for TextView. 82 | * 83 | * @param viewId 84 | * @param text 85 | * @return 86 | */ 87 | public ViewHolder setText(int viewId, String text) { 88 | TextView tv = getView(viewId); 89 | tv.setText(text); 90 | return this; 91 | } 92 | 93 | /** 94 | * Get widget text content by widget id. 95 | * 96 | * @param viewId 97 | * @return 98 | */ 99 | public String getText(int viewId) { 100 | TextView textView = getView(viewId); 101 | return textView.getText().toString(); 102 | } 103 | 104 | /** 105 | * Set image resource for ImageView. 106 | * 107 | * @param viewId 108 | * @param resId 109 | * @return 110 | */ 111 | public ViewHolder setImageResource(int viewId, int resId) { 112 | ImageView iv = getView(viewId); 113 | iv.setImageResource(resId); 114 | return this; 115 | } 116 | 117 | public ViewHolder setImageBitmap(int viewId, Bitmap bm) { 118 | ImageView iv = getView(viewId); 119 | iv.setImageBitmap(bm); 120 | return this; 121 | } 122 | 123 | public ViewHolder setImageURI(int viewId, Uri uri) { 124 | ImageView iv = getView(viewId); 125 | iv.setImageURI(uri);//ImageLoader.getInstance().loadImg(view,url); 126 | return this; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_ble_scan.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 |