├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bluetooth │ │ └── communicator │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── bluetooth │ │ └── communicator │ │ ├── BluetoothCommunicator.java │ │ ├── BluetoothConnection.java │ │ ├── BluetoothConnectionClient.java │ │ ├── BluetoothConnectionServer.java │ │ ├── BluetoothMessage.java │ │ ├── Channel.java │ │ ├── ClientChannel.java │ │ ├── Message.java │ │ ├── Peer.java │ │ ├── ServerChannel.java │ │ └── tools │ │ ├── BluetoothTools.java │ │ ├── CustomCountDownTimer.java │ │ └── Timer.java │ └── test │ └── java │ └── com │ └── bluetooth │ └── communicator │ └── ExampleUnitTest.java ├── apptest ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bluetooth │ │ └── communicatorexample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── bluetooth │ │ │ └── communicatorexample │ │ │ ├── Global.java │ │ │ ├── MainActivity.java │ │ │ ├── fragments │ │ │ ├── ConversationFragment.java │ │ │ └── PairingFragment.java │ │ │ ├── gui │ │ │ ├── ButtonSearch.java │ │ │ ├── CustomAnimator.java │ │ │ ├── GuiTools.java │ │ │ ├── MessagesAdapter.java │ │ │ ├── PeerListAdapter.java │ │ │ └── RequestDialog.java │ │ │ └── tools │ │ │ ├── Timer.java │ │ │ └── Tools.java │ └── res │ │ ├── anim │ │ ├── dwindle_icon.xml │ │ └── enlarge_icon.xml │ │ ├── drawable-v24 │ │ ├── cancel_icon.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── search_icon.xml │ │ └── send_icon.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── component_message_received.xml │ │ ├── component_message_send.xml │ │ ├── component_row.xml │ │ ├── dialog_connection_request.xml │ │ ├── fragment_conversation.xml │ │ └── fragment_pairing.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── bluetooth │ └── communicatorexample │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## BluetoothCommunicator 2 | BluetoothCommunicator is an open source library that, using Bluetooth Low Energy, allows you to communicate in P2P mode between two or more android devices in a very simple way.

3 | BluetoothCommunicator was originally created for RTranslator but can be used in any more generic case where a P2P communication system is needed between two or more android devices (up to about 4 with a direct connection between all devices, even more with a star structure), for an example app see BluetoothCommunicatorExample or RTranslator

4 | 5 |

6 | 7 | 8 | 9 |

10 | 11 | ### Features 12 | BluetoothCommunicator automatically implements (they are active by default): 13 | - reconnection in case of temporary connection loss. 14 | - reliable message sending: 15 | 16 | - splitting and rebuilding of long messages. 17 | 18 | - sending messages with a queue in order to always send the messages even in case of connection problems (they will be sent as soon as the connection is restored) and in the right order.


19 | 20 | ### Tutorial 21 | For use the library in a project you have to add jitpack.io to your root build.gradle (project): 22 | ``` 23 | allprojects { 24 | repositories { 25 | ... 26 | maven { url 'https://jitpack.io' } 27 | } 28 | } 29 | ``` 30 | Then add the last version of BluetoothCommunicator to your app build.gradle 31 | ``` 32 | dependencies { 33 | implementation 'com.github.niedev:BluetoothCommunicator:1.0.6' 34 | } 35 | ``` 36 | 37 | To use this library add these permissions to your manifest: 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | If you need to use bluetooth advertising or search in background you need to add also the following permission: 45 | ``` 46 | 47 | ``` 48 | 49 | Then add android:largeHeap="true" to the application tag in the manifest:
50 | Example 51 | ``` 52 | 53 | 54 | 55 | 56 | 57 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ``` 76 | 77 | After the installation of the library and the changes to the manifest is time to write the code: create a bluetooth communicator object, it is the object that handles all operations of bluetooth low energy library, if you want to manage the bluetooth connections in multiple activities I suggest you to save this object as an attribute of a custom class that extends Application and create a getter so you can access to bluetoothCommunicator from any activity or service with: 78 | ``` 79 | ((custom class name) getApplication()).getBluetoothCommunicator(); 80 | ``` 81 | Next step is to initialize bluetoothCommunicator, the parameters are: a context, the name by which the other devices will see us (limited to 18 characters and can be only characters listed in BluetoothTools.getSupportedUTFCharacters(context) because the number of bytes for advertising beacon is limited) and the strategy (for now the only supported strategy is BluetoothCommunicator.STRATEGY_P2P_WITH_RECONNECTION) 82 | ``` 83 | bluetoothCommunicator = new BluetoothCommunicator(this, "device name", BluetoothCommunicator.STRATEGY_P2P_WITH_RECONNECTION); 84 | ``` 85 | Then add the bluetooth communicator callback, the callback will listen for all events of bluetooth communicator: 86 | ``` 87 | bluetoothCommunicator.addCallback(new BluetoothCommunicator.Callback() { 88 | @Override 89 | public void onBluetoothLeNotSupported() { 90 | super.onBluetoothLeNotSupported(); 91 | 92 | Notify that bluetooth low energy is not compatible with this device 93 | } 94 | 95 | @Override 96 | public void onAdvertiseStarted() { 97 | super.onAdvertiseStarted(); 98 | 99 | Notify that advertise has started, if you want to do something after the start of advertising do it here, because 100 | after startAdvertise there is no guarantee that advertise is really started (it is delayed) 101 | } 102 | 103 | @Override 104 | public void onDiscoveryStarted() { 105 | super.onDiscoveryStarted(); 106 | 107 | Notify that discovery has started, if you want to do something after the start of discovery do it here, because 108 | after startDiscovery there is no guarantee that discovery is really started (it is delayed) 109 | } 110 | 111 | @Override 112 | public void onAdvertiseStopped() { 113 | super.onAdvertiseStopped(); 114 | 115 | Notify that advertise has stopped, if you want to do something after the stop of advertising do it here, because 116 | after stopAdvertising there is no guarantee that advertise is really stopped (it is delayed) 117 | } 118 | 119 | @Override 120 | public void onDiscoveryStopped() { 121 | super.onDiscoveryStopped(); 122 | 123 | Notify that discovery has stopped, if you want to do something after the stop of discovery do it here, because 124 | after stopDiscovery there is no guarantee that discovery is really stopped (it is delayed) 125 | } 126 | 127 | @Override 128 | public void onPeerFound(Peer peer) { 129 | super.onPeerFound(peer); 130 | 131 | Here for example you can save peer in a list or anywhere you want and when the user 132 | choose a peer you can call bluetoothCommunicator.connect(peer founded) but if you want to 133 | use a peer for connect you have to have peer updated (see onPeerUpdated or onPeerLost), if you use a 134 | non updated peer the connection might fail 135 | instead if you want to immediate connect where peer is found you can call bluetoothCommunicator.connect(peer) here 136 | } 137 | 138 | @Override 139 | public void onPeerLost(Peer peer){ 140 | super.onPeerLost(peer); 141 | 142 | It means that a peer is out of range or has interrupted the advertise, 143 | here you can delete the peer lost from a eventual collection of founded peers 144 | } 145 | 146 | @Override 147 | public void onPeerUpdated(Peer peer,Peer newPeer){ 148 | super.onPeerUpdated(peer,newPeer); 149 | 150 | It means that a founded peer (or connected peer) has changed (name or address or other things), 151 | if you have a collection of founded peers, you need to replace peer with newPeer if you want to connect successfully to that peer. 152 | 153 | In case the peer updated is connected and you have saved connected peers you have to update the peer if you want to successfully 154 | send a message or a disconnection request to that peer. 155 | } 156 | 157 | @Override 158 | public void onConnectionRequest(Peer peer){ 159 | super.onConnectionRequest(peer); 160 | 161 | It means you have received a connection request from another device (peer) (that have called connect) 162 | for accept the connection request and start connection call bluetoothCommunicator.acceptConnection(peer); 163 | for refusing call bluetoothCommunicator.rejectConnection(peer); (the peer must be the peer argument of onConnectionRequest) 164 | } 165 | 166 | @Override 167 | public void onConnectionSuccess(Peer peer,int source){ 168 | super.onConnectionSuccess(peer,source); 169 | 170 | This means that you have accepted the connection request using acceptConnection or the other 171 | device has accepted your connection request and the connection is complete, from now on you 172 | can send messages or data (or disconnection request) to this peer until onDisconnected 173 | 174 | To send messages to all connected peers you need to create a message with a context, a header, represented by a single character string 175 | (you can use a header to distinguish between different types of messages, or you can ignore it and use a random 176 | character), the text of the message, or a series of bytes if you want to send any kind of data and the peer you want to send the message to 177 | (must be connected to avoid errors), example: new Message(context,"a","hello world",peer); 178 | If you want to send message to a specific peer you have to set the sender of the message with the corresponding peer. 179 | 180 | To send disconnection request to connected peer you need to call bluetoothCommunicator.disconnect(peer); 181 | } 182 | 183 | @Override 184 | public void onConnectionFailed(Peer peer,int errorCode){ 185 | super.onConnectionFailed(peer,errorCode); 186 | 187 | This means that your connection request is rejected or has other problems, 188 | to know the cause of the failure see errorCode (BluetoothCommunicator.CONNECTION_REJECTED 189 | means rejected connection and BluetoothCommunicator.ERROR means generic error) 190 | } 191 | 192 | @Override 193 | public void onConnectionLost(Peer peer){ 194 | super.onConnectionLost(peer); 195 | 196 | This means that a connected peer has lost the connection with you and the library is trying 197 | to restore it, in this case you can update the gui to notify this problem. 198 | 199 | You can still send messages in this situation, all sent messages are put in a queue 200 | and sent as soon as the connection is restored 201 | } 202 | 203 | @Override 204 | public void onConnectionResumed(Peer peer){ 205 | super.onConnectionResumed(peer); 206 | 207 | Means that connection lost is resumed successfully 208 | } 209 | 210 | @Override 211 | public void onMessageReceived(Message message,int source){ 212 | super.onMessageReceived(message,source); 213 | 214 | Means that you have received a message containing TEXT, for know the sender you can call message.getSender() that return 215 | the peer that have sent the message, you can ignore source, it indicate only if you have received the message 216 | as client or as server 217 | } 218 | 219 | @Override 220 | public void onDataReceived(Message data,int source){ 221 | super.onDataReceived(data,source); 222 | 223 | Means that you have received a message containing DATA, for know the sender you can call message.getSender() that return 224 | the peer that have sent the message, you can ignore source, it indicate only if you have received the message 225 | as client or as server 226 | } 227 | 228 | @Override 229 | public void onDisconnected(Peer peer,int peersLeft){ 230 | super.onDisconnected(peer,peersLeft); 231 | 232 | Means that the peer is disconnected, peersLeft indicate the number of connected peers remained 233 | } 234 | 235 | @Override 236 | public void onDisconnectionFailed(){ 237 | super.onDisconnectionFailed(); 238 | 239 | Means that a disconnection is failed, super.onDisconnectionFailed will reactivate bluetooth for forcing disconnection 240 | (however the disconnection will be notified in onDisconnection) 241 | } 242 | }); 243 | ``` 244 | Finally you can start discovery and/or advertising: 245 | ``` 246 | bluetoothCommunicator.startAdvertising(); 247 | bluetoothCommunicator.startDiscovery(); 248 | ``` 249 | All other actions that can be done are explained with the comments in the code of callback I wrote before. 250 | For more details see the code of the example app (BluetoothCommunicatorExample) 251 |

252 | 253 | ### Advanced 254 | For anyone who wants to examinate the library code and generate the .aar file after clone the library on Android Studio: 255 | click on the "Gradle" tab in the right edge of Android Studio, then click on BluetoothCommunicator -> app -> Task -> build -> assemble, then go to the local folder of the BluetoothCommunicator project and click on app -> build -> outputs -> aar, here will be the debug and release .aar files 256 |

257 | 258 | ### Donations 259 | 260 | This is an open source project, I don't make any money from it. 261 | 262 | So, if you found this project useful and want to say thank you and support it, you can make a donation via paypal by clicking on the button below (any amount is well accepted). 263 | 264 | Donate 265 | 266 | In case you will donate, thank you :heart: 267 |

268 | 269 | ### Bugs and problems 270 | Avoid to have installed on your phone multiple apps that use this library, because in that case the bluetooth connection will have problems (maybe it is due to the fact that they are running advertising with the same UUID, try downloading the source files and changing the advertising UUID in the code if you want to try to fix). 271 | In case you have multiple apps using this library, uninstall all but one of them and restart your device in case of problems. 272 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion "28.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 23 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | dependencies { 29 | 30 | implementation 'androidx.appcompat:appcompat:1.2.0' 31 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 33 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bluetooth/communicator/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicator; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.bluetooth.communicator", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/BluetoothMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicator; 18 | 19 | import android.content.Context; 20 | import android.os.Parcel; 21 | import android.os.Parcelable; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | 26 | import com.bluetooth.communicator.tools.BluetoothTools; 27 | 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.ArrayList; 30 | 31 | class BluetoothMessage implements Parcelable { 32 | public static final int ID_LENGTH = 4; 33 | public static final int SEQUENCE_NUMBER_LENGTH = 3; 34 | public static final int TYPE_LENGTH = 1; 35 | public static final int TOTAL_LENGTH = ID_LENGTH + SEQUENCE_NUMBER_LENGTH + TYPE_LENGTH; 36 | public static final int NON_FINAL = 1; 37 | public static final int FINAL = 2; 38 | private Context context; 39 | private Peer sender; // if we are the sender, the sender can be null 40 | private SequenceNumber id; 41 | private SequenceNumber sequenceNumber; 42 | private int type; 43 | private byte[] data; 44 | 45 | 46 | public BluetoothMessage(Context context, Peer sender, SequenceNumber id, SequenceNumber sequenceNumber, int type, byte[] data) { 47 | this.context = context; 48 | this.sender = sender; 49 | this.id = id; 50 | this.sequenceNumber = sequenceNumber; 51 | this.type = type; 52 | this.data = data; 53 | } 54 | 55 | public BluetoothMessage(Context context, SequenceNumber id, SequenceNumber sequenceNumber, int type, byte[] data) { 56 | this.context = context; 57 | this.id = id; 58 | this.sequenceNumber = sequenceNumber; 59 | this.type = type; 60 | this.data = data; 61 | } 62 | 63 | public static BluetoothMessage createFromBytes(Context context, Peer sender, byte[] completeData) { 64 | String completeText = new String(completeData, StandardCharsets.UTF_8); 65 | if (completeText.length() > TOTAL_LENGTH) { 66 | SequenceNumber id = new SequenceNumber(context, completeText.substring(0, ID_LENGTH), ID_LENGTH); 67 | SequenceNumber sequenceNumber = new SequenceNumber(context, completeText.substring(ID_LENGTH, ID_LENGTH + SEQUENCE_NUMBER_LENGTH), SEQUENCE_NUMBER_LENGTH); 68 | int type = Integer.valueOf(completeText.substring(ID_LENGTH + SEQUENCE_NUMBER_LENGTH, TOTAL_LENGTH)); 69 | byte[] data = BluetoothTools.subBytes(completeData, TOTAL_LENGTH, completeData.length); // the header is deleted 70 | if (data != null && sender != null) { 71 | return new BluetoothMessage(context, sender, id, sequenceNumber, type, data); 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | 78 | public SequenceNumber getId() { 79 | return id; 80 | } 81 | 82 | public void setId(SequenceNumber id) { 83 | this.id = id; 84 | } 85 | 86 | public SequenceNumber getSequenceNumber() { 87 | return sequenceNumber; 88 | } 89 | 90 | public void setSequenceNumber(SequenceNumber sequenceNumber) { 91 | this.sequenceNumber = sequenceNumber; 92 | } 93 | 94 | public int getType() { 95 | return type; 96 | } 97 | 98 | public void setType(int type) { 99 | this.type = type; 100 | } 101 | 102 | public Peer getSender() { 103 | return sender; 104 | } 105 | 106 | public void setSender(Peer sender) { 107 | this.sender = sender; 108 | } 109 | 110 | public byte[] getData() { 111 | return data; 112 | } 113 | 114 | public void setData(byte[] data) { 115 | this.data = data; 116 | } 117 | 118 | public void addMessage(@NonNull BluetoothMessage message) { 119 | if (this.equals(message)) { 120 | if (getSequenceNumber() != null && message.getSequenceNumber() != null && message.getSequenceNumber().compare(getSequenceNumber()) > 0) { 121 | this.data = BluetoothTools.concatBytes(this.data, message.getData()); 122 | setSequenceNumber(message.getSequenceNumber()); 123 | type = message.getType(); 124 | } 125 | } 126 | } 127 | 128 | public byte[] getCompleteData() { 129 | return BluetoothTools.concatBytes(getId().getValue().getBytes(StandardCharsets.UTF_8), 130 | getSequenceNumber().getValue().getBytes(StandardCharsets.UTF_8), 131 | String.valueOf(getType()).getBytes(StandardCharsets.UTF_8), 132 | getData()); 133 | } 134 | 135 | public Message convertInMessage() { 136 | String completeText = new String(data, StandardCharsets.UTF_8); 137 | if (completeText.length() > 0) { 138 | String header = completeText.substring(0, Message.HEADER_LENGTH); 139 | byte[] data = BluetoothTools.subBytes(getData(), header.getBytes(StandardCharsets.UTF_8).length, getData().length); 140 | if (data != null) { 141 | return new Message(context, sender, header, data); 142 | } 143 | } 144 | return null; 145 | } 146 | 147 | @Override 148 | public boolean equals(@Nullable Object obj) { 149 | if (obj instanceof BluetoothMessage) { 150 | BluetoothMessage message = (BluetoothMessage) obj; 151 | if (sender != null && message.getSender() != null) { 152 | if (sender.equals(message.getSender())) { 153 | if (id != null && message.getId() != null) { 154 | return id.equals(message.getId()); 155 | } 156 | } 157 | } else { 158 | return false; 159 | } 160 | } 161 | 162 | return false; 163 | } 164 | 165 | public static class SequenceNumber { 166 | private Context context; 167 | private int size; 168 | private ArrayList supportedUTFCharacters; 169 | private Character[] value; 170 | 171 | public SequenceNumber(Context context, int size) { 172 | this.context = context; 173 | this.size = size; 174 | this.supportedUTFCharacters = BluetoothTools.getSupportedUTFCharacters(context); 175 | this.value = new Character[size]; 176 | for (int i = 0; i < size; i++) { 177 | this.value[i] = supportedUTFCharacters.get(0); 178 | } 179 | } 180 | 181 | /** 182 | * @param value must contain 3 characters to avoid mistakes 183 | */ 184 | public SequenceNumber(Context context, String value, int size) { 185 | this.context = context; 186 | this.size = size; 187 | this.supportedUTFCharacters = BluetoothTools.getSupportedUTFCharacters(context); 188 | this.value = new Character[size]; 189 | String fixedValue = BluetoothTools.fixLength(context, value, size, BluetoothTools.FIX_NUMBER); 190 | for (int i = 0; i < size; i++) { 191 | this.value[i] = fixedValue.charAt(i); 192 | } 193 | } 194 | 195 | public void increment() { 196 | int count = size; 197 | while (count > 0) { 198 | count--; 199 | if (supportedUTFCharacters.indexOf(value[count]) < supportedUTFCharacters.size() - 1) { 200 | value[count] = supportedUTFCharacters.get(supportedUTFCharacters.indexOf(value[count]) + 1); 201 | count = 0; 202 | } 203 | } 204 | // if the value is at max we don't increase it 205 | } 206 | 207 | /** 208 | * @return negative number if this < sequenceNumber, 0 if this == sequenceNumber and positive if this > sequenceNumber 209 | **/ 210 | public int compare(@NonNull SequenceNumber sequenceNumber) { 211 | int result = 0; 212 | int count = 0; 213 | while (count < size && result == 0) { 214 | result = Integer.compare(supportedUTFCharacters.indexOf(value[count]), supportedUTFCharacters.indexOf(sequenceNumber.value[count])); 215 | count++; 216 | } 217 | return result; 218 | } 219 | 220 | public boolean isMax() { 221 | SequenceNumber sequenceNumber = clone(); 222 | sequenceNumber.increment(); 223 | return equals(sequenceNumber); // if we have not been able to increase it means that value is at maximum and therefore will be equal to this object without increment 224 | } 225 | 226 | @Override 227 | public boolean equals(@Nullable Object obj) { 228 | if (obj instanceof SequenceNumber) { 229 | SequenceNumber sequenceNumber = (SequenceNumber) obj; 230 | return sequenceNumber.compare(this) == 0; 231 | } 232 | return false; 233 | } 234 | 235 | public SequenceNumber clone() { 236 | return new SequenceNumber(context, getValue(), size); 237 | } 238 | 239 | public String getValue() { 240 | String ret = ""; 241 | for (int i = 0; i < size; i++) { 242 | ret = ret.concat(value[i].toString()); 243 | } 244 | return ret; 245 | } 246 | } 247 | 248 | //parcel implementation 249 | public static final Creator CREATOR = new Creator() { 250 | @Override 251 | public BluetoothMessage createFromParcel(Parcel in) { 252 | return new BluetoothMessage(in); 253 | } 254 | 255 | @Override 256 | public BluetoothMessage[] newArray(int size) { 257 | return new BluetoothMessage[size]; 258 | } 259 | }; 260 | 261 | private BluetoothMessage(Parcel in) { 262 | sender = in.readParcelable(Peer.class.getClassLoader()); 263 | id = new SequenceNumber(context, in.readString(), ID_LENGTH); 264 | sequenceNumber = new SequenceNumber(context, in.readString(), SEQUENCE_NUMBER_LENGTH); 265 | type = in.readInt(); 266 | in.readByteArray(this.data); 267 | } 268 | 269 | @Override 270 | public int describeContents() { 271 | return 0; 272 | } 273 | 274 | @Override 275 | public void writeToParcel(Parcel parcel, int i) { 276 | parcel.writeParcelable(sender, i); 277 | parcel.writeString(id.getValue()); 278 | parcel.writeString(sequenceNumber.getValue()); 279 | parcel.writeInt(type); 280 | parcel.writeByteArray(this.data); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/ClientChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicator; 18 | 19 | import android.bluetooth.BluetoothGatt; 20 | import android.bluetooth.BluetoothGattCharacteristic; 21 | import android.bluetooth.BluetoothGattService; 22 | import android.content.Context; 23 | import android.os.Build; 24 | import android.util.Log; 25 | 26 | import androidx.annotation.NonNull; 27 | 28 | import com.bluetooth.communicator.tools.Timer; 29 | 30 | import java.nio.charset.StandardCharsets; 31 | 32 | class ClientChannel extends Channel { 33 | private BluetoothGatt bluetoothGatt; 34 | 35 | 36 | public ClientChannel(Context context, @NonNull Peer peer) { 37 | super(context, peer); 38 | } 39 | 40 | 41 | public void setBluetoothGatt(BluetoothGatt bluetoothGatt) { 42 | synchronized (lock) { 43 | this.bluetoothGatt = bluetoothGatt; 44 | } 45 | } 46 | 47 | public BluetoothGatt getBluetoothGatt() { 48 | return bluetoothGatt; 49 | } 50 | 51 | @Override 52 | protected void writeSubMessage() { 53 | new Thread() { 54 | @Override 55 | public void run() { 56 | super.run(); 57 | synchronized (lock) { 58 | boolean success = false; 59 | if (bluetoothGatt != null && !messagesPaused && getPeer().isFullyConnected()) { 60 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 61 | 62 | if (service != null) { 63 | if (pendingMessage != null) { 64 | BluetoothMessage subMessageToSend = pendingMessage.peekFirst(); 65 | if (subMessageToSend != null) { // if there are other subMessages for the message we are sending, we send the next one 66 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.MESSAGE_RECEIVE_UUID); 67 | if (output != null) { 68 | output.setValue(subMessageToSend.getCompleteData()); 69 | success = bluetoothGatt.writeCharacteristic(output); 70 | Log.e("subClientMessage send", "-" + success); 71 | } 72 | } 73 | } else { 74 | success = true; 75 | } 76 | } 77 | } 78 | final boolean finalSuccess = success; 79 | if (finalSuccess) { 80 | //start of message timer 81 | startMessageTimer(new Timer.Callback() { 82 | @Override 83 | public void onFinished() { 84 | onSubMessageWriteFailed(); 85 | } 86 | }); 87 | } else { 88 | writeSubMessage(); 89 | } 90 | } 91 | } 92 | }.start(); 93 | } 94 | 95 | @Override 96 | protected void writeSubData() { 97 | new Thread() { 98 | @Override 99 | public void run() { 100 | super.run(); 101 | synchronized (lock) { 102 | boolean success = false; 103 | if (bluetoothGatt != null && !dataPaused && getPeer().isFullyConnected()) { 104 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 105 | 106 | if (service != null) { 107 | if (pendingData != null) { 108 | BluetoothMessage subDataToSend = pendingData.peekFirst(); 109 | if (subDataToSend != null) { // if there are other subMessages for the message we are sending, we send the next one 110 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.DATA_RECEIVE_UUID); 111 | if (output != null) { 112 | output.setValue(subDataToSend.getCompleteData()); 113 | success = bluetoothGatt.writeCharacteristic(output); 114 | Log.e("subClientData send", "-" + success); 115 | } 116 | } 117 | } else { 118 | success = true; 119 | } 120 | } 121 | } 122 | final boolean finalSuccess = success; 123 | if (finalSuccess) { 124 | //start of data timer 125 | startDataTimer(new Timer.Callback() { 126 | @Override 127 | public void onFinished() { 128 | onSubDataWriteFailed(); 129 | } 130 | }); 131 | } else { 132 | writeSubData(); 133 | } 134 | } 135 | } 136 | }.start(); 137 | } 138 | 139 | @Override 140 | public void readPhy() { 141 | synchronized (lock) { 142 | if (bluetoothGatt != null && getPeer().isFullyConnected()) { 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 144 | bluetoothGatt.readPhy(); 145 | } 146 | } 147 | } 148 | } 149 | 150 | public boolean requestConnection(String uniqueName) { 151 | synchronized (lock) { 152 | boolean success = false; 153 | if (bluetoothGatt != null) { 154 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 155 | if (service != null) { 156 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_REQUEST_UUID); 157 | if (output != null) { 158 | output.setValue((uniqueName).getBytes(StandardCharsets.UTF_8)); 159 | success = bluetoothGatt.writeCharacteristic(output); 160 | } 161 | } 162 | } 163 | return success; 164 | } 165 | } 166 | 167 | public boolean notifyConnectionResumed() { 168 | synchronized (lock) { 169 | boolean success = false; 170 | if (bluetoothGatt != null) { 171 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 172 | if (service != null) { 173 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_RESUMED_RECEIVE_UUID); 174 | if (output != null) { 175 | output.setValue(String.valueOf(1).getBytes(StandardCharsets.UTF_8)); 176 | success = bluetoothGatt.writeCharacteristic(output); 177 | } 178 | } 179 | } 180 | return success; 181 | } 182 | } 183 | 184 | @Override 185 | public boolean notifyNameUpdated(String uniqueName) { 186 | synchronized (lock) { 187 | boolean success = false; 188 | if (bluetoothGatt != null && getPeer().isFullyConnected()) { 189 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 190 | if (service != null) { 191 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.NAME_UPDATE_RECEIVE_UUID); 192 | if (output != null) { 193 | output.setValue(uniqueName.getBytes(StandardCharsets.UTF_8)); 194 | success = bluetoothGatt.writeCharacteristic(output); 195 | } 196 | } 197 | } 198 | return success; 199 | } 200 | } 201 | 202 | @Override 203 | public boolean notifyDisconnection(DisconnectionNotificationCallback disconnectionNotificationCallback) { 204 | synchronized (lock) { 205 | boolean success = false; 206 | if (super.notifyDisconnection(disconnectionNotificationCallback)) { 207 | if (bluetoothGatt != null && getPeer().isFullyConnected()) { 208 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 209 | if (service != null) { 210 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.DISCONNECTION_RECEIVE_UUID); //si invia la notifica di disconnessione 211 | if (output != null) { 212 | output.setValue(String.valueOf(1).getBytes(StandardCharsets.UTF_8)); 213 | success = bluetoothGatt.writeCharacteristic(output); 214 | } 215 | } 216 | } 217 | } 218 | return success; 219 | } 220 | } 221 | 222 | @Override 223 | public boolean disconnect(DisconnectionCallback disconnectionCallback) { 224 | synchronized (lock) { 225 | if (super.disconnect(disconnectionCallback)) { 226 | if (bluetoothGatt != null) { 227 | // canceling notifications 228 | BluetoothGattService service = bluetoothGatt.getService(BluetoothConnection.APP_UUID); 229 | if (service != null) { 230 | BluetoothGattCharacteristic messageReceived = service.getCharacteristic(BluetoothConnectionServer.MESSAGE_SEND_UUID); 231 | if (messageReceived != null) { 232 | bluetoothGatt.setCharacteristicNotification(messageReceived, false); 233 | } 234 | BluetoothGattCharacteristic dataReceived = service.getCharacteristic(BluetoothConnectionServer.DATA_SEND_UUID); 235 | if (dataReceived != null) { 236 | bluetoothGatt.setCharacteristicNotification(dataReceived, false); 237 | } 238 | 239 | BluetoothGattCharacteristic disconnectionReceived = service.getCharacteristic(BluetoothConnectionServer.DISCONNECTION_SEND_UUID); 240 | if (disconnectionReceived != null) { 241 | bluetoothGatt.setCharacteristicNotification(disconnectionReceived, false); 242 | } 243 | } 244 | 245 | // actual disconnection 246 | bluetoothGatt.disconnect(); 247 | bluetoothGatt = null; 248 | } 249 | return true; 250 | } 251 | return false; 252 | } 253 | } 254 | 255 | public void destroy() { 256 | synchronized (lock) { 257 | super.destroy(); 258 | if (bluetoothGatt != null) { 259 | bluetoothGatt.disconnect(); 260 | bluetoothGatt.close(); 261 | } 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/Message.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicator; 18 | 19 | import android.content.Context; 20 | import android.os.Parcel; 21 | import android.os.Parcelable; 22 | 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | 26 | import com.bluetooth.communicator.tools.BluetoothTools; 27 | 28 | import java.nio.charset.StandardCharsets; 29 | import java.util.ArrayDeque; 30 | 31 | /** 32 | * Message is used to send and receive messages using BluetoothCommunicator, in practice this class is a container for the messages that will be sent and received. 33 | *

34 | * In order to send a message the message object must always contain a header and the text or data (if it will be sent via BluetoothCommunicator.sendMessage text will be sent, 35 | * if it will be sent via BluetoothCommunicator.sendData then data will be sent). 36 | * If you want to specify a peer to send the message to (by default it is sent to all) then the peer must be set as receiver, you can do it in the constructor or through the Message.setReceiver method. 37 | *

38 | * To send a message there is no need to set the sender because the latter will not be sent, the receiver will recognize the sender through the channel in which he will receive the message, 39 | * in any case it is something completely transparent. The sender is used only to identify the sender of received messages, and upon receipt of a message 40 | * via the BluetoothCommunicator.onMessageReceived or onDataReceived method the sender will have already been automatically inserted into the message by the library, along with the text / data and header. 41 | *

42 | * Another way you can use the Message class is to represent messages (graphically, to save them, etc.), some Message constructors would not make sense for sending or receiving messages, 43 | * but they can be useful for use as representation. 44 | */ 45 | public class Message implements Parcelable, Cloneable { 46 | public static final int HEADER_LENGTH = 1; 47 | private Context context; 48 | @Nullable 49 | private Peer sender; // if we are the sender, the sender can be null 50 | @Nullable 51 | private Peer receiver; //if is null the message will be sent to all connected peers 52 | private String header; // mandatory length: 1 53 | private byte[] data; 54 | 55 | /** 56 | * @param context a context 57 | * @param header must contain 1 character to avoid errors 58 | * @param text the text of the message (it will be sent by sendMessage) 59 | */ 60 | public Message(Context context, String header, @NonNull String text) { 61 | this.context = context; 62 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 63 | this.data = text.getBytes(StandardCharsets.UTF_8); 64 | } 65 | 66 | /** 67 | * 68 | * @param context a context 69 | * @param text the text of the message (it will be sent by sendMessage) 70 | */ 71 | public Message(Context context, @NonNull String text) { 72 | this.context = context; 73 | this.data = text.getBytes(StandardCharsets.UTF_8); 74 | } 75 | 76 | /** 77 | * @param context a context 78 | * @param header must contain 1 character to avoid errors 79 | */ 80 | public Message(Context context, String header, @NonNull byte[] data) { 81 | this.context = context; 82 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 83 | this.data = data; 84 | } 85 | 86 | /** 87 | * 88 | * @param context a context 89 | * @param data the data of the message (it will be sent by sendData) 90 | */ 91 | public Message(Context context, @NonNull byte[] data) { 92 | this.context = context; 93 | this.data = data; 94 | } 95 | 96 | /** 97 | * @param context a context 98 | * @param header must contain 1 character to avoid errors 99 | */ 100 | public Message(Context context, String header, String text, @Nullable Peer receiver) { 101 | this.context = context; 102 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 103 | this.data = text.getBytes(StandardCharsets.UTF_8); 104 | this.receiver = receiver; 105 | } 106 | 107 | /** 108 | * @param context a context 109 | * @param header must contain 1 character to avoid errors 110 | */ 111 | public Message(Context context, String header, @NonNull byte[] data, @Nullable Peer receiver) { 112 | this.context = context; 113 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 114 | this.data = data; 115 | this.receiver = receiver; 116 | } 117 | 118 | /** 119 | * @param context a context 120 | * @param header must contain 1 character to avoid errors 121 | * @param text the text of the message (it will be sent by sendMessage) 122 | */ 123 | public Message(Context context, @Nullable Peer sender, String header, @NonNull String text) { 124 | this.context = context; 125 | this.sender = sender; 126 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 127 | this.data = text.getBytes(StandardCharsets.UTF_8); 128 | } 129 | 130 | public Message(Context context, @Nullable Peer sender, @NonNull String text) { 131 | this.context = context; 132 | this.sender = sender; 133 | this.data = text.getBytes(StandardCharsets.UTF_8); 134 | } 135 | 136 | /** 137 | * @param context a context 138 | * @param header must contain 1 character to avoid errors 139 | */ 140 | public Message(Context context, @Nullable Peer sender, String header, @NonNull byte[] data) { 141 | this.context = context; 142 | this.sender = sender; 143 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 144 | this.data = data; 145 | } 146 | 147 | /** 148 | * @param context a context 149 | * @param sender the sender of the message 150 | * @param data the data of the message (it will be sent by sendData) 151 | */ 152 | public Message(Context context, @Nullable Peer sender, @NonNull byte[] data) { 153 | this.context = context; 154 | this.sender = sender; 155 | this.data = data; 156 | } 157 | 158 | 159 | /** 160 | * Sets the header, the header is a single character that can be used to differentiate the types of message, 161 | * if you use a single type of message, just pick a random character for header and ignore it when receiving 162 | * text messages or data messages. 163 | * 164 | * @param header must contain 1 character to avoid errors 165 | */ 166 | public void setHeader(String header) { 167 | this.header = BluetoothTools.fixLength(context, header, HEADER_LENGTH, BluetoothTools.FIX_TEXT); 168 | } 169 | 170 | /** 171 | * Returns the header 172 | * @return header 173 | */ 174 | public String getHeader() { 175 | return header; 176 | } 177 | 178 | /** 179 | * Returns the sender 180 | * @return sender 181 | */ 182 | public Peer getSender() { 183 | return sender; 184 | } 185 | 186 | /** 187 | * Sets the sender.
188 | * To send a message there is no need to set the sender because the latter will not be sent, the receiver will recognize the sender through the channel in which he will receive the message, 189 | * in any case it is something completely transparent. The sender is used only to identify the sender of received messages, and upon receipt of a message 190 | * via the BluetoothCommunicator.onMessageReceived or onDataReceived method the sender will have already been automatically inserted into the message by the library, along with the text / data and header. 191 | * A way this method can be useful is when you are using Message for representation (for the gui, for saving messages etc.). 192 | * 193 | * @param sender 194 | */ 195 | public void setSender(@Nullable Peer sender) { 196 | this.sender = sender; 197 | } 198 | 199 | /** 200 | * Returns the receiver 201 | * @return receiver 202 | */ 203 | @Nullable 204 | public Peer getReceiver() { 205 | return receiver; 206 | } 207 | 208 | /** 209 | * Sets the receiver.
210 | * If you want to specify a peer to send the message to (by default it is sent to all) then the peer must be set as receiver, you can do it in the constructor or through the Message.setReceiver method. 211 | * 212 | * @param receiver 213 | */ 214 | public void setReceiver(@Nullable Peer receiver) { 215 | this.receiver = receiver; 216 | } 217 | 218 | /** 219 | * Return the text of the message 220 | * 221 | * @return text 222 | */ 223 | public String getText() { 224 | return new String(this.data, StandardCharsets.UTF_8); 225 | } 226 | 227 | /** 228 | * Sets the text of the message 229 | * 230 | * @param text 231 | */ 232 | public void setText(String text) { 233 | this.data = text.getBytes(StandardCharsets.UTF_8); 234 | } 235 | 236 | /** 237 | * Return the data of the message 238 | * 239 | * @return text 240 | */ 241 | public byte[] getData() { 242 | return this.data; 243 | } 244 | 245 | /** 246 | * Sets the data of the message 247 | * 248 | * @param data 249 | */ 250 | public void setData(byte[] data) { 251 | this.data = data; 252 | } 253 | 254 | /** 255 | * This method is used only by the library, there is no need for you to use it because the split and the reassembly of a long message is handled by the library. 256 | * 257 | * @param id 258 | * @return the message splitted in more BluetoothMessages (or converted in one BluetoothMessage if the message is short enough) 259 | */ 260 | public ArrayDeque splitInBluetoothMessages(BluetoothMessage.SequenceNumber id) { 261 | int subDataLength = BluetoothConnection.SUB_MESSAGES_LENGTH - BluetoothMessage.TOTAL_LENGTH; 262 | ArrayDeque subDataArray = BluetoothTools.splitBytes(BluetoothTools.concatBytes(header.getBytes(StandardCharsets.UTF_8), data), subDataLength); 263 | 264 | BluetoothMessage.SequenceNumber sequenceNumber = new BluetoothMessage.SequenceNumber(context,BluetoothMessage.SEQUENCE_NUMBER_LENGTH); 265 | ArrayDeque bluetoothMessages = new ArrayDeque<>(); 266 | while (subDataArray.peekFirst() != null) { 267 | byte[] subData = subDataArray.pollFirst(); 268 | int type; 269 | if (subDataArray.peekFirst() == null) { 270 | type = BluetoothMessage.FINAL; 271 | } else { 272 | type = BluetoothMessage.NON_FINAL; 273 | } 274 | bluetoothMessages.addLast(new BluetoothMessage(context, id.clone(), sequenceNumber.clone(), type, subData)); 275 | sequenceNumber.increment(); 276 | } 277 | return bluetoothMessages; 278 | } 279 | 280 | @Override 281 | protected Object clone() throws CloneNotSupportedException { 282 | return super.clone(); 283 | } 284 | 285 | //parcel implementation 286 | public static final Creator CREATOR = new Creator() { 287 | @Override 288 | public Message createFromParcel(Parcel in) { 289 | return new Message(in); 290 | } 291 | 292 | @Override 293 | public Message[] newArray(int size) { 294 | return new Message[size]; 295 | } 296 | }; 297 | 298 | private Message(Parcel in) { 299 | sender = in.readParcelable(Peer.class.getClassLoader()); 300 | header = in.readString(); 301 | in.readByteArray(this.data); 302 | } 303 | 304 | @Override 305 | public int describeContents() { 306 | return 0; 307 | } 308 | 309 | @Override 310 | public void writeToParcel(Parcel parcel, int i) { 311 | parcel.writeParcelable(sender, i); 312 | parcel.writeString(header); 313 | parcel.writeByteArray(this.data); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/ServerChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicator; 18 | 19 | import android.bluetooth.BluetoothAdapter; 20 | import android.bluetooth.BluetoothGattCharacteristic; 21 | import android.bluetooth.BluetoothGattServer; 22 | import android.bluetooth.BluetoothGattService; 23 | import android.content.Context; 24 | import android.os.Build; 25 | import android.util.Log; 26 | 27 | import androidx.annotation.NonNull; 28 | 29 | import com.bluetooth.communicator.tools.Timer; 30 | 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.UUID; 33 | 34 | class ServerChannel extends Channel { 35 | private BluetoothGattServer bluetoothGattServer; 36 | private BluetoothAdapter bluetoothAdapter; 37 | private UUID sendingCharacteristic = null; 38 | 39 | 40 | protected ServerChannel(Context context, @NonNull Peer peer, final BluetoothAdapter bluetoothAdapter) { 41 | super(context, peer); 42 | this.bluetoothAdapter = bluetoothAdapter; 43 | } 44 | 45 | 46 | public void setBluetoothGattServer(BluetoothGattServer bluetoothGattServer) { 47 | this.bluetoothGattServer = bluetoothGattServer; 48 | } 49 | 50 | @Override 51 | protected void writeSubMessage() { 52 | new Thread() { 53 | @Override 54 | public void run() { 55 | synchronized (lock) { 56 | super.run(); 57 | boolean success = false; 58 | if (bluetoothGattServer != null && !messagesPaused && getPeer().isFullyConnected()) { 59 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 60 | 61 | if (service != null) { 62 | if (pendingMessage != null) { 63 | BluetoothMessage subMessageToSend = pendingMessage.peekFirst(); 64 | if (subMessageToSend != null) { // if there are other subMessages for the message we are sending, we send the next one 65 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.MESSAGE_SEND_UUID); 66 | if (output != null) { 67 | output.setValue(subMessageToSend.getCompleteData()); 68 | sendingCharacteristic = BluetoothConnectionServer.MESSAGE_SEND_UUID; 69 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 70 | Log.e("subServerMessage send", "-" + success); 71 | } 72 | } 73 | } else { 74 | success = true; 75 | } 76 | } 77 | } 78 | final boolean finalSuccess = success; 79 | if (finalSuccess) { 80 | // start of message timer 81 | startMessageTimer(new Timer.Callback() { 82 | @Override 83 | public void onFinished() { 84 | onSubMessageWriteFailed(); 85 | } 86 | }); 87 | } else { 88 | writeSubMessage(); 89 | } 90 | } 91 | } 92 | }.start(); 93 | } 94 | 95 | @Override 96 | protected void writeSubData() { 97 | new Thread() { 98 | @Override 99 | public void run() { 100 | synchronized (lock) { 101 | super.run(); 102 | boolean success = false; 103 | if (bluetoothGattServer != null && !dataPaused && getPeer().isFullyConnected()) { 104 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 105 | 106 | if (service != null) { 107 | if (pendingData != null) { 108 | BluetoothMessage subDataToSend = pendingData.peekFirst(); 109 | if (subDataToSend != null) { // if there are other subMessages for the message we are sending, we send the next one 110 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.DATA_SEND_UUID); 111 | if (output != null) { 112 | //output.setValue(subDataToSend); 113 | output.setValue(String.valueOf(1).getBytes(StandardCharsets.UTF_8)); 114 | sendingCharacteristic = BluetoothConnectionServer.DATA_SEND_UUID; 115 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 116 | Log.e("subServerData send", "-" + success); 117 | } 118 | } 119 | } else { 120 | success = true; 121 | } 122 | } 123 | } 124 | final boolean finalSuccess = success; 125 | if (finalSuccess) { 126 | // start of data timer 127 | startDataTimer(new Timer.Callback() { 128 | @Override 129 | public void onFinished() { 130 | onSubDataWriteFailed(); 131 | } 132 | }); 133 | } else { 134 | writeSubData(); 135 | } 136 | } 137 | } 138 | }.start(); 139 | } 140 | 141 | public UUID getSendingCharacteristic() { 142 | return sendingCharacteristic; 143 | } 144 | 145 | @Override 146 | public void readPhy() { 147 | synchronized (lock) { 148 | if (bluetoothGattServer != null && getPeer().isFullyConnected()) { 149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 150 | bluetoothGattServer.readPhy(getPeer().getRemoteDevice(bluetoothAdapter)); 151 | } 152 | } 153 | } 154 | } 155 | 156 | public boolean acceptConnection() { 157 | synchronized (lock) { 158 | boolean success = false; 159 | if (bluetoothGattServer != null) { 160 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 161 | if (service != null) { 162 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_RESPONSE_UUID); 163 | if (output != null) { 164 | output.setValue(String.valueOf(BluetoothConnection.ACCEPT).getBytes(StandardCharsets.UTF_8)); 165 | sendingCharacteristic = BluetoothConnectionServer.CONNECTION_RESPONSE_UUID; 166 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 167 | } 168 | } 169 | } 170 | return success; 171 | } 172 | } 173 | 174 | public boolean rejectConnection() { 175 | synchronized (lock) { 176 | boolean success = false; 177 | if (bluetoothGattServer != null) { 178 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 179 | if (service != null) { 180 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_RESPONSE_UUID); 181 | if (output != null) { 182 | output.setValue(String.valueOf(BluetoothConnection.REJECT).getBytes(StandardCharsets.UTF_8)); 183 | sendingCharacteristic = BluetoothConnectionServer.CONNECTION_RESPONSE_UUID; 184 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 185 | } 186 | } 187 | } 188 | return success; 189 | } 190 | } 191 | 192 | public boolean notifyConnectionResumed() { 193 | synchronized (lock) { 194 | boolean success = false; 195 | if (bluetoothGattServer != null) { 196 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 197 | if (service != null) { 198 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_RESUMED_SEND_UUID); 199 | if (output != null) { 200 | output.setValue(String.valueOf(BluetoothConnection.ACCEPT).getBytes(StandardCharsets.UTF_8)); 201 | sendingCharacteristic = BluetoothConnectionServer.CONNECTION_RESUMED_SEND_UUID; 202 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 203 | } 204 | } 205 | } 206 | return success; 207 | } 208 | } 209 | 210 | public boolean notifyConnectionResumedRejected() { 211 | synchronized (lock) { 212 | boolean success = false; 213 | if (bluetoothGattServer != null) { 214 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 215 | if (service != null) { 216 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.CONNECTION_RESUMED_SEND_UUID); 217 | if (output != null) { 218 | output.setValue(String.valueOf(BluetoothConnection.REJECT).getBytes(StandardCharsets.UTF_8)); 219 | sendingCharacteristic = BluetoothConnectionServer.CONNECTION_RESUMED_SEND_UUID; 220 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 221 | } 222 | } 223 | } 224 | return success; 225 | } 226 | } 227 | 228 | public boolean notifyNameUpdated(String name) { 229 | synchronized (lock) { 230 | boolean success = false; 231 | if (bluetoothGattServer != null && getPeer().isFullyConnected()) { 232 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 233 | if (service != null) { 234 | BluetoothGattCharacteristic output = service.getCharacteristic(BluetoothConnectionServer.NAME_UPDATE_SEND_UUID); 235 | if (output != null) { 236 | output.setValue(name); 237 | sendingCharacteristic = BluetoothConnectionServer.NAME_UPDATE_SEND_UUID; 238 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), output, true); 239 | } 240 | } 241 | } 242 | return success; 243 | } 244 | } 245 | 246 | @Override 247 | public boolean notifyDisconnection(DisconnectionNotificationCallback disconnectionNotificationCallback) { 248 | synchronized (lock) { 249 | boolean success = false; 250 | if (super.notifyDisconnection(disconnectionNotificationCallback)) { 251 | if (bluetoothGattServer != null && getPeer().isFullyConnected()) { 252 | BluetoothGattService service = bluetoothGattServer.getService(BluetoothConnection.APP_UUID); 253 | if (service != null) { 254 | BluetoothGattCharacteristic disconnectionSend = service.getCharacteristic(BluetoothConnectionServer.DISCONNECTION_SEND_UUID); 255 | if (disconnectionSend != null) { 256 | disconnectionSend.setValue(String.valueOf(1).getBytes(StandardCharsets.UTF_8)); 257 | sendingCharacteristic = BluetoothConnectionServer.DISCONNECTION_SEND_UUID; 258 | success = bluetoothGattServer.notifyCharacteristicChanged(getPeer().getRemoteDevice(bluetoothAdapter), disconnectionSend, true); 259 | } 260 | } 261 | } 262 | } 263 | return success; 264 | } 265 | } 266 | 267 | @Override 268 | public boolean disconnect(DisconnectionCallback disconnectionCallback) { 269 | synchronized (lock) { 270 | if (super.disconnect(disconnectionCallback)) { 271 | if (bluetoothGattServer != null) { 272 | bluetoothGattServer.cancelConnection(getPeer().getRemoteDevice(bluetoothAdapter)); 273 | return true; 274 | } 275 | } 276 | return false; 277 | } 278 | } 279 | 280 | @Override 281 | public void destroy() { 282 | synchronized (lock) { 283 | super.destroy(); 284 | if (bluetoothGattServer != null) { 285 | bluetoothGattServer.cancelConnection(getPeer().getRemoteDevice(bluetoothAdapter)); 286 | bluetoothGattServer.close(); 287 | } 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/tools/BluetoothTools.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicator.tools; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.util.Log; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | import java.security.SecureRandom; 10 | import java.util.ArrayDeque; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.Objects; 14 | import java.util.Random; 15 | 16 | /** 17 | * This class contains a series of static methods to help the library, for the user there is an important method however: 18 | * getSupportedUTFCharacters returns the list of supported characters that the user can compare with the characters of the name 19 | * that will be passed to BluetoothCommunicator in the constructor o in setName, in fact the name must not contain 20 | * characters other than those supported and must not exceed 18 characters in length, otherwise BluetoothCommunicator 21 | * may not work correctly. 22 | */ 23 | public class BluetoothTools { 24 | public static final int FIX_NUMBER = 0; 25 | public static final int FIX_TEXT = 1; 26 | 27 | /** 28 | * return all characters of UTF encoding (this is because bluetooth only support a certain amount of bytes 29 | * to send to nearby devices) so ensure that the name that will be passed to BluetoothCommunicator in the 30 | * constructor o in setName contains only these character and not exceed 18 characters in length, otherwise 31 | * BluetoothCommunicator may not work correctly. 32 | * 33 | * @param context 34 | * @return list of supported characters 35 | */ 36 | public static ArrayList getSupportedUTFCharacters(Context context) { 37 | ArrayList characters = new ArrayList<>(); 38 | characters.add(' '); 39 | characters.add('!'); 40 | characters.add('"'); 41 | characters.add('#'); 42 | characters.add('$'); 43 | characters.add('%'); 44 | characters.add('&'); 45 | characters.add('\''); 46 | characters.add('('); 47 | characters.add(')'); 48 | characters.add('*'); 49 | characters.add('+'); 50 | characters.add(','); 51 | characters.add('-'); 52 | characters.add('.'); 53 | characters.add('/'); 54 | characters.add('0'); 55 | characters.add('1'); 56 | characters.add('2'); 57 | characters.add('3'); 58 | characters.add('4'); 59 | characters.add('5'); 60 | characters.add('6'); 61 | characters.add('7'); 62 | characters.add('8'); 63 | characters.add('9'); 64 | characters.add(':'); 65 | characters.add(';'); 66 | characters.add('<'); 67 | characters.add('='); 68 | characters.add('>'); 69 | characters.add('?'); 70 | characters.add('@'); 71 | characters.add('A'); 72 | characters.add('B'); 73 | characters.add('C'); 74 | characters.add('D'); 75 | characters.add('E'); 76 | characters.add('F'); 77 | characters.add('G'); 78 | characters.add('H'); 79 | characters.add('I'); 80 | characters.add('J'); 81 | characters.add('K'); 82 | characters.add('L'); 83 | characters.add('M'); 84 | characters.add('N'); 85 | characters.add('O'); 86 | characters.add('P'); 87 | characters.add('Q'); 88 | characters.add('R'); 89 | characters.add('S'); 90 | characters.add('T'); 91 | characters.add('U'); 92 | characters.add('V'); 93 | characters.add('W'); 94 | characters.add('X'); 95 | characters.add('Y'); 96 | characters.add('Z'); 97 | characters.add('['); 98 | characters.add('\\'); 99 | characters.add(']'); 100 | characters.add('^'); 101 | characters.add('_'); 102 | characters.add('`'); 103 | characters.add('a'); 104 | characters.add('b'); 105 | characters.add('c'); 106 | characters.add('d'); 107 | characters.add('e'); 108 | characters.add('f'); 109 | characters.add('g'); 110 | characters.add('h'); 111 | characters.add('i'); 112 | characters.add('j'); 113 | characters.add('k'); 114 | characters.add('l'); 115 | characters.add('m'); 116 | characters.add('n'); 117 | characters.add('o'); 118 | characters.add('p'); 119 | characters.add('q'); 120 | characters.add('r'); 121 | characters.add('s'); 122 | characters.add('t'); 123 | characters.add('u'); 124 | characters.add('v'); 125 | characters.add('w'); 126 | characters.add('x'); 127 | characters.add('y'); 128 | characters.add('z'); 129 | characters.add('{'); 130 | characters.add('|'); 131 | characters.add('}'); 132 | characters.add('~'); 133 | Collections.sort(characters); // alphabetical order 134 | return characters; 135 | } 136 | 137 | public static String getSupportedNameCharactersString(Context context) { 138 | String string = ""; 139 | ArrayList characters = getSupportedUTFCharacters(context); 140 | for (Character character : characters) { 141 | string = string.concat(" " + character.toString()); 142 | } 143 | int lentgh = string.getBytes(StandardCharsets.UTF_8).length; 144 | Log.e("lenght", lentgh + ""); 145 | return string; 146 | } 147 | 148 | public static String fixLength(Context context, String string, int length, int typeOfFix) { 149 | int fillingLength = length - string.length(); 150 | if (fillingLength > 0) { 151 | // filling 152 | Character fillChar = getSupportedUTFCharacters(context).get(0); 153 | StringBuilder outputBuffer = new StringBuilder(fillingLength); 154 | for (int i = 0; i < fillingLength; i++) { 155 | outputBuffer.append(fillChar); 156 | } 157 | if (typeOfFix == FIX_NUMBER) { 158 | return outputBuffer.toString().concat(string); 159 | } else { 160 | return string.concat(outputBuffer.toString()); 161 | } 162 | } else { 163 | // cut 164 | if (typeOfFix == FIX_NUMBER) { 165 | return string.substring(fillingLength * -1); 166 | } else { 167 | return string.substring(0, length); 168 | } 169 | } 170 | } 171 | 172 | public static String generateRandomUTFString(Context context, int length) { 173 | return new RandomString(length).nextString(context); 174 | } 175 | 176 | public static String generateBluetoothNameId(Context context) { 177 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); 178 | String id = sharedPreferences.getString("bluetoothNameId", ""); 179 | if (id != null && id.length() == 2) { 180 | return id; 181 | } else { 182 | //generazione dell'id e salvataggio 183 | SharedPreferences.Editor edit = sharedPreferences.edit(); 184 | id = generateRandomUTFString(context, 2); 185 | edit.putString("bluetoothNameId", id); 186 | edit.apply(); 187 | return id; 188 | } 189 | } 190 | 191 | /** 192 | * index cell goes in second array 193 | */ 194 | public static ArrayDeque splitBytes(byte[] array, int subArraysLength) { 195 | 196 | ArrayDeque resultMatrixList = new ArrayDeque<>(); 197 | for (int j = 0; j < array.length; j += subArraysLength) { 198 | byte[] subArray; 199 | if (j + subArraysLength < array.length) { 200 | subArray = new byte[subArraysLength]; 201 | } else { 202 | subArray = new byte[array.length - j]; 203 | } 204 | System.arraycopy(array, j, subArray, 0, subArray.length); 205 | resultMatrixList.addLast(subArray); 206 | } 207 | 208 | return resultMatrixList; 209 | } 210 | 211 | public static byte[] concatBytes(byte[]... arrays) { 212 | int resultArrayLength = 0; 213 | for (byte[] array : arrays) { 214 | resultArrayLength += array.length; 215 | } 216 | byte[] resultArray = new byte[resultArrayLength]; 217 | int destIndex = 0; 218 | for (byte[] array : arrays) { 219 | System.arraycopy(array, 0, resultArray, destIndex, array.length); 220 | destIndex += array.length; 221 | } 222 | return resultArray; 223 | } 224 | 225 | public static byte[] subBytes(byte[] array, int begin, int end) { 226 | if (end <= begin || begin < 0 || end > array.length) { 227 | return null; 228 | } 229 | int length = end - begin; 230 | byte[] subArray = new byte[length]; 231 | System.arraycopy(array, begin, subArray, 0, length); 232 | return subArray; 233 | } 234 | 235 | private static class RandomString { 236 | private final Random random; 237 | private final char[] buf; 238 | 239 | /** 240 | * Create an alphanumeric string generator. 241 | */ 242 | private RandomString(int length, Random random) { 243 | if (length < 1) throw new IllegalArgumentException(); 244 | this.random = Objects.requireNonNull(random); 245 | this.buf = new char[length]; 246 | } 247 | 248 | /** 249 | * Create an alphanumeric strings from a secure generator. 250 | */ 251 | private RandomString(int length) { 252 | this(length, new SecureRandom()); 253 | } 254 | 255 | /** 256 | * Generate a random string. 257 | */ 258 | private String nextString(Context context) { 259 | for (int idx = 0; idx < buf.length; ++idx) { 260 | buf[idx] = (getSupportedUTFCharacters(context).get(random.nextInt(95))); //si genera un carattere casuale composto da tutti i valori possibili del codice ascii normale (non esteso) per poter essere espressi da un solo byte in utf-8 261 | } 262 | return new String(buf); 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/tools/CustomCountDownTimer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicator.tools; 18 | 19 | import android.os.Handler; 20 | import android.os.Looper; 21 | import android.os.Message; 22 | import android.os.SystemClock; 23 | 24 | public abstract class CustomCountDownTimer { 25 | /** 26 | * Millis since epoch when alarm should stop. 27 | */ 28 | private final long mMillisInFuture; 29 | 30 | /** 31 | * The interval in millis that the user receives callbacks 32 | */ 33 | private final long mCountdownInterval; 34 | 35 | private long mStopTimeInFuture; 36 | 37 | /** 38 | * boolean representing if the timer was cancelled 39 | */ 40 | private boolean mCancelled = false; 41 | 42 | /** 43 | * @param millisInFuture The number of millis in the future from the call 44 | * to {@link #start()} until the countdown is done and {@link #onFinish()} 45 | * is called. 46 | * @param countDownInterval The interval along the way to receive 47 | * {@link #onTick(long)} callbacks. 48 | */ 49 | public CustomCountDownTimer(long millisInFuture, long countDownInterval) { 50 | mMillisInFuture = millisInFuture; 51 | mCountdownInterval = countDownInterval; 52 | } 53 | 54 | /** 55 | * Cancel the countdown. 56 | */ 57 | public synchronized final void cancel() { 58 | mCancelled = true; 59 | mHandler.removeMessages(MSG); 60 | } 61 | 62 | /** 63 | * Start the countdown. 64 | */ 65 | public synchronized final CustomCountDownTimer start() { 66 | mCancelled = false; 67 | if (mMillisInFuture <= 0) { 68 | onFinish(); 69 | return this; 70 | } 71 | mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture; 72 | mHandler.sendMessage(mHandler.obtainMessage(MSG)); 73 | return this; 74 | } 75 | 76 | 77 | /** 78 | * Callback fired on regular interval. 79 | * @param millisUntilFinished The amount of time until finished. 80 | */ 81 | public abstract void onTick(long millisUntilFinished); 82 | 83 | /** 84 | * Callback fired when the time is up. 85 | */ 86 | public abstract void onFinish(); 87 | 88 | 89 | private static final int MSG = 1; 90 | 91 | 92 | // handles counting down 93 | private Handler mHandler = new Handler(Looper.getMainLooper()) { 94 | 95 | @Override 96 | public void handleMessage(Message msg) { 97 | 98 | synchronized (CustomCountDownTimer.this) { 99 | if (mCancelled) { 100 | return; 101 | } 102 | 103 | final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); 104 | 105 | if (millisLeft <= 0) { 106 | onFinish(); 107 | } else { 108 | long lastTickStart = SystemClock.elapsedRealtime(); 109 | onTick(millisLeft); 110 | 111 | // take into account user's onTick taking time to execute 112 | long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart; 113 | long delay; 114 | 115 | if (millisLeft < mCountdownInterval) { 116 | // just delay until done 117 | delay = millisLeft - lastTickDuration; 118 | 119 | // special case: user's onTick took more than interval to 120 | // complete, trigger onFinish without delay 121 | if (delay < 0) delay = 0; 122 | } else { 123 | delay = mCountdownInterval - lastTickDuration; 124 | 125 | // special case: user's onTick took more than interval to 126 | // complete, skip to next interval 127 | while (delay < 0) delay += mCountdownInterval; 128 | } 129 | 130 | sendMessageDelayed(obtainMessage(MSG), delay); 131 | } 132 | } 133 | } 134 | }; 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/bluetooth/communicator/tools/Timer.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicator.tools; 2 | 3 | public class Timer { 4 | private CustomCountDownTimer countDownTimer; 5 | private boolean isFinished = false; 6 | private Callback callback; 7 | private final Object lock = new Object(); 8 | 9 | public Timer(long duration) { 10 | countDownTimer = new CustomCountDownTimer(duration, duration) { 11 | @Override 12 | public void onTick(long millisUntilFinished) { 13 | } 14 | 15 | @Override 16 | public void onFinish() { 17 | synchronized (lock) { 18 | isFinished = true; 19 | if (callback != null) { 20 | callback.onFinished(); 21 | } 22 | } 23 | } 24 | }; 25 | } 26 | 27 | public void start() { 28 | countDownTimer.start(); 29 | } 30 | 31 | public void cancel() { 32 | synchronized (lock) { 33 | countDownTimer.cancel(); 34 | callback = null; 35 | } 36 | } 37 | 38 | public boolean isFinished() { 39 | return isFinished; 40 | } 41 | 42 | public void setCallback(Callback callback) { 43 | this.callback = callback; 44 | } 45 | 46 | public static abstract class Callback { 47 | public abstract void onFinished(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/test/java/com/bluetooth/communicator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /apptest/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /apptest/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | ext { 4 | supportLibraryVersion = '1.0.0' 5 | } 6 | 7 | android { 8 | compileSdkVersion 28 9 | buildToolsVersion "28.0.3" 10 | 11 | defaultConfig { 12 | applicationId "com.bluetooth.communicatorexample" 13 | minSdkVersion 23 14 | targetSdkVersion 28 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: "libs", include: ["*.jar"]) 31 | implementation 'androidx.appcompat:appcompat:1.2.0' 32 | implementation 'androidx.constraintlayout:constraintlayout:2.0.2' 33 | //bluetooth communicator 34 | 35 | // Support libraries 36 | implementation "com.google.android.material:material:1.0.0" 37 | implementation "androidx.cardview:cardview:1.0.0" 38 | implementation "androidx.recyclerview:recyclerview:1.0.0" 39 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 40 | implementation project(path: ':app') 41 | // Tests 42 | testImplementation 'junit:junit:4.12' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 45 | 46 | } -------------------------------------------------------------------------------- /apptest/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /apptest/src/androidTest/java/com/bluetooth/communicatorexample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicatorexample; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.bluetooth.communicatorexample", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /apptest/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/Global.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample; 18 | 19 | import android.app.Application; 20 | 21 | 22 | import com.bluetooth.communicator.BluetoothCommunicator; 23 | import com.bluetooth.communicator.tools.BluetoothTools; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Random; 27 | 28 | public class Global extends Application { 29 | private BluetoothCommunicator bluetoothCommunicator; 30 | 31 | @Override 32 | public void onCreate() { 33 | super.onCreate(); 34 | String name = android.os.Build.MODEL; 35 | //compatibily check for supported characters 36 | ArrayList supportedCharacters = BluetoothTools.getSupportedUTFCharacters(this); 37 | boolean equals = true; 38 | for (int i = 0; i < name.length() && equals; i++) { 39 | if (!supportedCharacters.contains(Character.valueOf(name.charAt(i)))) { 40 | equals = false; 41 | } 42 | } 43 | if (!equals || name.length() > 18) { 44 | name = "User " + new Random().nextInt(21); 45 | } 46 | 47 | bluetoothCommunicator = new BluetoothCommunicator(this, name, BluetoothCommunicator.STRATEGY_P2P_WITH_RECONNECTION); 48 | } 49 | 50 | public BluetoothCommunicator getBluetoothCommunicator() { 51 | return bluetoothCommunicator; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/fragments/ConversationFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.fragments; 18 | 19 | 20 | import android.animation.Animator; 21 | import android.os.Bundle; 22 | import android.view.LayoutInflater; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.WindowInsets; 26 | import android.widget.EditText; 27 | import android.widget.ProgressBar; 28 | import android.widget.TextView; 29 | import android.widget.Toast; 30 | import android.widget.Toolbar; 31 | 32 | import androidx.annotation.NonNull; 33 | import androidx.annotation.Nullable; 34 | 35 | 36 | import com.bluetooth.communicator.BluetoothCommunicator; 37 | import com.bluetooth.communicator.Message; 38 | import com.bluetooth.communicator.Peer; 39 | import com.bluetooth.communicatorexample.Global; 40 | import com.bluetooth.communicatorexample.MainActivity; 41 | import com.bluetooth.communicatorexample.R; 42 | import com.bluetooth.communicatorexample.gui.CustomAnimator; 43 | import com.bluetooth.communicatorexample.gui.GuiTools; 44 | import com.bluetooth.communicatorexample.gui.MessagesAdapter; 45 | 46 | import androidx.appcompat.widget.AppCompatImageButton; 47 | import androidx.constraintlayout.widget.ConstraintLayout; 48 | import androidx.fragment.app.Fragment; 49 | import androidx.recyclerview.widget.LinearLayoutManager; 50 | import androidx.recyclerview.widget.LinearSmoothScroller; 51 | import androidx.recyclerview.widget.RecyclerView; 52 | 53 | 54 | public class ConversationFragment extends Fragment { 55 | private ProgressBar loading; 56 | private static final float LOADING_SIZE_DP = 24; 57 | private EditText editText; 58 | private AppCompatImageButton sendButton; 59 | private RecyclerView mRecyclerView; 60 | protected TextView description; 61 | private ConstraintLayout constraintLayout; 62 | private BluetoothCommunicator.Callback communicatorCallback; 63 | private Global global; 64 | private MainActivity activity; 65 | private MessagesAdapter mAdapter; 66 | private RecyclerView.SmoothScroller smoothScroller; 67 | 68 | public ConversationFragment() { 69 | //an empty constructor is always needed for fragments 70 | } 71 | 72 | @Override 73 | public void onCreate(@Nullable Bundle savedInstanceState) { 74 | super.onCreate(savedInstanceState); 75 | communicatorCallback = new BluetoothCommunicator.Callback() { 76 | @Override 77 | public void onConnectionLost(Peer peer) { 78 | super.onConnectionLost(peer); 79 | Toast.makeText(activity,"Connection lost, reconnecting...",Toast.LENGTH_LONG).show(); 80 | } 81 | 82 | @Override 83 | public void onConnectionResumed(Peer peer) { 84 | super.onConnectionResumed(peer); 85 | Toast.makeText(activity,"Connection resumed",Toast.LENGTH_LONG).show(); 86 | } 87 | 88 | @Override 89 | public void onMessageReceived(Message message, int source) { 90 | super.onMessageReceived(message, source); 91 | /* means that we have received a message containing TEXT, for know the sender we can call message.getSender() that return 92 | the peer that have sent the message, we can ignore source, it indicate only if we have received the message 93 | as clients or as servers 94 | */ 95 | mAdapter.addMessage(message); 96 | //smooth scroll 97 | smoothScroller.setTargetPosition(mAdapter.getItemCount() - 1); 98 | mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller); 99 | } 100 | 101 | @Override 102 | public void onDisconnected(Peer peer, int peersLeft) { 103 | super.onDisconnected(peer, peersLeft); 104 | /*means that the peer is disconnected, peersLeft indicate the number of connected peers remained 105 | */ 106 | if (peersLeft == 0) { 107 | activity.setFragment(MainActivity.DEFAULT_FRAGMENT); 108 | } 109 | } 110 | }; 111 | } 112 | 113 | @Override 114 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 115 | // Inflate the layout for this fragment 116 | return inflater.inflate(R.layout.fragment_conversation, container, false); 117 | } 118 | 119 | @Override 120 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 121 | super.onViewCreated(view, savedInstanceState); 122 | editText = view.findViewById(R.id.editText); 123 | sendButton = view.findViewById(R.id.button_send); 124 | mRecyclerView = view.findViewById(R.id.recycler_view); 125 | description = view.findViewById(R.id.description); 126 | loading = view.findViewById(R.id.progressBar2); 127 | constraintLayout = view.findViewById(R.id.container2); 128 | } 129 | 130 | @Override 131 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 132 | super.onActivityCreated(savedInstanceState); 133 | activity = (MainActivity) requireActivity(); 134 | global = (Global) activity.getApplication(); 135 | Toolbar toolbar = activity.findViewById(R.id.toolbarConversation); 136 | activity.setActionBar(toolbar); 137 | // we give the constraint layout the information on the system measures (status bar etc.), which has the fragmentContainer, 138 | // because they are not passed to it if started with a Transaction and therefore it overlaps the status bar because it fitsSystemWindows does not work 139 | WindowInsets windowInsets = activity.getFragmentContainer().getRootWindowInsets(); 140 | if (windowInsets != null) { 141 | constraintLayout.dispatchApplyWindowInsets(windowInsets.replaceSystemWindowInsets(windowInsets.getSystemWindowInsetLeft(), windowInsets.getSystemWindowInsetTop(), windowInsets.getSystemWindowInsetRight(), 0)); 142 | } 143 | 144 | LinearLayoutManager layoutManager = new LinearLayoutManager(activity); 145 | layoutManager.setStackFromEnd(true); 146 | mRecyclerView.setLayoutManager(layoutManager); 147 | smoothScroller = new LinearSmoothScroller(activity) { 148 | @Override 149 | protected int calculateTimeForScrolling(int dx) { 150 | return 100; 151 | } 152 | }; 153 | 154 | mAdapter = new MessagesAdapter(global.getBluetoothCommunicator().getUniqueName(), new MessagesAdapter.Callback() { 155 | @Override 156 | public void onFirstItemAdded() { 157 | description.setVisibility(View.GONE); 158 | mRecyclerView.setVisibility(View.VISIBLE); 159 | } 160 | }); 161 | mRecyclerView.setAdapter(mAdapter); 162 | 163 | sendButton.setOnClickListener(new View.OnClickListener() { 164 | @Override 165 | public void onClick(View v) { 166 | if (global.getBluetoothCommunicator().getConnectedPeersList().size() > 0) { 167 | //sending message 168 | if (editText.getText().length() > 0) { 169 | //the sender will be inserted by the receiver device, so you don't need to enter it 170 | Message message = new Message(global, "m", editText.getText().toString(), global.getBluetoothCommunicator().getConnectedPeersList().get(0)); 171 | global.getBluetoothCommunicator().sendMessage(message); 172 | editText.setText(""); 173 | //aggiunta del messaggio alla lista dei messaggi 174 | mAdapter.addMessage(message); 175 | //smooth scroll 176 | smoothScroller.setTargetPosition(mAdapter.getItemCount() - 1); 177 | mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller); 178 | } 179 | } 180 | } 181 | }); 182 | } 183 | 184 | @Override 185 | public void onResume() { 186 | super.onResume(); 187 | global.getBluetoothCommunicator().addCallback(communicatorCallback); 188 | } 189 | 190 | @Override 191 | public void onPause() { 192 | super.onPause(); 193 | global.getBluetoothCommunicator().removeCallback(communicatorCallback); 194 | } 195 | 196 | @Override 197 | public void onDestroy() { 198 | super.onDestroy(); 199 | } 200 | 201 | public void appearLoading() { 202 | int loadingSizePx = GuiTools.convertDpToPixels(activity, LOADING_SIZE_DP); 203 | CustomAnimator animator = new CustomAnimator(); 204 | Animator animation = animator.createAnimatorSize(loading, 1, 1, loadingSizePx, loadingSizePx, getResources().getInteger(R.integer.durationShort)); 205 | animation.addListener(new Animator.AnimatorListener() { 206 | @Override 207 | public void onAnimationStart(Animator animation) { 208 | if(loading != null) { 209 | loading.setVisibility(View.VISIBLE); 210 | } 211 | } 212 | 213 | @Override 214 | public void onAnimationEnd(Animator animation) { 215 | 216 | } 217 | 218 | @Override 219 | public void onAnimationCancel(Animator animation) { 220 | 221 | } 222 | 223 | @Override 224 | public void onAnimationRepeat(Animator animation) { 225 | 226 | } 227 | }); 228 | animation.start(); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/ButtonSearch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.content.Context; 20 | import android.content.res.ColorStateList; 21 | import android.graphics.drawable.Drawable; 22 | import android.util.AttributeSet; 23 | import android.view.animation.Animation; 24 | import android.view.animation.AnimationUtils; 25 | 26 | import androidx.annotation.Nullable; 27 | import androidx.appcompat.widget.AppCompatImageButton; 28 | 29 | import java.util.ArrayList; 30 | 31 | import com.bluetooth.communicatorexample.R; 32 | 33 | public class ButtonSearch extends AppCompatImageButton { 34 | private boolean searching = true; 35 | private boolean visible = true; 36 | private boolean animating = false; 37 | private ArrayList listeners = new ArrayList<>(); 38 | private OnClickListener clickListener; 39 | private int drawableId = R.drawable.cancel_icon; 40 | 41 | 42 | public ButtonSearch(Context context) { 43 | super(context); 44 | } 45 | 46 | public ButtonSearch(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | } 49 | 50 | public ButtonSearch(Context context, AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | } 53 | 54 | /** 55 | * When this method is called it update searching and if the SearchButton is not visible or has an animation in progress 56 | * the graphic update is applied at the end of the animation of the appearance 57 | */ 58 | public void setSearching(boolean searching, @Nullable CustomAnimator animator) { 59 | this.searching = searching; 60 | if (!animating && visible) { 61 | if (animator != null) { 62 | if (this.searching) { 63 | if (drawableId != R.drawable.cancel_icon) { // if graphically the object is not already searching 64 | // animation execution 65 | animating = true; 66 | animator.animateIconChange(this, getDrawable(R.drawable.cancel_icon), new CustomAnimator.EndListener() { 67 | @Override 68 | public void onAnimationEnd() { 69 | animating = false; 70 | drawableId = R.drawable.cancel_icon; 71 | // restore visible 72 | setVisible(visible, null); 73 | } 74 | }); 75 | } else { 76 | // restore visible 77 | setVisible(visible, null); 78 | } 79 | } else { 80 | if (drawableId != R.drawable.search_icon) { // if the object is not already graphically !searching 81 | // animation execution 82 | animating = true; 83 | animator.animateIconChange(this, getDrawable(R.drawable.search_icon), new CustomAnimator.EndListener() { 84 | @Override 85 | public void onAnimationEnd() { 86 | animating = false; 87 | drawableId = R.drawable.search_icon; 88 | // restore visible 89 | setVisible(visible, null); 90 | } 91 | }); 92 | } else { 93 | // restore visible 94 | setVisible(visible, null); 95 | } 96 | } 97 | } else { 98 | if (this.isSearching()) { 99 | setImageDrawable(getDrawable(R.drawable.cancel_icon)); 100 | drawableId = R.drawable.cancel_icon; 101 | } else { 102 | setImageDrawable(getDrawable(R.drawable.search_icon)); 103 | drawableId = R.drawable.search_icon; 104 | } 105 | } 106 | } 107 | } 108 | 109 | /** 110 | */ 111 | public void setVisible(boolean visible, @Nullable final CustomAnimator.EndListener listener) { 112 | if (listener != null) { 113 | listeners.add(listener); 114 | } 115 | this.visible = visible; 116 | if (!animating) { 117 | if (isVisible()) { 118 | // appearance 119 | if (getVisibility() != VISIBLE) { // if graphically the object is not already visible 120 | // animation execution 121 | this.animating = true; 122 | final Animation enlargeAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.enlarge_icon); 123 | enlargeAnimation.setDuration(getResources().getInteger(R.integer.durationShort)); 124 | startAnimation(enlargeAnimation); 125 | getAnimation().setAnimationListener(new Animation.AnimationListener() { 126 | @Override 127 | public void onAnimationStart(Animation animation) { 128 | setVisibility(VISIBLE); 129 | } 130 | 131 | @Override 132 | public void onAnimationEnd(Animation animation) { 133 | animating = false; 134 | if (!isVisible()) { // if visible has changed in the meantime 135 | setVisible(isVisible(), null); 136 | } else { 137 | notifySetVisibleSuccess(); 138 | // restore icon 139 | setSearching(searching, null); 140 | } 141 | } 142 | 143 | @Override 144 | public void onAnimationRepeat(Animation animation) { 145 | } 146 | }); 147 | } else { 148 | notifySetVisibleSuccess(); 149 | } 150 | } else { 151 | // disappearance 152 | if (getVisibility() != GONE) { // if graphically the object is not already !visible 153 | // animation execution 154 | this.animating = true; 155 | final Animation dwindleAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.dwindle_icon); 156 | dwindleAnimation.setDuration(getResources().getInteger(R.integer.durationShort)); 157 | startAnimation(dwindleAnimation); 158 | getAnimation().setAnimationListener(new Animation.AnimationListener() { 159 | @Override 160 | public void onAnimationStart(Animation animation) { 161 | } 162 | 163 | @Override 164 | public void onAnimationEnd(Animation animation) { 165 | setVisibility(GONE); 166 | animating = false; 167 | if (isVisible()) { // if visible has changed in the meantime 168 | setVisible(isVisible(), null); 169 | } else { 170 | notifySetVisibleSuccess(); 171 | } 172 | } 173 | 174 | @Override 175 | public void onAnimationRepeat(Animation animation) { 176 | } 177 | }); 178 | } else { 179 | notifySetVisibleSuccess(); 180 | } 181 | } 182 | } 183 | } 184 | 185 | private void notifySetVisibleSuccess() { 186 | // finished animation notification and elimination of listeners 187 | while (listeners.size() > 0) { 188 | listeners.remove(0).onAnimationEnd(); 189 | } 190 | } 191 | 192 | @Override 193 | public void setOnClickListener(@Nullable OnClickListener l) { 194 | clickListener = l; 195 | super.setOnClickListener(clickListener); 196 | } 197 | 198 | public boolean isSearching() { 199 | return searching; 200 | } 201 | 202 | public boolean isVisible() { 203 | return visible; 204 | } 205 | 206 | public Drawable getDrawable(int id) { 207 | Drawable drawable = getResources().getDrawable(id, null); 208 | drawable.setTintList(getColorStateList(getContext(), R.color.primary)); 209 | return drawable; 210 | } 211 | 212 | public static ColorStateList getColorStateList(Context context, int colorCode) { 213 | return context.getResources().getColorStateList(colorCode, null); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/CustomAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.animation.Animator; 20 | import android.animation.AnimatorSet; 21 | import android.animation.ValueAnimator; 22 | import android.graphics.drawable.Drawable; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.animation.Animation; 26 | import android.view.animation.AnimationUtils; 27 | import android.widget.ImageView; 28 | 29 | import androidx.annotation.Nullable; 30 | 31 | import com.bluetooth.communicatorexample.R; 32 | 33 | public class CustomAnimator { 34 | 35 | public void animateIconChange(final ImageView view, final Drawable newIcon, @Nullable final EndListener responseListener) { // check that the fact that the view gets smaller does not affect the rest of the graphics 36 | Animation dwindleAnimation = AnimationUtils.loadAnimation(view.getContext(), R.anim.dwindle_icon); 37 | dwindleAnimation.setDuration(view.getResources().getInteger(R.integer.durationShort) / 2); 38 | final Animation enlargeAnimation = AnimationUtils.loadAnimation(view.getContext(), R.anim.enlarge_icon); 39 | enlargeAnimation.setDuration(view.getResources().getInteger(R.integer.durationShort) / 2); 40 | view.startAnimation(dwindleAnimation); 41 | view.getAnimation().setAnimationListener(new Animation.AnimationListener() { 42 | @Override 43 | public void onAnimationStart(Animation animation) { 44 | } 45 | 46 | @Override 47 | public void onAnimationEnd(Animation animation) { 48 | view.setImageDrawable(newIcon); 49 | view.startAnimation(enlargeAnimation); 50 | if (responseListener != null) { 51 | view.getAnimation().setAnimationListener(new Animation.AnimationListener() { 52 | @Override 53 | public void onAnimationStart(Animation animation) { 54 | } 55 | 56 | @Override 57 | public void onAnimationEnd(Animation animation) { 58 | responseListener.onAnimationEnd(); 59 | } 60 | 61 | @Override 62 | public void onAnimationRepeat(Animation animation) { 63 | } 64 | }); 65 | } 66 | } 67 | 68 | @Override 69 | public void onAnimationRepeat(Animation animation) { 70 | } 71 | }); 72 | } 73 | 74 | public Animator createAnimatorSize(final View view, int initialWidthInPixels, int initialHeightInPixels, int finalWidthInPixels, int finalHeightInPixels, int duration) { 75 | AnimatorSet animatorSet = new AnimatorSet(); 76 | Animator animatorWidth = createAnimatorWidth(view, initialWidthInPixels, finalWidthInPixels, duration); 77 | Animator animatorHeight = createAnimatorHeight(view, initialHeightInPixels, finalHeightInPixels, duration); 78 | animatorSet.play(animatorWidth).with(animatorHeight); 79 | 80 | return animatorSet; 81 | } 82 | 83 | public Animator createAnimatorWidth(final View view, int initialPixels, int finalPixels, int duration) { 84 | ValueAnimator animator = ValueAnimator.ofInt(initialPixels, finalPixels); 85 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 86 | @Override 87 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 88 | if(view!=null) { 89 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 90 | layoutParams.width = (int) valueAnimator.getAnimatedValue(); 91 | view.setLayoutParams(layoutParams); 92 | } 93 | } 94 | }); 95 | animator.setDuration(duration); 96 | 97 | return animator; 98 | } 99 | 100 | public Animator createAnimatorHeight(final View view, int initialPixels, int finalPixels, int duration) { 101 | ValueAnimator animator = ValueAnimator.ofInt(initialPixels, finalPixels); 102 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 103 | @Override 104 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 105 | if(view!=null) { 106 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 107 | layoutParams.height = (int) valueAnimator.getAnimatedValue(); 108 | view.setLayoutParams(layoutParams); 109 | } 110 | } 111 | }); 112 | animator.setDuration(duration); 113 | 114 | return animator; 115 | } 116 | 117 | 118 | public abstract static class EndListener { 119 | public abstract void onAnimationEnd(); 120 | } 121 | } -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/GuiTools.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.content.Context; 20 | import android.util.DisplayMetrics; 21 | import android.util.TypedValue; 22 | 23 | public class GuiTools { 24 | public static int convertDpToPixels(Context context, float dp) { 25 | DisplayMetrics metrics = context.getResources().getDisplayMetrics(); 26 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/MessagesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.view.LayoutInflater; 20 | import android.view.View; 21 | import android.view.ViewGroup; 22 | import android.widget.LinearLayout; 23 | import android.widget.TextView; 24 | 25 | import androidx.annotation.NonNull; 26 | import androidx.cardview.widget.CardView; 27 | import androidx.recyclerview.widget.RecyclerView; 28 | 29 | import com.bluetooth.communicator.Message; 30 | import com.bluetooth.communicatorexample.R; 31 | 32 | 33 | import java.util.ArrayList; 34 | 35 | 36 | /** 37 | * Is used to connect to the RecycleView, which functions as a ListView, a list of strings, which will be inserted in the ViewHolder layout and this will be inserted in the list 38 | **/ 39 | public class MessagesAdapter extends RecyclerView.Adapter { 40 | private static final int MINE = 0; 41 | private static final int NON_MINE = 1; 42 | private ArrayList mResults = new ArrayList<>(); 43 | private Callback callback; 44 | private String myUniqueName; 45 | 46 | 47 | public MessagesAdapter(String myUniqueName, @NonNull Callback callback) { 48 | this.myUniqueName = myUniqueName; 49 | this.callback = callback; 50 | } 51 | 52 | public MessagesAdapter(ArrayList messages, String myUniqueName, @NonNull Callback callback) { 53 | this.myUniqueName = myUniqueName; 54 | this.callback = callback; 55 | if (messages != null) { 56 | if (messages.size() > 0) { 57 | callback.onFirstItemAdded(); 58 | } 59 | mResults.addAll(messages); 60 | notifyItemRangeInserted(0, messages.size() - 1); 61 | } 62 | } 63 | 64 | @NonNull 65 | @Override 66 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 67 | switch (viewType) { 68 | case MINE: 69 | return new SendHolder(LayoutInflater.from(parent.getContext()), parent); 70 | case NON_MINE: 71 | return new ReceivedHolder(LayoutInflater.from(parent.getContext()), parent); 72 | } 73 | return new SendHolder(LayoutInflater.from(parent.getContext()), parent); // to not return null 74 | } 75 | 76 | @Override 77 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 78 | if (holder instanceof MessageHolder) { 79 | Message message = mResults.get(position); 80 | if (holder instanceof ReceivedHolder && message.getSender() != null && message.getSender().getUniqueName().equals(myUniqueName)) { 81 | ((ReceivedHolder) holder).text.setVisibility(View.GONE); 82 | ((ReceivedHolder) holder).containerSender.setVisibility(View.VISIBLE); 83 | ((ReceivedHolder) holder).sender.setText(message.getSender().getName()); 84 | } 85 | ((MessageHolder) holder).setText(message.getText()); 86 | } 87 | } 88 | 89 | @Override 90 | public int getItemViewType(int position) { 91 | Message message = mResults.get(position); 92 | if (message.getSender() == null || message.getSender().getUniqueName().equals(myUniqueName)) { 93 | return MINE; 94 | } else { 95 | return NON_MINE; 96 | } 97 | } 98 | 99 | @Override 100 | public long getItemId(int position) { 101 | return position; 102 | } 103 | 104 | @Override 105 | public int getItemCount() { 106 | return mResults.size(); 107 | } 108 | 109 | @Override 110 | public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { 111 | super.onViewRecycled(holder); 112 | } 113 | 114 | @Override 115 | public boolean onFailedToRecycleView(@NonNull RecyclerView.ViewHolder holder) { 116 | return true; 117 | } 118 | 119 | public void addMessage(Message message) { 120 | if (getItemCount() == 0) { 121 | callback.onFirstItemAdded(); 122 | } 123 | mResults.add(message); 124 | notifyItemInserted(getItemCount() - 1); 125 | } 126 | 127 | public Message getMessage(int index) { 128 | return mResults.get(index); 129 | } 130 | 131 | public int indexOf(Message message) { 132 | return mResults.indexOf(message); 133 | } 134 | 135 | public ArrayList getMessages() { 136 | return mResults; 137 | } 138 | 139 | /** 140 | * The layout for each item in the RecicleView list 141 | */ 142 | private class ReceivedHolder extends RecyclerView.ViewHolder implements MessageHolder { 143 | TextView text; 144 | 145 | LinearLayout containerSender; 146 | TextView textSender; 147 | TextView sender; 148 | 149 | 150 | ReceivedHolder(LayoutInflater inflater, ViewGroup parent) { 151 | super(inflater.inflate(R.layout.component_message_received, parent, false)); 152 | text = itemView.findViewById(R.id.text_content); 153 | 154 | containerSender = itemView.findViewById(R.id.sender_container); 155 | textSender = itemView.findViewById(R.id.text_content2); 156 | sender = itemView.findViewById(R.id.text_sender); 157 | } 158 | 159 | @Override 160 | public void setText(String text) { 161 | this.textSender.setText(text); 162 | this.text.setText(text); 163 | } 164 | } 165 | 166 | /** 167 | * The layout for each item in the RecicleView list 168 | */ 169 | private class SendHolder extends RecyclerView.ViewHolder implements MessageHolder { 170 | TextView text; 171 | CardView card; 172 | 173 | SendHolder(LayoutInflater inflater, ViewGroup parent) { 174 | super(inflater.inflate(R.layout.component_message_send, parent, false)); 175 | text = itemView.findViewById(R.id.text); 176 | card = itemView.findViewById(R.id.card); 177 | } 178 | 179 | @Override 180 | public void setText(String text) { 181 | this.text.setText(text); 182 | } 183 | } 184 | 185 | interface MessageHolder { 186 | void setText(String text); 187 | } 188 | 189 | public interface Callback { 190 | void onFirstItemAdded(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/PeerListAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.app.Activity; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | import android.widget.BaseAdapter; 24 | import android.widget.TextView; 25 | 26 | import com.bluetooth.communicator.Peer; 27 | import com.bluetooth.communicatorexample.R; 28 | 29 | 30 | import java.util.ArrayList; 31 | 32 | public class PeerListAdapter extends BaseAdapter { 33 | public static final int HOST = 1; 34 | private ArrayList array; 35 | private LayoutInflater inflater; 36 | private Callback callback; 37 | private CustomAnimator animator = new CustomAnimator(); 38 | private Activity activity; 39 | private boolean isClickable = true; 40 | private boolean showToast = false; 41 | 42 | public PeerListAdapter(Activity activity, ArrayList array, Callback callback) { 43 | this.array = array; 44 | this.callback = callback; 45 | if (array.size() > 0) { 46 | callback.onFirstItemAdded(); 47 | } 48 | this.activity = activity; 49 | notifyDataSetChanged(); 50 | inflater = activity.getLayoutInflater(); 51 | } 52 | 53 | @Override 54 | public int getCount() { 55 | return array.size(); 56 | } 57 | 58 | @Override 59 | public Object getItem(int i) { 60 | return array.get(i); 61 | } 62 | 63 | @Override 64 | public long getItemId(int i) { 65 | return i; 66 | } 67 | 68 | @Override 69 | public boolean hasStableIds() { 70 | return true; 71 | } 72 | 73 | @Override 74 | public int getItemViewType(int position) { 75 | return HOST; 76 | } 77 | 78 | @Override 79 | public int getViewTypeCount() { 80 | return 1; 81 | } 82 | 83 | @Override 84 | public View getView(int position, View view, ViewGroup viewGroup) { 85 | final Object item = getItem(position); 86 | int itemType = getItemViewType(position); 87 | if (itemType == HOST) { 88 | Peer guiPeer = (Peer) item; 89 | String peerName = ((Peer) item).getName(); 90 | if (view == null) { 91 | view = inflater.inflate(R.layout.component_row, null); 92 | } 93 | ((TextView) view.findViewById(R.id.textRow)).setText(peerName); 94 | 95 | } 96 | return view; 97 | } 98 | 99 | public synchronized void add(Peer peer) { 100 | if (array.size() == 0) { 101 | callback.onFirstItemAdded(); 102 | } 103 | array.add(peer); 104 | notifyDataSetChanged(); 105 | } 106 | 107 | public synchronized void set(int index, Peer item) { 108 | array.set(index, item); 109 | notifyDataSetChanged(); 110 | } 111 | 112 | public Peer get(int i) { 113 | return array.get(i); 114 | } 115 | 116 | public int indexOf(Peer object) { 117 | return array.indexOf(object); 118 | } 119 | 120 | public int indexOfPeer(String uniqueName) { 121 | for (int i = 0; i < array.size(); i++) { 122 | Peer peer = array.get(i); 123 | String uniqueName1 = peer.getUniqueName(); 124 | if (uniqueName1.length() > 0 && uniqueName1.equals(uniqueName)) { 125 | return i; 126 | } 127 | } 128 | return -1; 129 | } 130 | 131 | public synchronized void remove(Peer peer) { 132 | if (array.remove(peer)) { 133 | notifyDataSetChanged(); 134 | } 135 | if (array.size() == 0) { 136 | // deleting the listview 137 | callback.onLastItemRemoved(); 138 | } 139 | } 140 | 141 | public synchronized void clear() { 142 | array.clear(); 143 | notifyDataSetChanged(); 144 | if (array.size() == 0) { 145 | callback.onLastItemRemoved(); 146 | } 147 | } 148 | 149 | public int size() { 150 | return array.size(); 151 | } 152 | 153 | public void setCallback(Callback callback) { 154 | this.callback = callback; 155 | } 156 | 157 | public boolean isClickable() { 158 | return isClickable; 159 | } 160 | 161 | public void setClickable(boolean clickable, boolean showToast) { 162 | this.isClickable = clickable; 163 | this.showToast = showToast; 164 | } 165 | 166 | public boolean getShowToast() { 167 | return showToast; 168 | } 169 | 170 | public void setShowToast(boolean showToast) { 171 | this.showToast = showToast; 172 | } 173 | 174 | public Callback getCallback() { 175 | return callback; 176 | } 177 | 178 | public static abstract class Callback { 179 | public void onFirstItemAdded() { 180 | } 181 | 182 | public void onLastItemRemoved() { 183 | } 184 | 185 | public void onClickNotAllowed(boolean showToast) { 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/gui/RequestDialog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.gui; 18 | 19 | import android.app.Activity; 20 | import android.content.DialogInterface; 21 | import android.view.View; 22 | import android.widget.TextView; 23 | 24 | import androidx.appcompat.app.AlertDialog; 25 | 26 | import com.bluetooth.communicatorexample.R; 27 | import com.bluetooth.communicatorexample.tools.Timer; 28 | 29 | public class RequestDialog { 30 | private final int SHOW_TIMER_SECONDS = 5; 31 | private TextView time; 32 | private long timeout = -1; 33 | private final AlertDialog alertDialog; 34 | 35 | public RequestDialog(Activity context, String message, long timeout, DialogInterface.OnClickListener positiveListener, DialogInterface.OnClickListener negativeListener) { 36 | final View dialogLayout = context.getLayoutInflater().inflate(R.layout.dialog_connection_request, null); 37 | TextView textViewMessage = dialogLayout.findViewById(R.id.connection_request_text); 38 | this.time = dialogLayout.findViewById(R.id.countDown); 39 | this.timeout = timeout; 40 | textViewMessage.setText(message); 41 | //dialog creation 42 | final AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(context); 43 | builder.setView(dialogLayout); 44 | builder.setCancelable(true); 45 | builder.setPositiveButton(android.R.string.yes, positiveListener); 46 | builder.setNegativeButton(android.R.string.no, negativeListener); 47 | 48 | alertDialog = builder.create(); 49 | alertDialog.setCanceledOnTouchOutside(false); 50 | } 51 | 52 | public RequestDialog(Activity context, String message, DialogInterface.OnClickListener positiveListener, DialogInterface.OnClickListener negativeListener) { 53 | //dialog creation 54 | final AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(context); 55 | builder.setMessage(message); 56 | builder.setCancelable(true); 57 | builder.setPositiveButton(android.R.string.yes, positiveListener); 58 | builder.setNegativeButton(android.R.string.no, negativeListener); 59 | 60 | alertDialog = builder.create(); 61 | alertDialog.setCanceledOnTouchOutside(false); 62 | } 63 | 64 | public void show() { 65 | alertDialog.show(); 66 | if (timeout != -1) { 67 | new Timer(timeout, new Timer.DateCallback() { 68 | @Override 69 | public void onTick(int hoursUntilEnd, int minutesUntilEnd, int secondsUntilEnd) { 70 | if (secondsUntilEnd <= SHOW_TIMER_SECONDS) { 71 | if (time.getVisibility() == View.INVISIBLE) { 72 | time.setVisibility(View.VISIBLE); 73 | } 74 | time.setText(String.valueOf(secondsUntilEnd)); 75 | } 76 | } 77 | 78 | @Override 79 | public void onEnd() { 80 | alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick(); 81 | } 82 | }).start(); 83 | } 84 | } 85 | 86 | public void setOnCancelListener(DialogInterface.OnCancelListener onDismissListener) { 87 | alertDialog.setOnCancelListener(onDismissListener); 88 | } 89 | 90 | public void cancel() { 91 | alertDialog.cancel(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/tools/Timer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.tools; 18 | 19 | import android.os.Handler; 20 | import android.os.Looper; 21 | 22 | import com.bluetooth.communicator.tools.CustomCountDownTimer; 23 | 24 | 25 | public class Timer { 26 | private final long hourMillis=3600000; 27 | private final long minuteMillis=60000; 28 | private final long secondMillis=1000; 29 | private CustomCountDownTimer timer; 30 | private long duration; 31 | private Handler mainHandler; 32 | 33 | public Timer(long durationMillis, final DateCallback dateCallback){ 34 | mainHandler = new Handler(Looper.getMainLooper()); 35 | duration=durationMillis; 36 | timer= new CustomCountDownTimer(durationMillis,1000) { 37 | @Override 38 | public void onTick(long millisUntilEnd) { 39 | int[] date=convertIntoDate(millisUntilEnd); 40 | dateCallback.onTick(date[0],date[1],date[2]); 41 | } 42 | 43 | @Override 44 | public void onFinish() { 45 | dateCallback.onEnd(); 46 | } 47 | }; 48 | } 49 | 50 | public Timer(long durationMillis, long intervalMillis, final Callback dateCallback){ 51 | mainHandler = new Handler(Looper.getMainLooper()); 52 | duration=durationMillis; 53 | timer= new CustomCountDownTimer(durationMillis,intervalMillis) { 54 | @Override 55 | public void onTick(long millisUntilEnd) { 56 | dateCallback.onTick(millisUntilEnd); 57 | } 58 | 59 | @Override 60 | public void onFinish() { 61 | dateCallback.onEnd(); 62 | } 63 | }; 64 | } 65 | 66 | public void start(){ 67 | mainHandler.post(new Runnable() { 68 | @Override 69 | public void run() { 70 | timer.start(); 71 | } 72 | }); 73 | } 74 | 75 | public void cancel(){ 76 | mainHandler.post(new Runnable() { 77 | @Override 78 | public void run() { 79 | timer.cancel(); 80 | } 81 | }); 82 | } 83 | 84 | public int[] getDuration(){ 85 | return convertIntoDate(duration); 86 | } 87 | 88 | private int[] convertIntoDate(long millis){ 89 | int hours=0; 90 | int minutes=0; 91 | int seconds=0; 92 | if(millis>hourMillis){ 93 | long rest=millis%hourMillis; 94 | hours= (int) ((millis-rest)/hourMillis); 95 | millis=rest; 96 | } 97 | if(millis>minuteMillis){ 98 | long rest=millis%minuteMillis; 99 | minutes= (int) ((millis-rest)/minuteMillis); 100 | millis=rest; 101 | } 102 | if(millis>secondMillis){ 103 | long rest=millis%secondMillis; 104 | seconds= (int) ((millis-rest)/secondMillis); 105 | } 106 | 107 | return new int[]{hours,minutes,seconds}; 108 | } 109 | 110 | public interface DateCallback { 111 | void onTick(int hoursUntilEnd, int minutesUntilEnd, int secondsUntilEnd); 112 | void onEnd(); 113 | } 114 | 115 | public interface Callback { 116 | void onTick(long millisUntilEnd); 117 | void onEnd(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /apptest/src/main/java/com/bluetooth/communicatorexample/tools/Tools.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Luca Martino. 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 copyFile 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.bluetooth.communicatorexample.tools; 18 | 19 | import android.content.Context; 20 | import android.content.pm.PackageManager; 21 | 22 | import androidx.core.content.ContextCompat; 23 | 24 | public class Tools { 25 | /** 26 | * Returns true if the app was granted all the permissions. Otherwise, returns false. 27 | */ 28 | public static boolean hasPermissions(Context context, String... permissions) { 29 | for (String permission : permissions) { 30 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { 31 | return false; 32 | } 33 | } 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apptest/src/main/res/anim/dwindle_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /apptest/src/main/res/anim/enlarge_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /apptest/src/main/res/drawable-v24/cancel_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /apptest/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /apptest/src/main/res/drawable-v24/search_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /apptest/src/main/res/drawable-v24/send_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /apptest/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/component_message_received.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 40 | 41 | 58 | 59 | 65 | 66 | 83 | 84 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/component_message_send.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 26 | 27 | 41 | 42 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/component_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 39 | 40 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/dialog_connection_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 40 | 41 | 57 | 58 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/fragment_conversation.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 28 | 29 | 44 | 45 | 61 | 62 | 83 | 84 | 103 | 104 | 119 | 120 | 136 | 137 | -------------------------------------------------------------------------------- /apptest/src/main/res/layout/fragment_pairing.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 27 | 28 | 43 | 44 | 61 | 62 | 81 | 82 | 83 | 84 | 98 | 99 | 102 | 103 | 119 | 120 | 135 | 136 | 153 | 154 | 172 | 173 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /apptest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/apptest/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /apptest/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #43a047 20 | #388e3c 21 | #66bb6a 22 | #36b024 23 | #ff0099cc 24 | #320099cc 25 | #5a43a047 26 | #5a43a047 27 | #cfd8dc 28 | #212121 29 | #404040 30 | #5A5A5A 31 | #757575 32 | #8C8C8C 33 | #9e9e9e 34 | #bdbdbd 35 | #e0e0e0 36 | #F5F5F5 37 | #fafafa 38 | #ffffff 39 | #d50000 40 | #ffd600 41 | #00c853 42 | -------------------------------------------------------------------------------- /apptest/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 200 20 | -------------------------------------------------------------------------------- /apptest/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | BluetoothCommunicatorExample 20 | -------------------------------------------------------------------------------- /apptest/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 31 | 32 | 36 | 37 | -------------------------------------------------------------------------------- /apptest/src/test/java/com/bluetooth/communicatorexample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bluetooth.communicatorexample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.6.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niedev/BluetoothCommunicator/847f6b35a7699347260b1c3aa636ae30dcbf3036/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 22 16:31:50 CEST 2020 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-5.6.4-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':apptest' 2 | include ':app' 3 | rootProject.name = "BluetoothCommunicator" --------------------------------------------------------------------------------