8 |
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 |
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"
--------------------------------------------------------------------------------